diff --git a/.forgejo/issue_template/bug.yaml b/.forgejo/issue_template/bug.yaml new file mode 100644 index 000000000..1b92658f7 --- /dev/null +++ b/.forgejo/issue_template/bug.yaml @@ -0,0 +1,25 @@ +name: 'Bug report' +about: 'Report a bug in Pleroma' +body: +- type: markdown + attributes: + value: | + ### Precheck + + * For support use https://git.pleroma.social/pleroma/pleroma-support or [community channels](https://git.pleroma.social/pleroma/pleroma#community-channels). + * Please do a quick search to ensure no similar bug has been reported before. If the bug has not been addressed after 2 weeks, it's fine to bump it. + * Try to ensure that the bug is actually related to the Pleroma backend. For example, if a bug happens in Pleroma-FE but not in Mastodon-FE or mobile clients, it's likely that the bug should be filed in [Pleroma-FE](https://git.pleroma.social/pleroma/pleroma-fe/issues/new) repository. +- type: textarea + id: environment + attributes: + label: Environment + value: | + * Installation type (OTP or From Source): + * Pleroma version (could be found in the "Version" tab of settings in Pleroma-FE): + * Elixir version (`elixir -v` for from source installations, N/A for OTP): + * Operating system: + * PostgreSQL version (`psql -V`): +- type: textarea + id: bug-description + attributes: + label: Bug description \ No newline at end of file diff --git a/.forgejo/pull_request_template.md b/.forgejo/pull_request_template.md new file mode 100644 index 000000000..5bb204a14 --- /dev/null +++ b/.forgejo/pull_request_template.md @@ -0,0 +1,13 @@ +### Checklist + +- [ ] Adding a changelog: In the `changelog.d` directory, create a file named `.`. + + diff --git a/.woodpecker/changelog.yaml b/.woodpecker/changelog.yaml new file mode 100644 index 000000000..64062f17e --- /dev/null +++ b/.woodpecker/changelog.yaml @@ -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 diff --git a/.woodpecker/docker-combine.yaml b/.woodpecker/docker-combine.yaml new file mode 100644 index 000000000..6c91d26e9 --- /dev/null +++ b/.woodpecker/docker-combine.yaml @@ -0,0 +1,60 @@ +when: + - event: push + branch: ${CI_REPO_DEFAULT_BRANCH} + path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**", "Dockerfile" ] + - event: tag + - event: manual + branch: ${CI_REPO_DEFAULT_BRANCH} + - event: manual + branch: stable + +depends_on: + - docker + +skip_clone: true + +labels: + platform: linux/amd64 + +steps: + docker-develop-combine: + image: git.fluffytail.org/phnt/wpc-docker-tagger:latest + when: + - event: push + branch: ${CI_REPO_DEFAULT_BRANCH} + - event: manual + branch: ${CI_REPO_DEFAULT_BRANCH} + settings: &docker_settings + registry: "git.pleroma.social" + image: "pleroma/pleroma" + architectures: [amd64, arm64] + tags: + - latest + - develop + - ${CI_COMMIT_SHA:0:8} + username: + from_secret: pleroma-ci-user + password: + from_secret: pleroma-ci-password + + docker-stable-combine: + image: git.fluffytail.org/phnt/wpc-docker-tagger:latest + when: + - evaluate: 'CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG == ""' + settings: + <<: *docker_settings + tags: &stable_docker_tags + - latest + - stable + - ${CI_COMMIT_SHA:0:8} + + docker-stable-tag-combine: + image: git.fluffytail.org/phnt/wpc-docker-tagger:latest + when: + - event: tag + - evaluate: 'CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG != ""' + settings: + <<: *docker_settings + tags: + - <<: *stable_docker_tags + - ${CI_COMMIT_TAG} diff --git a/.woodpecker/docker.yaml b/.woodpecker/docker.yaml new file mode 100644 index 000000000..abc6bfa3b --- /dev/null +++ b/.woodpecker/docker.yaml @@ -0,0 +1,96 @@ +when: + - event: push + branch: ${CI_REPO_DEFAULT_BRANCH} + path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**", "Dockerfile" ] + - event: tag + - event: manual + branch: ${CI_REPO_DEFAULT_BRANCH} + - event: manual + branch: stable + +matrix: + platform: + - linux/amd64 + - linux/arm64 + +# This is needed for the when clauses below. +labels: + platform: ${platform} + memory: 'high' + +variables: + docker_variables: &docker_variables + repo: pleroma/pleroma + registry: git.pleroma.social + username: + from_secret: pleroma-ci-user + password: + from_secret: pleroma-ci-password + kaniko_image: &kaniko_image woodpeckerci/plugin-kaniko:2.3.1 + +steps: + docker-develop-amd64: + image: woodpeckerci/plugin-kaniko:2.3.1 + when: + - evaluate: 'platform == "linux/amd64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + settings: + <<: *docker_variables + tags: + - latest-amd64 + - develop-amd64 + - ${CI_COMMIT_SHA:0:8}-amd64 + + docker-develop-arm64: + image: woodpeckerci/plugin-kaniko:2.3.1 + when: + - evaluate: 'platform == "linux/arm64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + settings: + <<: *docker_variables + tags: + - latest-arm64 + - develop-arm64 + - ${CI_COMMIT_SHA:0:8}-arm64 + + docker-stable-amd64: + image: *kaniko_image + when: + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG == ""' + settings: + <<: *docker_variables + tags: &amd64_tags + - latest-amd64 + - stable-amd64 + - ${CI_COMMIT_SHA:0:8}-amd64 + + docker-stable-tag-amd64: + image: *kaniko_image + when: + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"' + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG != ""' + settings: + <<: *docker_variables + tags: + - <<: *amd64_tags + - ${CI_COMMIT_TAG}-amd64 + + docker-stable-arm64: + image: *kaniko_image + when: + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG == ""' + settings: + <<: *docker_variables + tags: &arm64_tags + - latest-arm64 + - stable-arm64 + - ${CI_COMMIT_SHA:0:8}-arm64 + + docker-stable-tag-arm64: + image: *kaniko_image + when: + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"' + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG != ""' + settings: + <<: *docker_variables + tags: + - <<: *arm64_tags + - ${CI_COMMIT_TAG}-arm64 diff --git a/.woodpecker/lint.yaml b/.woodpecker/lint.yaml new file mode 100644 index 000000000..7b2a1683c --- /dev/null +++ b/.woodpecker/lint.yaml @@ -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 diff --git a/.woodpecker/otp-musl.yaml b/.woodpecker/otp-musl.yaml new file mode 100644 index 000000000..60558b893 --- /dev/null +++ b/.woodpecker/otp-musl.yaml @@ -0,0 +1,292 @@ +when: + - event: push + branch: ${CI_REPO_DEFAULT_BRANCH} + path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ] + - event: tag + - event: manual + branch: ${CI_REPO_DEFAULT_BRANCH} + - event: manual + branch: stable + +matrix: + platform: + - linux/amd64 + - linux/arm + - linux/arm64 + +# This is needed for the when clauses below. +# When the platform clause is fixed, this might not be needed anymore +labels: + platform: ${platform} + +variables: + script_file_entrypoint: &script_file_entrypoint + - /bin/sh + - -c + - 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh' + build_cmds: &build_cmds + - apk add git build-base cmake file-dev openssl vips-dev zip + - echo "import Config" > config/prod.secret.exs + - mix local.hex --force + - mix local.rebar --force + - mix deps.get --only prod + - mkdir release + - export PLEROMA_BUILD_BRANCH=${CI_COMMIT_BRANCH} + - mix release --path release + build_image_amd64: &build_image_amd64 docker.io/hexpm/elixir-amd64:1.17.3-erlang-27.3.4.2-alpine-3.22.1 + build_image_arm: &build_image_arm docker.io/arm32v7/elixir:1.17.3-alpine + build_image_arm64: &build_image_arm64 docker.io/hexpm/elixir-arm64:1.17.3-erlang-27.3.4.2-alpine-3.22.1 + artifacts_uploader_image: &artifacts_uploader_image docker.io/woodpeckercommunity/plugin-gitea-package:0.5.0 + artifacts_uploader_settings: &artifacts_uploader_settings + user: + from_secret: pleroma-ci-user + password: + from_secret: pleroma-ci-password + owner: 'pleroma' + env: &env + MIX_ENV: prod + VIX_COMPILATION_MODE: PLATFORM_PROVIDED_LIBVIPS + +steps: + otp-develop-amd64-musl: + image: *build_image_amd64 + entrypoint: *script_file_entrypoint + when: + - evaluate: 'platform == "linux/amd64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + environment: *env + commands: &amd64_build + - <<: *build_cmds + - zip -9rq ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64-musl.zip release + + otp-stable-amd64-musl: + image: *build_image_amd64 + entrypoint: *script_file_entrypoint + when: + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"' + environment: *env + commands: *amd64_build + + # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable + otp-stable-tag-amd64-musl: + image: *build_image_amd64 + entrypoint: *script_file_entrypoint + when: + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"' + environment: *env + commands: + - <<: *build_cmds + - zip -9rq stable-${CI_COMMIT_SHA:0:8}-amd64-musl.zip release + + otp-develop-arm-musl: + image: *build_image_arm + entrypoint: *script_file_entrypoint + when: + - evaluate: 'platform == "linux/arm" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + environment: *env + commands: &arm_build + - <<: *build_cmds + - zip -9rq ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm-musl.zip release + + otp-stable-arm-musl: + image: *build_image_arm + entrypoint: *script_file_entrypoint + when: + - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"' + environment: *env + commands: *arm_build + + # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable + otp-stable-tag-arm-musl: + image: *build_image_arm + entrypoint: *script_file_entrypoint + when: + - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"' + environment: *env + commands: + - <<: *build_cmds + - zip -9rq stable-${CI_COMMIT_SHA:0:8}-arm-musl.zip release + + otp-develop-arm64-musl: + image: *build_image_arm64 + entrypoint: *script_file_entrypoint + when: + - evaluate: 'platform == "linux/arm64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + environment: *env + commands: &arm64_build + - <<: *build_cmds + - zip -9rq ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64-musl.zip release + + otp-stable-arm64-musl: + image: *build_image_arm64 + entrypoint: *script_file_entrypoint + when: + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"' + environment: *env + commands: *arm64_build + + # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable + otp-stable-tag-arm64-musl: + image: *build_image_arm64 + entrypoint: *script_file_entrypoint + when: + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"' + environment: *env + commands: + - <<: *build_cmds + - zip -9rq stable-${CI_COMMIT_SHA:0:8}-arm64-musl.zip release + + upload-artifacts-amd64-musl: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-${CI_COMMIT_BRANCH}-amd64-musl + package_version: ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64-musl + file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64-musl.zip + file_name: pleroma-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64-musl.zip + update: 'true' + + upload-latest-amd64-musl: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-${CI_COMMIT_BRANCH}-amd64-musl + package_version: latest + file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64-musl.zip + file_name: pleroma.zip + update: 'true' + + # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable + upload-artifacts-tag-amd64-musl: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-stable-amd64-musl + package_version: stable-${CI_COMMIT_SHA:0:8}-amd64-musl + file_source: ./stable-${CI_COMMIT_SHA:0:8}-amd64-musl.zip + file_name: pleroma-stable-${CI_COMMIT_SHA:0:8}-amd64-musl.zip + update: 'true' + + # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable + upload-latest-tag-amd64-musl: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-stable-amd64-musl + package_version: latest + file_source: ./stable-${CI_COMMIT_SHA:0:8}-amd64-musl.zip + file_name: pleroma.zip + update: 'true' + + upload-artifacts-arm-musl: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm-musl + package_version: ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm-musl + file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm-musl.zip + file_name: pleroma-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm-musl.zip + update: 'true' + + upload-latest-arm-musl: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm-musl + package_version: latest + file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm-musl.zip + file_name: pleroma.zip + update: 'true' + + # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable + upload-artifacts-tag-arm-musl: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-stable-arm-musl + package_version: stable-${CI_COMMIT_SHA:0:8}-arm-musl + file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm-musl.zip + file_name: pleroma-stable-${CI_COMMIT_SHA:0:8}-arm-musl.zip + update: 'true' + + # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable + upload-latest-tag-arm-musl: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-stable-arm-musl + package_version: latest + file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm-musl.zip + file_name: pleroma.zip + update: 'true' + + upload-artifacts-arm64-musl: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm64-musl + package_version: ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64-musl + file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64-musl.zip + file_name: pleroma-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64-musl.zip + update: 'true' + + upload-latest-arm64-musl: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm64-musl + package_version: latest + file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64-musl.zip + file_name: pleroma.zip + update: 'true' + + # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable + upload-artifacts-tag-arm64-musl: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-stable-arm64-musl + package_version: stable-${CI_COMMIT_SHA:0:8}-arm64-musl + file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm64-musl.zip + file_name: pleroma-stable-${CI_COMMIT_SHA:0:8}-arm64-musl.zip + update: 'true' + + # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable + upload-latest-tag-arm64-musl: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-stable-arm64-musl + package_version: latest + file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm64-musl.zip + file_name: pleroma.zip + update: 'true' diff --git a/.woodpecker/otp.yaml b/.woodpecker/otp.yaml new file mode 100644 index 000000000..9a33c228e --- /dev/null +++ b/.woodpecker/otp.yaml @@ -0,0 +1,293 @@ +when: + - event: push + branch: ${CI_REPO_DEFAULT_BRANCH} + path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ] + - event: tag + - event: manual + branch: ${CI_REPO_DEFAULT_BRANCH} + - event: manual + branch: stable + +matrix: + platform: + - linux/amd64 + - linux/arm + - linux/arm64 + +# This is needed for the when clauses below. +# When the platform clause is fixed, this might not be needed anymore +labels: + platform: ${platform} + +variables: + script_file_entrypoint: &script_file_entrypoint + - /bin/sh + - -c + - 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh' + build_cmds: &build_cmds + - apt-get update && apt-get install -y cmake libmagic-dev libvips-dev erlang-dev git build-essential zip + - echo "import Config" > config/prod.secret.exs + - mix local.hex --force + - mix local.rebar --force + - mix deps.get --only prod + - mkdir release + - export PLEROMA_BUILD_BRANCH=${CI_COMMIT_BRANCH} + - mix release --path release + build_image_amd64: &build_image_amd64 docker.io/hexpm/elixir-amd64:1.17.3-erlang-27.3.4.2-ubuntu-noble-20250716 + build_image_arm: &build_image_arm docker.io/arm32v7/elixir:1.17.3 + build_image_arm64: &build_image_arm64 docker.io/hexpm/elixir-arm64:1.17.3-erlang-27.3.4.2-ubuntu-noble-20250716 + artifacts_uploader_image: &artifacts_uploader_image docker.io/woodpeckercommunity/plugin-gitea-package:0.5.0 + artifacts_uploader_settings: &artifacts_uploader_settings + user: + from_secret: pleroma-ci-user + password: + from_secret: pleroma-ci-password + owner: 'pleroma' + env: &env + MIX_ENV: prod + VIX_COMPILATION_MODE: PLATFORM_PROVIDED_LIBVIPS + DEBIAN_FRONTEND: noninteractive + +steps: + otp-develop-amd64: + image: *build_image_amd64 + entrypoint: *script_file_entrypoint + when: + - evaluate: 'platform == "linux/amd64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + environment: *env + commands: &amd64_build + - <<: *build_cmds + - zip -9rq ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64.zip release + + otp-stable-amd64: + image: *build_image_amd64 + entrypoint: *script_file_entrypoint + when: + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"' + environment: *env + commands: *amd64_build + + # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable + otp-stable-tag-amd64: + image: *build_image_amd64 + entrypoint: *script_file_entrypoint + when: + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"' + environment: *env + commands: + - <<: *build_cmds + - zip -9rq stable-${CI_COMMIT_SHA:0:8}-amd64.zip release + + otp-develop-arm: + image: *build_image_arm + entrypoint: *script_file_entrypoint + when: + - evaluate: 'platform == "linux/arm" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + environment: *env + commands: &arm_build + - <<: *build_cmds + - zip -9rq ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm.zip release + + otp-stable-arm: + image: *build_image_arm + entrypoint: *script_file_entrypoint + when: + - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"' + environment: *env + commands: *arm_build + + # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable + otp-stable-tag-arm: + image: *build_image_arm + entrypoint: *script_file_entrypoint + when: + - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"' + environment: *env + commands: + - <<: *build_cmds + - zip -9rq stable-${CI_COMMIT_SHA:0:8}-arm.zip release + + otp-develop-arm64: + image: *build_image_arm64 + entrypoint: *script_file_entrypoint + when: + - evaluate: 'platform == "linux/arm64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + environment: *env + commands: &arm64_build + - <<: *build_cmds + - zip -9rq ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64.zip release + + otp-stable-arm64: + image: *build_image_arm64 + entrypoint: *script_file_entrypoint + when: + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"' + environment: *env + commands: *arm64_build + + # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable + otp-stable-tag-arm64: + image: *build_image_arm64 + entrypoint: *script_file_entrypoint + when: + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"' + environment: *env + commands: + - <<: *build_cmds + - zip -9rq stable-${CI_COMMIT_SHA:0:8}-arm64.zip release + + upload-artifacts-amd64: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-${CI_COMMIT_BRANCH}-amd64 + package_version: ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64 + file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64.zip + file_name: pleroma-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64.zip + update: 'true' + + upload-latest-amd64: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-${CI_COMMIT_BRANCH}-amd64 + package_version: latest + file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64.zip + file_name: pleroma.zip + update: 'true' + + # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable + upload-artifacts-tag-amd64: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-stable-amd64 + package_version: stable-${CI_COMMIT_SHA:0:8}-amd64 + file_source: ./stable-${CI_COMMIT_SHA:0:8}-amd64.zip + file_name: pleroma-stable-${CI_COMMIT_SHA:0:8}-amd64.zip + update: 'true' + + # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable + upload-latest-tag-amd64: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-stable-amd64 + package_version: latest + file_source: ./stable-${CI_COMMIT_SHA:0:8}-amd64.zip + file_name: pleroma.zip + update: 'true' + + upload-artifacts-arm: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm + package_version: ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm + file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm.zip + file_name: pleroma-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm.zip + update: 'true' + + upload-latest-arm: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm + package_version: latest + file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm.zip + file_name: pleroma.zip + update: 'true' + + # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable + upload-artifacts-tag-arm: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-stable-arm + package_version: stable-${CI_COMMIT_SHA:0:8}-arm + file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm.zip + file_name: pleroma-stable-${CI_COMMIT_SHA:0:8}-arm.zip + update: 'true' + + # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable + upload-latest-tag-arm: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-stable-arm + package_version: latest + file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm.zip + file_name: pleroma.zip + update: 'true' + + upload-artifacts-arm64: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm64 + package_version: ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64 + file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64.zip + file_name: pleroma-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64.zip + update: 'true' + + upload-latest-arm64: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm64 + package_version: latest + file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64.zip + file_name: pleroma.zip + update: 'true' + + # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable + upload-artifacts-tag-arm64: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-stable-arm64 + package_version: stable-${CI_COMMIT_SHA:0:8}-arm64 + file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm64.zip + file_name: pleroma-stable-${CI_COMMIT_SHA:0:8}-arm64.zip + update: 'true' + + # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable + upload-latest-tag-arm64: + image: *artifacts_uploader_image + when: + - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"' + settings: + <<: *artifacts_uploader_settings + package_name: pleroma-otp-stable-arm64 + package_version: latest + file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm64.zip + file_name: pleroma.zip + update: 'true' diff --git a/.woodpecker/unit-testing-elixir-1.15.yaml b/.woodpecker/unit-testing-elixir-1.15.yaml new file mode 100644 index 000000000..5c6be764d --- /dev/null +++ b/.woodpecker/unit-testing-elixir-1.15.yaml @@ -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.15: + image: elixir:1.15-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 diff --git a/.woodpecker/unit-testing-elixir-1.18.yaml b/.woodpecker/unit-testing-elixir-1.18.yaml new file mode 100644 index 000000000..2a40f41f7 --- /dev/null +++ b/.woodpecker/unit-testing-elixir-1.18.yaml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index adc76c767..c7bb9b09d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,54 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## 2.10.1 + +### Changed + +- Move avatar_description and header_description fields to the account object +- Update Bandit to 1.10.4 +- No-op code correctness improvements detected by Elixir 1.19 compiler +- Downgrade Hackney to 1.20.1 +- Use a custom redirect handler to ensure MediaProxy redirects are followed with Hackney +- Update Hackney, the default HTTP client, to the latest release which supports Happy Eyeballs for improved IPv6 federation +- Paginate follow requests +- Moved Phoenix LiveDashboard to /pleroma/live_dashboard +- Add mute/block expiry to the relationship object +- Filter indexable activities before inserting indexing jobs into the queue. + +### Added + +- Allow assigning users to reports +- Allow fine-grained announce visibilities +- Add immutable tag on cache-control header for several endpoints that's serving the same exact things. +- Add reasonable defaults for :database_config_whitelist +- Support lists `exclusive` param +- Add v1/instance/domain_blocks endpoint +- Add /api/v2/instance profile fields limits info used by Mastodon +- Added Oban Web dashboard located at /pleroma/oban +- Add instructions on how to run a release in docker, to make it easier to run on older distros. + +### Fixed + +- Fix the daily email digest job which was not executing +- Encode custom emoji URLs in EmojiReact activity tags. +- Gopher: Fix Ranch listener not being stopped properly on Pleroma restart when database configuration is enabled +- Fix fetching Hubzilla Actors with alsoKnownAs as string +- Fix /phoenix/live_dashboard redirect not working when user added a path segment +- Fix 404 error codes for missing static files +- Fix OAuth app registration to accept `redirect_uris` as an array of strings (RFC 7591), while keeping backwards compatibility with string input. +- Correct old migrations for expiring activities and user access tokens. +- Federate `votersCount` correctly +- DB prune: Check if user follows hashtag with no objects before deletion +- Stop the rate limiter from crashing when run with wrong settings. +- Restore embeds route +- ReverseProxy: Recursively follow redirects until redirect_limit is reached +- Fix compilation with vips-8.18.0 with bumping to vix 0.36.0 + +### Removed + +- Docs: Removed outdated, incorrect, unmaintained and inappropriate installation documentation (Arch, NetBSD, NixOS) + ## 2.10 ### Security diff --git a/README.md b/README.md index 2837b6ef8..982a7249a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + ## About @@ -19,8 +19,6 @@ If you are running Linux (glibc or musl) on x86/arm, the recommended way to inst If your platform is not supported, or you just want to be able to edit the source code easily, you may install Pleroma from source. - [Alpine Linux](https://docs-develop.pleroma.social/backend/installation/alpine_linux_en/) -- [Arch Linux](https://docs-develop.pleroma.social/backend/installation/arch_linux_en/) -- [CentOS 7](https://docs-develop.pleroma.social/backend/installation/centos7_en/) - [Debian-based](https://docs-develop.pleroma.social/backend/installation/debian_based_en/) - [Debian-based (jp)](https://docs-develop.pleroma.social/backend/installation/debian_based_jp/) - [FreeBSD](https://docs-develop.pleroma.social/backend/installation/freebsd_en/) diff --git a/changelog.d/ci-artifacts.skip b/changelog.d/ci-artifacts.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/config/config.exs b/config/config.exs index 683805fe3..5bf2c5c2e 100644 --- a/config/config.exs +++ b/config/config.exs @@ -960,6 +960,15 @@ config :pleroma, Pleroma.Search.QdrantSearch, vectors: %{size: 384, distance: "Cosine"} } +config :pleroma, :database_config_whitelist, [ + {:pleroma}, + {:cors_plug}, + {:ex_aws, :s3}, + {:mime}, + {:prometheus, Pleroma.Web.Endpoint.MetricsExporter}, + {:web_push_encryption, :vapid_details} +] + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/config/test.exs b/config/test.exs index 4da2b4f57..9652b7d4b 100644 --- a/config/test.exs +++ b/config/test.exs @@ -102,7 +102,6 @@ config :pleroma, :http, send_user_agent: false rum_enabled = System.get_env("RUM_ENABLED") == "true" config :pleroma, :database, rum_enabled: rum_enabled -IO.puts("RUM enabled: #{rum_enabled}") config :joken, default_signer: "yU8uHKq+yyAkZ11Hx//jcdacWc8yQ1bxAAGrplzB0Zwwjkp35v0RK9SO8WTPr6QZ" @@ -192,7 +191,7 @@ config :pleroma, Pleroma.Application, streamer_registry: false, test_http_pools: true -config :pleroma, Pleroma.Web.Streaming, sync_streaming: true +config :pleroma, Pleroma.Web.Streamer, sync_streaming: true config :pleroma, Pleroma.Uploaders.Uploader, timeout: 1_000 @@ -207,8 +206,9 @@ config :pleroma, Pleroma.User.Backup, tempdir: "test/tmp" if File.exists?("./config/test.secret.exs") do import_config "test.secret.exs" -else - IO.puts( - "You may want to create test.secret.exs to declare custom database connection parameters." - ) end + +# Avoid noisy shutdown logs from os_mon during tests. +config :os_mon, + start_cpu_sup: false, + start_memsup: false diff --git a/docs/administration/CLI_tasks/config.md b/docs/administration/CLI_tasks/config.md index 13d671a7e..35e02145e 100644 --- a/docs/administration/CLI_tasks/config.md +++ b/docs/administration/CLI_tasks/config.md @@ -169,4 +169,18 @@ This forcibly removes any enabled MRF that does not exist and will fix the abili === "From Source" ```sh mix pleroma.config fix_mrf_policies - ``` \ No newline at end of file + ``` + +## Remove non-whitelisted configs from the database + +This removes any configuration value that is not explicitly whitelisted by `:pleroma, :database_config_whitelist`. Might be useful after updating the whitelist. + +=== "OTP" + ```sh + ./bin/pleroma_ctl config filter_whitelisted + ``` + +=== "From Source" + ```sh + mix pleroma.config filter_whitelisted + ``` diff --git a/docs/administration/dashboards.md b/docs/administration/dashboards.md new file mode 100644 index 000000000..b95e0fac0 --- /dev/null +++ b/docs/administration/dashboards.md @@ -0,0 +1,47 @@ +# Dashboards + +Pleroma comes with two types of backend dashboards viewable to instance administrators: + +* [Phoenix LiveDashboard](https://hexdocs.pm/phoenix_live_dashboard/Phoenix.LiveDashboard.html) - A general system oriented dashboard for viewing statistics about Pleroma resource consumption, Pleroma's database and Pleroma's job processor (Oban). +* [Oban Web](https://hexdocs.pm/oban_web/overview.html) - A dashboard specific to Oban for viewing Oban statistics, managing jobs and job queues. + +!!! note + Both dashboards require working Websockets. + If your browser or web server don't support Websockets, both dashboards either won't update or will not display all information. + +## Phoenix LiveDashboard + +Instance administrators can access this dashboard at `/pleroma/live_dashboard`, giving a simple overview of software versions including Erlang and Elixir versions, instance uptime and resource consumption. + +This dashboard gives insights into the current state of the BEAM VM running Pleroma code and database statistics including basic diagnostics. +It can be useful for troubleshooting of some issues namely regarding database performance. + +### Relevant dashboard tabs + +* Home - A general overview of system information including software versions, uptime and memory BEAM memory consumption. +* OS Data - Information about the OS and system such as CPU load, memory usage and disk usage. +* Ecto Stats - Information about the Pleroma database. + - Diagnose - Basic database diagnostics, including a `bloat` warning when an index or a table have excessive bloat, which can lead to bad database performance. + - Bloat - A table showing size of "bloat" (unused wasted space) in database tables and indexes. Very high bloat size in the `activities` and `objects` tables can lead to bad performance especially on slower disks such as on most VPS providers. + - Db settings - A small list of PostgreSQL settings mostly relevant to database performance. + - Total table size - Shows sizes of all database tables including indexes sorted by size, useful for quickly checking overall database size. + - Long running queries - A list of of slow database queries and their duration. Multiple entries with duration in multiple seconds indicate a slowly performing database. +* Oban - Shows a list of all Oban jobs. + +!!! note + The DB bloat warning for `index 'oban_jobs::oban_jobs_args_index'` in Ecto Stats can be safely ignored. + +## Oban Web + +An advanced dashboard and management console viewable to instance administrators specifically for Oban, Pleroma's job processor. +It allows managing jobs, including force retrying failed jobs and job deletion. +It can be accessed at `/pleroma/oban`. + +!!! danger + This dashboard is very powerful! If you are unsure what a certain feature does, don't use it. + Changing individual queue state/settings in the "Queues" view is heavily discouraged. + +* Shows a real time chart of either a number of executed jobs, or job execution/wait time per a given time frame and the state/queue/worker. +* Shows a list of jobs in each state, their argument, number of attempts and execution/scheduled time. +* Selecting one or multiple jobs in the list allows performing actions like canceling/deleting and retrying. +* Clicking on a job shows a detailed view including the full argument, when it was inserted, information about its attempts, and performing actions on it. diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 54dd4a5f0..9efa1c8b3 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -1132,8 +1132,9 @@ Boolean, enables/disables in-database configuration. Read [Transferring the conf List of valid configuration sections which are allowed to be configured from the database. Settings stored in the database before the whitelist is configured are -still applied, so it is suggested to only use the whitelist on instances that -have not migrated the config to the database. +still applied. Consider running the `mix pleroma.config filter_whitelisted` task +after updating the whitelist. Read [Remove non-whitelisted configs from the database](../administration/CLI_tasks/config.md#remove-non-whitelisted-configs-from-the-database) +for more information. Example: ```elixir diff --git a/docs/development/API/admin_api.md b/docs/development/API/admin_api.md index 64c06ca2b..3719ceeb9 100644 --- a/docs/development/API/admin_api.md +++ b/docs/development/API/admin_api.md @@ -665,6 +665,7 @@ Status: 404 - *optional* `limit`: **integer** the number of records to retrieve - *optional* `page`: **integer** page number - *optional* `page_size`: **integer** number of log entries per page (default is `50`) + - *optional* `assigned_account`: **string** assigned account ID - Response: - On failure: 403 Forbidden error `{"error": "error_msg"}` when requested by anonymous or non-admin - On success: JSON, returns a list of reports, where: @@ -749,6 +750,7 @@ Status: 404 "url": "https://pleroma.example.org/users/lain", "username": "lain" }, + "assigned_account": null, "content": "Please delete it", "created_at": "2019-04-29T19:48:15.000Z", "id": "9iJGOv1j8hxuw19bcm", @@ -868,6 +870,37 @@ Status: 404 ] ``` +- Response: + - On failure: + - 400 Bad Request, JSON: + + ```json + [ + { + `id`, // report id + `error` // error message + } + ] + ``` + + - On success: `204`, empty response + +## `POST /api/v1/pleroma/admin/reports/assign_account` + +### Assign account to one or multiple reports + +- Params: + +```json + `reports`: [ + { + `id`, // required, report id + `nickname` // account nickname, use null to unassign account + }, + ... + ] +``` + - Response: - On failure: - 400 Bad Request, JSON: diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md index 052b2716b..194c98e3f 100644 --- a/docs/development/API/differences_in_mastoapi_responses.md +++ b/docs/development/API/differences_in_mastoapi_responses.md @@ -74,7 +74,7 @@ Pleroma does not process remote images and therefore cannot include fields such The `GET /api/v1/bookmarks` endpoint accepts optional parameter `folder_id` for bookmark folder ID. -The `POST /api/v1/statuses/:id/bookmark` endpoint accepts optional parameter `folder_id` for bookmark folder ID. +The `POST /api/v1/statuses/:id/bookmark` endpoint accepts optional parameter `folder_id` for bookmark folder ID. Bookmarking an already bookmarked post will update the folder association, or remove it if `folder_id` is omitted or `null`. ## Accounts @@ -127,8 +127,6 @@ Has these additional fields under the `pleroma` object: - `notification_settings`: object, can be absent. See `/api/v1/pleroma/notification_settings` for the parameters/keys returned. - `accepts_chat_messages`: boolean, but can be null if we don't have that information about a user - `favicon`: nullable URL string, Favicon image of the user's instance -- `avatar_description`: string, image description for user avatar, defaults to empty string -- `header_description`: string, image description for user banner, defaults to empty string ### Source diff --git a/docs/development/API/pleroma_api.md b/docs/development/API/pleroma_api.md index 7946ba1f6..b19523bce 100644 --- a/docs/development/API/pleroma_api.md +++ b/docs/development/API/pleroma_api.md @@ -690,6 +690,7 @@ Audio scrobbling in Pleroma is **deprecated**. * `album`: the album of the media playing [optional] * `artist`: the artist of the media playing [optional] * `length`: the length of the media playing [optional] + * `external_link`: a URL referencing the media playing [optional] * Response: the newly created media metadata entity representing the Listen activity # Emoji Reactions diff --git a/docs/installation/arch_linux_en.md b/docs/installation/arch_linux_en.md deleted file mode 100644 index f7d722ef9..000000000 --- a/docs/installation/arch_linux_en.md +++ /dev/null @@ -1,226 +0,0 @@ -# Installing on Arch Linux - -{! backend/installation/otp_vs_from_source_source.include !} - -## Installation - -This guide will assume that you have administrative rights, either as root or a user with [sudo permissions](https://wiki.archlinux.org/index.php/Sudo). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su -s $SHELL -c 'command'` instead. - -### Required packages - -* `postgresql` -* `elixir` -* `git` -* `base-devel` -* `cmake` -* `file` - -#### Optional packages used in this guide - -* `nginx` (preferred, example configs for other reverse proxies can be found in the repo) -* `certbot` (or any other ACME client for Let’s Encrypt certificates) -* `ImageMagick` -* `ffmpeg` -* `exiftool` - -### Prepare the system - -* First update the system, if not already done: - -```shell -sudo pacman -Syu -``` - -* Install some of the above mentioned programs: - -```shell -sudo pacman -S git base-devel elixir cmake file -``` - -### Install PostgreSQL - -[Arch Wiki article](https://wiki.archlinux.org/index.php/PostgreSQL) - -* Install the `postgresql` package: - -```shell -sudo pacman -S postgresql -``` - -* Initialize the database cluster: - -```shell -sudo -iu postgres initdb -D /var/lib/postgres/data -``` - -* Start and enable the `postgresql.service` - -```shell -sudo systemctl enable --now postgresql.service -``` - -### Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md)) - -```shell -sudo pacman -S ffmpeg imagemagick perl-image-exiftool -``` - -### Install PleromaBE - -* Add a new system user for the Pleroma service: - -```shell -sudo useradd -r -s /bin/false -m -d /var/lib/pleroma -U pleroma -``` - -**Note**: To execute a single command as the Pleroma system user, use `sudo -Hu pleroma command`. You can also switch to a shell by using `sudo -Hu pleroma $SHELL`. If you don’t have and want `sudo` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l pleroma -s $SHELL -c 'command'` and `su -l pleroma -s $SHELL` for starting a shell. - -* Git clone the PleromaBE repository and make the Pleroma user the owner of the directory: - -```shell -sudo mkdir -p /opt/pleroma -sudo chown -R pleroma:pleroma /opt/pleroma -sudo -Hu pleroma git clone -b stable https://git.pleroma.social/pleroma/pleroma /opt/pleroma -``` - -* Change to the new directory: - -```shell -cd /opt/pleroma -``` - -* Install the dependencies for Pleroma and answer with `yes` if it asks you to install `Hex`: - -```shell -sudo -Hu pleroma mix deps.get -``` - -* Generate the configuration: `sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen` - * Answer with `yes` if it asks you to install `rebar3`. - * This may take some time, because parts of pleroma get compiled first. - * After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`. - -* Check the configuration and if all looks right, rename it, so Pleroma will load it (`prod.secret.exs` for productive instance, `dev.secret.exs` for development instances): - -```shell -sudo -Hu pleroma mv config/{generated_config.exs,prod.secret.exs} -``` - -* The previous command creates also the file `config/setup_db.psql`, with which you can create the database: - -```shell -sudo -Hu postgres psql -f config/setup_db.psql -``` - -* Now run the database migration: - -```shell -sudo -Hu pleroma MIX_ENV=prod mix ecto.migrate -``` - -* Now you can start Pleroma already - -```shell -sudo -Hu pleroma MIX_ENV=prod mix phx.server -``` - -### Finalize installation - -If you want to open your newly installed instance to the world, you should run nginx or some other webserver/proxy in front of Pleroma and you should consider to create a systemd service file for Pleroma. - -#### Nginx - -* Install nginx, if not already done: - -```shell -sudo pacman -S nginx -``` - -* Create directories for available and enabled sites: - -```shell -sudo mkdir -p /etc/nginx/sites-{available,enabled} -``` - -* Append the following line at the end of the `http` block in `/etc/nginx/nginx.conf`: - -```Nginx -include sites-enabled/*; -``` - -* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it: - -```shell -sudo pacman -S certbot certbot-nginx -``` - -and then set it up: - -```shell -sudo mkdir -p /var/lib/letsencrypt/ -sudo certbot certonly --email -d --standalone -``` - -If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again). - ---- - -* Copy the example nginx configuration and activate it: - -```shell -sudo cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/pleroma.nginx -sudo ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled/pleroma.nginx -``` - -* Before starting nginx edit the configuration and change it to your needs (e.g. change servername, change cert paths) - -* (Strongly recommended) serve media on another domain - -Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors. - -* Enable and start nginx: - -```shell -sudo systemctl enable --now nginx.service -``` - -If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run: - -```shell -sudo certbot certonly --email -d --webroot -w /var/lib/letsencrypt/ -``` - -#### Other webserver/proxies - -You can find example configurations for them in `/opt/pleroma/installation/`. - -#### Systemd service - -* Copy example service file - -```shell -sudo cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service -``` - -* Edit the service file and make sure that all paths fit your installation -* Enable and start `pleroma.service`: - -```shell -sudo systemctl enable --now pleroma.service -``` - -#### Create your first user - -If your instance is up and running, you can create your first user with administrative rights with the following task: - -```shell -sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new --admin -``` - -#### Further reading - -{! backend/installation/further_reading.include !} - -## Questions - -Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC. diff --git a/docs/installation/netbsd_en.md b/docs/installation/netbsd_en.md deleted file mode 100644 index 7c003700c..000000000 --- a/docs/installation/netbsd_en.md +++ /dev/null @@ -1,294 +0,0 @@ -# Installing on NetBSD - -{! backend/installation/generic_dependencies.include !} - -# Installation options - -Currently there are two options available for NetBSD: manual installation (from source) or using experimental package from [pkgsrc-wip](https://github.com/NetBSD/pkgsrc-wip/tree/master/pleroma). - -WIP package can be installed via pkgsrc and can be crosscompiled for easier binary distribution. Source installation most probably will be restricted to a single machine. - -## pkgsrc installation - -WIP package creates Mix.Release (similar to how Docker images are built) but doesn't bundle Erlang runtime, listing it as a dependency instead. This allows for easier and more modular installations, especially on weaker machines. Currently this method also does not support all features of `pleroma_ctl` command (like changing installation type or managing frontends) as NetBSD is not yet a supported binary flavour of Pleroma's CI. - -In any case, you can install it the same way as any other `pkgsrc-wip` package: - -``` -cd /usr/pkgsrc -git clone --depth 1 git://wip.pkgsrc.org/pkgsrc-wip.git wip -cp -rf wip/pleroma www -cp -rf wip/libvips graphics -cd /usr/pkgsrc/www/pleroma -bmake && bmake install -``` - -Use `bmake package` to create a binary package. This can come especially handy if you're targeting embedded or low-power systems and are crosscompiling on a more powerful machine. - -> Note: Elixir has [endianness bug](https://github.com/elixir-lang/elixir/issues/2785) which requires it to be compiled on a machine with the same endianness. In other words, package crosscompiled on amd64 (little endian) won't work on powerpc or sparc machines (big endian). While _in theory™_ nothing catastrophic should happen, one can see that for example regexes won't work properly. Some distributions just strip this warning away, so it doesn't bother the users... anyway, you've been warned. - -## Source installation - -pkgin should have been installed by the NetBSD installer if you selected -the right options. If it isn't installed, install it using `pkg_add`. - -Note that `postgresql11-contrib` is needed for the Postgres extensions -Pleroma uses. - -> Note: you can use modern versions of PostgreSQL. In this case, just use `postgresql16-contrib` and so on. - -The `mksh` shell is needed to run the Elixir `mix` script. - -`# pkgin install acmesh elixir git-base git-docs mksh nginx postgresql11-server postgresql11-client postgresql11-contrib sudo ffmpeg4 ImageMagick` - -You can also build these packages using pkgsrc: -``` -databases/postgresql11-contrib -databases/postgresql11-client -databases/postgresql11-server -devel/git-base -devel/git-docs -devel/cmake -lang/elixir -security/acmesh -security/sudo -shells/mksh -www/nginx -``` - -Create a user for Pleroma: - -``` -# groupadd pleroma -# useradd -d /home/pleroma -m -g pleroma -s /usr/pkg/bin/mksh pleroma -# echo 'export LC_ALL="en_GB.UTF-8"' >> /home/pleroma/.profile -# su -l pleroma -c $SHELL -``` - -Clone the repository: - -``` -$ cd /home/pleroma -$ git clone -b stable https://git.pleroma.social/pleroma/pleroma.git -``` - -Get deps and compile: - -``` -$ cd /home/pleroma/pleroma -$ export MIX_ENV=prod -$ mix deps.get -$ mix compile -``` - -## Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md)) - -`# pkgin install ImageMagick ffmpeg4 p5-Image-ExifTool` - -or via pkgsrc: - -``` -graphics/p5-Image-ExifTool -graphics/ImageMagick -multimedia/ffmpeg4 -``` - -# Configuration - -## Understanding $PREFIX - -From now on, you may encounter `$PREFIX` variable in the paths. This variable indicates your current local pkgsrc prefix. Usually it's `/usr/pkg` unless you configured it otherwise. Translating to pkgsrc's lingo, it's called `LOCALBASE`, which essentially means the same this. You may want to set it up for your local shell session (this uses `mksh` which should already be installed as one of the required dependencies): - -``` -$ export PREFIX=$(pkg_info -Q LOCALBASE mksh) -$ echo $PREFIX -/usr/pkg -``` - -## Setting up your instance - -Now, you need to configure your instance. During this initial configuration, you will be asked some questions about your server. You will need a domain name at this point; it doesn't have to be deployed, but changing it later will be very cumbersome. - -If you've installed via pkgsrc, `pleroma_ctl` should already be in your `PATH`; if you've installed from source, it's located at `/home/pleroma/pleroma/release/bin/pleroma_ctl`. - -``` -$ su -l pleroma -$ pleroma_ctl instance gen --output $PREFIX/etc/pleroma/config.exs --output-psql /tmp/setup_db.psql -``` - -During installation, you will be asked about static and upload directories. Don't forget to create them and update permissions: - -``` -mkdir -p /var/lib/pleroma/uploads -chown -R pleroma:pleroma /var/lib/pleroma -``` - -## Setting up the database - -First, run `# /etc/rc.d/pgsql start`. Then, `$ sudo -Hu pgsql -g pgsql createdb`. - -We can now initialize the database. You'll need to edit generated SQL file from the previous step. It's located at `/tmp/setup_db.psql`. - -Edit this file, and *change the password* to a password of your choice. Make sure it is secure, since -it'll be protecting your database. Now initialize the database: - -``` -$ sudo -Hu pgsql -g pgsql psql -f /tmp/setup_db.psql -``` - -Postgres allows connections from all users without a password by default. To -fix this, edit `$PREFIX/pgsql/data/pg_hba.conf`. Change every `trust` to -`password`. - -Once this is done, restart Postgres with `# /etc/rc.d/pgsql restart`. - -Run the database migrations. - -### pkgsrc installation - -``` -pleroma_ctl migrate -``` - -### Source installation - -You will need to do this whenever you update with `git pull`: - -``` -$ cd /home/pleroma/pleroma -$ MIX_ENV=prod mix ecto.migrate -``` - -## Configuring nginx - -Install the example configuration file -(`$PREFIX/share/examples/pleroma/pleroma.nginx` or `/home/pleroma/pleroma/installation/pleroma.nginx`) to -`$PREFIX/etc/nginx.conf`. - -Note that it will need to be wrapped in a `http {}` block. You should add -settings for the nginx daemon outside of the http block, for example: - -``` -user nginx nginx; -error_log /var/log/nginx/error.log; -worker_processes 4; - -events { -} -``` - -Edit the defaults: - -* Change `ssl_certificate` and `ssl_trusted_certificate` to -`/etc/nginx/tls/fullchain`. -* Change `ssl_certificate_key` to `/etc/nginx/tls/key`. -* Change `example.tld` to your instance's domain name. - -### (Strongly recommended) serve media on another domain - -Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors. - -## Configuring acme.sh - -We'll be using acme.sh in Stateless Mode for TLS certificate renewal. - -First, get your account fingerprint: - -``` -$ sudo -Hu nginx -g nginx acme.sh --register-account -``` - -You need to add the following to your nginx configuration for the server -running on port 80: - -``` - location ~ ^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)$ { - default_type text/plain; - return 200 "$1.6fXAG9VyG0IahirPEU2ZerUtItW2DHzDzD9wZaEKpqd"; - } -``` - -Replace the string after after `$1.` with your fingerprint. - -Start nginx: - -``` -# /etc/rc.d/nginx start -``` - -It should now be possible to issue a cert (replace `example.com` -with your domain name): - -``` -$ sudo -Hu nginx -g nginx acme.sh --issue -d example.com --stateless -``` - -Let's add auto-renewal to `/etc/daily.local` -(replace `example.com` with your domain): - -``` -/usr/pkg/bin/sudo -Hu nginx -g nginx \ - /usr/pkg/sbin/acme.sh -r \ - -d example.com \ - --cert-file /etc/nginx/tls/cert \ - --key-file /etc/nginx/tls/key \ - --ca-file /etc/nginx/tls/ca \ - --fullchain-file /etc/nginx/tls/fullchain \ - --stateless -``` - -## Autostart - -For properly functioning instance, you will need pleroma (backend service), nginx (reverse proxy) and postgresql (database) services running. There's no requirement for them to reside on the same machine, but you have to provide autostart for each of them. - -### nginx -``` -# cp $PREFIX/share/examples/rc.d/nginx /etc/rc.d -# echo "nginx=YES" >> /etc/rc.conf -``` - -### postgresql - -``` -# cp $PREFIX/share/examples/rc.d/pgsql /etc/rc.d -# echo "pgsql=YES" >> /etc/rc.conf -``` - -### pleroma - -First, copy the script (pkgsrc variant) -``` -# cp $PREFIX/share/examples/pleroma/pleroma.rc /etc/rc.d/pleroma -``` - -or source variant -``` -# cp /home/pleroma/pleroma/installation/netbsd/rc.d/pleroma /etc/rc.d/pleroma -# chmod +x /etc/rc.d/pleroma -``` - -Then, add the following to `/etc/rc.conf`: - -``` -pleroma=YES -``` - -## Conclusion - -Run `# /etc/rc.d/pleroma start` to start Pleroma. -Restart nginx with `# /etc/rc.d/nginx restart` and you should be up and running. - -Make sure your time is in sync, or other instances will receive your posts with -incorrect timestamps. You should have ntpd running. - -## Instances running NetBSD - -* - -#### Further reading - -{! backend/installation/further_reading.include !} - -## Questions - -Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC. diff --git a/docs/installation/nixos_en.md b/docs/installation/nixos_en.md deleted file mode 100644 index f3c4988b1..000000000 --- a/docs/installation/nixos_en.md +++ /dev/null @@ -1,15 +0,0 @@ -# Installing on NixOS - -NixOS contains a source build package of pleroma and a NixOS module to install it. -For installation add this to your configuration.nix and add a config.exs next to it: -```nix - services.pleroma = { - enable = true; - configs = [ (lib.fileContents ./config.exs) ]; - secretConfigFile = "/var/lib/pleroma/secret.exs"; - }; -``` - -## Questions -The nix community uses matrix for communication: [#nix:nixos.org](https://matrix.to/#/#nix:nixos.org) - diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md index 86efa27f8..123dccf3a 100644 --- a/docs/installation/otp_en.md +++ b/docs/installation/otp_en.md @@ -13,6 +13,9 @@ You will be running commands as root. If you aren't root already, please elevate Similarly to other binaries, OTP releases tend to be only compatible with the distro they are built on, as such this guide focuses only on Debian/Ubuntu and Alpine. +!!! note + If you get `GLIBC_... not found` errors on Debian/Ubuntu, you can run the OTP release from `/opt/pleroma` inside a newer distro container without upgrading the host. See [`release_to_docker_en.md`](release_to_docker_en.md). + ### Detecting flavour Paste the following into the shell: diff --git a/docs/installation/release_to_docker_en.md b/docs/installation/release_to_docker_en.md new file mode 100644 index 000000000..a1fd165ac --- /dev/null +++ b/docs/installation/release_to_docker_en.md @@ -0,0 +1,61 @@ +# Running OTP releases via Docker (glibc shim) + +Pleroma OTP releases are built on specific distros. If your host OS is older than +the build environment, you may hit runtime linker errors such as: + +``` +/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found +``` + +If you don't want to upgrade your host OS, you can run the existing OTP release +from `/opt/pleroma` inside an Ubuntu 24.04 container while keeping your existing +host config and data directories. + +This approach uses a small "shim" container image to provide a newer `glibc`. +It is **not** the official Pleroma Docker image. + +## Requirements + +- Docker Engine + the Docker Compose plugin on the host +- Root access (or equivalent access to the Docker socket) +- Existing OTP release in `/opt/pleroma` +- Existing config in `/etc/pleroma` and data in `/var/lib/pleroma` + +## Setup + +1. Copy the provided templates: + + ```sh + mkdir -p /etc/pleroma/container + cp -a /opt/pleroma/installation/release-to-docker/* /etc/pleroma/container/ + ``` + +2. Build the shim image: + + ```sh + cd /etc/pleroma/container + docker compose build + ``` + +3. Replace your systemd unit: + + ```sh + cp /etc/pleroma/container/pleroma.service /etc/systemd/system/pleroma.service + systemctl daemon-reload + systemctl enable --now pleroma + journalctl -u pleroma -f + ``` + +## Running migrations / `pleroma_ctl` + +Migrations are run automatically by default when the container starts. You can +disable this by setting `PLEROMA_RUN_MIGRATIONS=0` in +`/etc/pleroma/container/docker-compose.yml`. + +To run admin commands inside the container: + +```sh +cd /etc/pleroma/container +docker compose exec pleroma /opt/pleroma/bin/pleroma_ctl status +docker compose run --rm --no-deps pleroma /opt/pleroma/bin/pleroma_ctl migrate +``` diff --git a/installation/release-to-docker/Dockerfile b/installation/release-to-docker/Dockerfile new file mode 100644 index 000000000..0ffeb5d5b --- /dev/null +++ b/installation/release-to-docker/Dockerfile @@ -0,0 +1,26 @@ +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + gosu \ + libstdc++6 \ + libncurses6 libncursesw6 \ + openssl libssl3 \ + libmagic1t64 file \ + postgresql-client \ + ffmpeg imagemagick libimage-exiftool-perl \ + libvips42t64 \ + unzip \ + curl \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /opt/pleroma + +COPY pleroma-host-release-entrypoint.sh /usr/local/bin/pleroma-host-release-entrypoint.sh +RUN chmod +x /usr/local/bin/pleroma-host-release-entrypoint.sh +ENTRYPOINT ["/usr/local/bin/pleroma-host-release-entrypoint.sh"] +CMD ["/opt/pleroma/bin/pleroma", "start"] diff --git a/installation/release-to-docker/README.md b/installation/release-to-docker/README.md new file mode 100644 index 000000000..4129bd94b --- /dev/null +++ b/installation/release-to-docker/README.md @@ -0,0 +1,66 @@ +# Run OTP releases on older glibc using Docker + +Pleroma OTP releases are built on specific distros and may require a newer `glibc` +than your host has. A typical failure looks like: + +``` +... /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found ... +``` + +If you don't want to upgrade your host OS, you can run the existing OTP release +from `/opt/pleroma` inside an Ubuntu 24.04 container while keeping your existing +host paths (`/etc/pleroma`, `/var/lib/pleroma`, etc.). + +This folder provides a "shim" container + systemd unit. It is **not** the Pleroma +Docker image. + +## What this does + +- Builds a small Ubuntu 24.04 image with runtime libs (including newer `glibc`). +- Mounts your existing host release at `/opt/pleroma` into the container. +- Runs as the same UID/GID that owns `/opt/pleroma` on the host (via `gosu`). +- Optionally runs migrations automatically on container start. +- Uses `network_mode: host` so your existing config that talks to `localhost` + keeps working. + +## Setup (Debian/Ubuntu host) + +1. Install Docker Engine + the Docker Compose plugin. +2. Copy these files to a stable location (example: `/etc/pleroma/container`): + + ``` + mkdir -p /etc/pleroma/container + cp -a /opt/pleroma/installation/release-to-docker/* /etc/pleroma/container/ + ``` + +3. Build the shim image: + + ``` + cd /etc/pleroma/container + docker compose build + ``` + +4. Replace your systemd unit: + + ``` + cp /etc/pleroma/container/pleroma.service /etc/systemd/system/pleroma.service + systemctl daemon-reload + systemctl enable --now pleroma + journalctl -u pleroma -f + ``` + +## Running `pleroma_ctl` + +Since the host binary may not run on older `glibc`, run admin commands inside the +container: + +``` +cd /etc/pleroma/container +docker compose exec pleroma /opt/pleroma/bin/pleroma_ctl status +docker compose run --rm --no-deps pleroma /opt/pleroma/bin/pleroma_ctl migrate +``` + +## Configuration notes + +- Migrations run automatically by default. + - Set `PLEROMA_RUN_MIGRATIONS=0` in `docker-compose.yml` to disable. diff --git a/installation/release-to-docker/docker-compose.yml b/installation/release-to-docker/docker-compose.yml new file mode 100644 index 000000000..043803241 --- /dev/null +++ b/installation/release-to-docker/docker-compose.yml @@ -0,0 +1,22 @@ +services: + pleroma: + build: + context: . + dockerfile: Dockerfile + image: pleroma-host-release-wrapper:ubuntu24 + init: true + network_mode: host + restart: unless-stopped + environment: + HOME: /opt/pleroma + LANG: C.UTF-8 + LC_ALL: C.UTF-8 + ELIXIR_ERL_OPTIONS: "+fnu" + # Set to 0 to skip running migrations on container start. + PLEROMA_RUN_MIGRATIONS: "1" + volumes: + # Existing OTP release installation (host) + - /opt/pleroma:/opt/pleroma:rw + # Existing config + uploads + static (host) + - /etc/pleroma:/etc/pleroma:rw + - /var/lib/pleroma:/var/lib/pleroma:rw diff --git a/installation/release-to-docker/pleroma-host-release-entrypoint.sh b/installation/release-to-docker/pleroma-host-release-entrypoint.sh new file mode 100644 index 000000000..b9c035678 --- /dev/null +++ b/installation/release-to-docker/pleroma-host-release-entrypoint.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +uid="${PLEROMA_UID:-}" +gid="${PLEROMA_GID:-}" + +if [[ -z "${uid}" || -z "${gid}" ]]; then + uid="$(stat -c '%u' /opt/pleroma)" + gid="$(stat -c '%g' /opt/pleroma)" +fi + +export HOME="${HOME:-/opt/pleroma}" + +if [[ "${PLEROMA_RUN_MIGRATIONS:-1}" != "0" && "${1:-}" == "/opt/pleroma/bin/pleroma" && "${2:-}" == "start" ]]; then + echo "Running migrations..." + gosu "${uid}:${gid}" /opt/pleroma/bin/pleroma_ctl migrate +fi + +exec gosu "${uid}:${gid}" "$@" diff --git a/installation/release-to-docker/pleroma.service b/installation/release-to-docker/pleroma.service new file mode 100644 index 000000000..d3833363c --- /dev/null +++ b/installation/release-to-docker/pleroma.service @@ -0,0 +1,16 @@ +[Unit] +Description=Pleroma social network (OTP release via Docker glibc shim) +After=network.target docker.service postgresql.service nginx.service +Requires=docker.service + +[Service] +Type=simple +WorkingDirectory=/etc/pleroma/container +ExecStart=/usr/bin/docker compose up --build --remove-orphans +ExecStop=/usr/bin/docker compose down +Restart=on-failure +RestartSec=5s +TimeoutStartSec=0 + +[Install] +WantedBy=multi-user.target diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 8b3b2f18b..c5f2e9b0a 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -234,6 +234,61 @@ defmodule Mix.Tasks.Pleroma.Config do end) end + # Removes non-whitelisted configuration sections + def run(["filter_whitelisted" | rest]) do + {options, [], []} = + OptionParser.parse( + rest, + strict: [force: :boolean], + aliases: [f: :force] + ) + + force = Keyword.get(options, :force, false) + + start_pleroma() + + whitelisted_configs = Pleroma.Config.get(:database_config_whitelist) + + if whitelisted_configs in [nil, false] do + shell_error("No unwanted settings in ConfigDB. No changes made.") + else + whitelisted_groups = + whitelisted_configs + |> Enum.filter(fn + {_group} -> true + _ -> false + end) + |> Enum.map(fn {group} -> group end) + + whitelisted_keys = + whitelisted_configs + |> Enum.filter(fn + {_group, _key} -> true + _ -> false + end) + + filtered = + from(c in ConfigDB) + |> Repo.all() + |> Enum.filter(¬_whitelisted?(&1, whitelisted_groups, whitelisted_keys)) + + if not Enum.empty?(filtered) do + shell_info("The following settings will be removed from ConfigDB:\n") + Enum.each(filtered, &dump(&1)) + + if force or shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do + filtered_ids = Enum.map(filtered, fn %{id: id} -> id end) + + Repo.delete_all(from(c in ConfigDB, where: c.id in ^filtered_ids)) + else + shell_error("No changes made.") + end + else + shell_error("No unwanted settings in ConfigDB. No changes made.") + end + end + end + @spec migrate_to_db(Path.t() | nil) :: any() def migrate_to_db(file_path \\ nil) do with :ok <- Pleroma.Config.DeprecationWarnings.warn() do @@ -330,7 +385,13 @@ defmodule Mix.Tasks.Pleroma.Config do |> Enum.each(&write_and_delete(&1, file, opts[:delete])) :ok = File.close(file) - System.cmd("mix", ["format", path]) + + # Ensure `mix format` runs in the same env as the current task and doesn't + # emit config-time stderr noise (e.g. dev secret warnings) into `mix test`. + System.cmd("mix", ["format", path], + env: [{"MIX_ENV", to_string(Mix.env())}], + stderr_to_stdout: true + ) end defp config_header, do: "import Config\r\n\r\n" @@ -428,4 +489,9 @@ defmodule Mix.Tasks.Pleroma.Config do Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;") Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;") end + + defp not_whitelisted?(%{group: group, key: key}, whitelisted_groups, whitelisted_keys) do + not Enum.member?(whitelisted_groups, group) and + not Enum.member?(whitelisted_keys, {group, key}) + end end diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index e52b5e0a7..396536827 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -226,7 +226,12 @@ defmodule Mix.Tasks.Pleroma.Database do DELETE FROM hashtags AS ht WHERE NOT EXISTS ( SELECT 1 FROM hashtags_objects hto - WHERE ht.id = hto.hashtag_id) + WHERE ht.id = hto.hashtag_id + ) + AND NOT EXISTS ( + SELECT 1 FROM user_follows_hashtag ufh + WHERE ht.id = ufh.hashtag_id + ) """ |> Repo.query() diff --git a/lib/mix/tasks/pleroma/openapi_spec.ex b/lib/mix/tasks/pleroma/openapi_spec.ex index 1ea468476..852e1e9af 100644 --- a/lib/mix/tasks/pleroma/openapi_spec.ex +++ b/lib/mix/tasks/pleroma/openapi_spec.ex @@ -22,7 +22,7 @@ defmodule Mix.Tasks.Pleroma.OpenapiSpec do else {_, errors} -> IO.puts(IO.ANSI.format([:red, :bright, "Spec check failed, errors:"])) - Enum.map(errors, &IO.puts/1) + Enum.each(errors, &IO.puts/1) raise "Spec check failed" end diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index edce9e871..facc38815 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -72,7 +72,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do query, timeout: :infinity ) - |> Stream.map(&Pleroma.Search.Meilisearch.object_to_search_data/1) + |> Stream.map(&Pleroma.Search.object_to_search_data/1) |> Stream.filter(fn o -> not is_nil(o) end) |> Stream.chunk_every(chunk_size) |> Stream.transform(0, fn objects, acc -> diff --git a/lib/pleroma/activity/html.ex b/lib/pleroma/activity/html.ex index ba284b4d5..c83889c87 100644 --- a/lib/pleroma/activity/html.ex +++ b/lib/pleroma/activity/html.ex @@ -38,7 +38,7 @@ defmodule Pleroma.Activity.HTML do def invalidate_cache_for(activity_id) do keys = get_cache_keys_for(activity_id) - Enum.map(keys, &@cachex.del(:scrubber_cache, &1)) + Enum.each(keys, &@cachex.del(:scrubber_cache, &1)) @cachex.del(:scrubber_management_cache, activity_id) end diff --git a/lib/pleroma/bookmark.ex b/lib/pleroma/bookmark.ex index 1a2a63b82..1c78d495f 100644 --- a/lib/pleroma/bookmark.ex +++ b/lib/pleroma/bookmark.ex @@ -19,7 +19,7 @@ defmodule Pleroma.Bookmark do schema "bookmarks" do belongs_to(:user, User, type: FlakeId.Ecto.CompatType) belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType) - belongs_to(:folder, BookmarkFolder, type: FlakeId.Ecto.CompatType) + belongs_to(:folder, BookmarkFolder, type: FlakeId.Ecto.Type) timestamps() end @@ -38,7 +38,7 @@ defmodule Pleroma.Bookmark do |> validate_required([:user_id, :activity_id]) |> unique_constraint(:activity_id, name: :bookmarks_user_id_activity_id_index) |> Repo.insert( - on_conflict: [set: [folder_id: folder_id]], + on_conflict: [set: [folder_id: folder_id, updated_at: NaiveDateTime.utc_now()]], conflict_target: [:user_id, :activity_id] ) end @@ -76,11 +76,4 @@ defmodule Pleroma.Bookmark do |> Repo.one() |> Repo.delete() end - - def set_folder(bookmark, folder_id) do - bookmark - |> cast(%{folder_id: folder_id}, [:folder_id]) - |> validate_required([:folder_id]) - |> Repo.update() - end end diff --git a/lib/pleroma/bookmark_folder.ex b/lib/pleroma/bookmark_folder.ex index 14d37e197..65856bb29 100644 --- a/lib/pleroma/bookmark_folder.ex +++ b/lib/pleroma/bookmark_folder.ex @@ -14,7 +14,7 @@ defmodule Pleroma.BookmarkFolder do alias Pleroma.User @type t :: %__MODULE__{} - @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true} + @primary_key {:id, FlakeId.Ecto.Type, autogenerate: true} schema "bookmark_folders" do field(:name, :string) diff --git a/lib/pleroma/config_db.ex b/lib/pleroma/config_db.ex index e9990fa35..2c3df773b 100644 --- a/lib/pleroma/config_db.ex +++ b/lib/pleroma/config_db.ex @@ -10,6 +10,7 @@ defmodule Pleroma.ConfigDB do import Pleroma.Web.Gettext alias __MODULE__ + alias Pleroma.EctoType.Config.RateLimit alias Pleroma.Repo @type t :: %__MODULE__{} @@ -60,8 +61,59 @@ defmodule Pleroma.ConfigDB do |> cast(params, [:key, :group, :value]) |> validate_required([:key, :group, :value]) |> unique_constraint(:key, name: :config_group_key_index) + |> validate_rate_limit() end + defp validate_rate_limit(changeset) do + group = get_field(changeset, :group) + key = get_field(changeset, :key) + + if group == :pleroma and key == :rate_limit do + value = get_field(changeset, :value) + + case normalize_rate_limit(value) do + {:ok, normalized_value} -> + put_change(changeset, :value, normalized_value) + + {:error, {limiter_name, reason}} -> + add_error( + changeset, + :value, + "invalid :rate_limit value for #{inspect(limiter_name)}: #{reason}" + ) + end + else + changeset + end + end + + defp normalize_rate_limit(nil), do: {:ok, nil} + + defp normalize_rate_limit(%{} = value), do: normalize_rate_limit(Map.to_list(value)) + + defp normalize_rate_limit(value) when is_list(value) do + if Keyword.keyword?(value) do + value + |> Enum.reduce_while({:ok, []}, fn {limiter_name, limiter_value}, {:ok, acc} -> + case RateLimit.cast_with_error(limiter_value) do + {:ok, normalized_limiter_value} -> + {:cont, {:ok, [{limiter_name, normalized_limiter_value} | acc]}} + + {:error, reason} -> + {:halt, {:error, {limiter_name, reason}}} + end + end) + |> case do + {:ok, acc} -> {:ok, Enum.reverse(acc)} + {:error, _} = error -> error + end + else + {:error, {:rate_limit, "must be a keyword list"}} + end + end + + defp normalize_rate_limit(_), do: {:error, {:rate_limit, "must be a keyword list"}} + defp create(params) do %ConfigDB{} |> changeset(params) diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index c0411edbf..178dd6094 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -22,13 +22,14 @@ defmodule Pleroma.Constants do "generator", "rules", "language", - "voters" + "voters", + "assigned_account" ] ) const(static_only_files, do: - ~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css) + ~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js schemas doc embed.js embed.css) ) const(status_updatable_fields, diff --git a/lib/pleroma/ecto_type/config/rate_limit.ex b/lib/pleroma/ecto_type/config/rate_limit.ex new file mode 100644 index 000000000..0518ffd7e --- /dev/null +++ b/lib/pleroma/ecto_type/config/rate_limit.ex @@ -0,0 +1,71 @@ +# Pleroma: A lightweight social networking server +# Copyright © Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.EctoType.Config.RateLimit do + @moduledoc false + + use Ecto.Type + + @type t :: + nil + | {non_neg_integer(), non_neg_integer()} + | [{non_neg_integer(), non_neg_integer()}] + + @impl true + def type, do: :term + + @impl true + def cast(value) do + case cast_with_error(value) do + {:ok, normalized} -> {:ok, normalized} + {:error, _reason} -> :error + end + end + + @impl true + def load(value), do: cast(value) + + @impl true + def dump(value), do: cast(value) + + @spec cast_with_error(term()) :: {:ok, t()} | {:error, String.t()} + def cast_with_error(nil), do: {:ok, nil} + + def cast_with_error({scale, limit}) do + with {:ok, scale} <- parse_integer(scale, "scale"), + {:ok, limit} <- parse_integer(limit, "limit"), + true <- scale >= 1 and limit >= 1 do + {:ok, {scale, limit}} + else + false -> {:error, "scale and limit must be >= 1"} + {:error, reason} -> {:error, reason} + end + end + + def cast_with_error([{_, _} = unauth, {_, _} = auth]) do + with {:ok, unauth} <- cast_with_error(unauth), + {:ok, auth} <- cast_with_error(auth) do + {:ok, [unauth, auth]} + else + {:error, reason} -> {:error, reason} + end + end + + def cast_with_error(_), + do: + {:error, "must be a {scale, limit} tuple, a [{scale, limit}, {scale, limit}] list, or nil"} + + defp parse_integer(value, _label) when is_integer(value), do: {:ok, value} + + defp parse_integer(value, label) when is_binary(value) do + value = String.trim(value) + + case Integer.parse(value) do + {number, ""} -> {:ok, number} + _ -> {:error, "#{label} must be an integer"} + end + end + + defp parse_integer(_value, label), do: {:error, "#{label} must be an integer"} +end diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 21bcb0111..2fe425955 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -11,6 +11,8 @@ defmodule Pleroma.Emoji do alias Pleroma.Emoji.Combinations alias Pleroma.Emoji.Loader + alias Pleroma.Utils.URIEncoding + alias Pleroma.Web.Endpoint require Logger @@ -189,6 +191,34 @@ defmodule Pleroma.Emoji do def emoji_url(_), do: nil + @spec local_url(String.t() | nil) :: String.t() | nil + def local_url(nil), do: nil + + def local_url("http" <> _ = url) do + URIEncoding.encode_url(url) + end + + def local_url("/" <> _ = path) do + path = URIEncoding.encode_url(path, bypass_parse: true, bypass_decode: true) + Endpoint.url() <> path + end + + def local_url(path) when is_binary(path) do + local_url("/" <> path) + end + + def build_emoji_tag({name, url}) do + url = URIEncoding.encode_url(url) + + %{ + "icon" => %{"url" => "#{url}", "type" => "Image"}, + "name" => ":" <> name <> ":", + "type" => "Emoji", + "updated" => "1970-01-01T00:00:00Z", + "id" => url + } + end + def emoji_name_with_instance(name, url) do url = url |> URI.parse() |> Map.get(:host) "#{name}@#{url}" diff --git a/lib/pleroma/emoji/formatter.ex b/lib/pleroma/emoji/formatter.ex index 87fd35f13..9238bf912 100644 --- a/lib/pleroma/emoji/formatter.ex +++ b/lib/pleroma/emoji/formatter.ex @@ -5,7 +5,6 @@ defmodule Pleroma.Emoji.Formatter do alias Pleroma.Emoji alias Pleroma.HTML - alias Pleroma.Web.Endpoint alias Pleroma.Web.MediaProxy def emojify(text) do @@ -44,7 +43,7 @@ defmodule Pleroma.Emoji.Formatter do Emoji.get_all() |> Enum.filter(fn {emoji, %Emoji{}} -> String.contains?(text, ":#{emoji}:") end) |> Enum.reduce(%{}, fn {name, %Emoji{file: file}}, acc -> - Map.put(acc, name, to_string(URI.merge(Endpoint.url(), file))) + Map.put(acc, name, Emoji.local_url(file)) end) end diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index 653feb32f..e27f78d33 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -147,24 +147,22 @@ defmodule Pleroma.FollowingRelationship do |> Repo.aggregate(:count, :id) end - def get_follow_requests(%User{id: id}) do + def get_follow_requests_query(%User{id: id}) do __MODULE__ - |> join(:inner, [r], f in assoc(r, :follower)) + |> join(:inner, [r], f in assoc(r, :follower), as: :follower) |> where([r], r.state == ^:follow_pending) |> where([r], r.following_id == ^id) - |> where([r, f], f.is_active == true) - |> select([r, f], f) - |> Repo.all() + |> where([r, follower: f], f.is_active == true) + |> select([r, follower: f], f) end - def get_outgoing_follow_requests(%User{id: id}) do + def get_outgoing_follow_requests_query(%User{id: id}) do __MODULE__ - |> join(:inner, [r], f in assoc(r, :following)) + |> join(:inner, [r], f in assoc(r, :following), as: :following) |> where([r], r.state == ^:follow_pending) |> where([r], r.follower_id == ^id) - |> where([r, f], f.is_active == true) - |> select([r, f], f) - |> Repo.all() + |> where([r, following: f], f.is_active == true) + |> select([r, following: f], f) end def following?(%User{id: follower_id}, %User{id: followed_id}) do diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex index add3ba925..7b6985efe 100644 --- a/lib/pleroma/gopher/server.ex +++ b/lib/pleroma/gopher/server.ex @@ -21,10 +21,13 @@ defmodule Pleroma.Gopher.Server do def init([ip, port]) do Logger.info("Starting gopher server on #{port}") + Process.flag(:trap_exit, true) + + listener = :gopher {:ok, _pid} = :ranch.start_listener( - :gopher, + listener, :ranch_tcp, %{ num_acceptors: 100, @@ -35,7 +38,11 @@ defmodule Pleroma.Gopher.Server do [] ) - {:ok, %{ip: ip, port: port}} + {:ok, %{ip: ip, port: port, listener: listener}} + end + + def terminate(_reason, state) do + :ranch.stop_listener(state.listener) end end diff --git a/lib/pleroma/http/adapter_helper/hackney.ex b/lib/pleroma/http/adapter_helper/hackney.ex index f3451cf9c..2b1502001 100644 --- a/lib/pleroma/http/adapter_helper/hackney.ex +++ b/lib/pleroma/http/adapter_helper/hackney.ex @@ -6,8 +6,9 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do @behaviour Pleroma.HTTP.AdapterHelper @defaults [ - follow_redirect: true, - force_redirect: true + follow_redirect: false, + force_redirect: false, + with_body: true ] @spec options(keyword(), URI.t()) :: keyword() diff --git a/lib/pleroma/list.ex b/lib/pleroma/list.ex index b446b91a0..cd23bd0a9 100644 --- a/lib/pleroma/list.ex +++ b/lib/pleroma/list.ex @@ -17,13 +17,14 @@ defmodule Pleroma.List do field(:title, :string) field(:following, {:array, :string}, default: []) field(:ap_id, :string) + field(:exclusive, :boolean, default: false) timestamps() end - def title_changeset(list, attrs \\ %{}) do + def update_changeset(list, attrs \\ %{}) do list - |> cast(attrs, [:title]) + |> cast(attrs, [:title, :exclusive]) |> validate_required([:title]) end @@ -91,14 +92,14 @@ defmodule Pleroma.List do |> Repo.all() end - def rename(%Pleroma.List{} = list, title) do + def update(%Pleroma.List{} = list, params) do list - |> title_changeset(%{title: title}) + |> update_changeset(params) |> Repo.update() end - def create(title, %User{} = creator) do - changeset = title_changeset(%Pleroma.List{user_id: creator.id}, %{title: title}) + def create(params, %User{} = creator) do + changeset = update_changeset(%Pleroma.List{user_id: creator.id}, params) if changeset.valid? do Repo.transaction(fn -> @@ -149,4 +150,14 @@ defmodule Pleroma.List do end def member?(_, _), do: false + + def get_exclusive_list_members(%User{id: user_id}) do + Pleroma.List + |> where([l], l.user_id == ^user_id) + |> where([l], l.exclusive == true) + |> select([l], l.following) + |> Repo.all() + |> List.flatten() + |> Enum.uniq() + end end diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex index 68b054e4d..fab24d183 100644 --- a/lib/pleroma/marker.ex +++ b/lib/pleroma/marker.ex @@ -78,7 +78,7 @@ defmodule Pleroma.Marker do defp get_marker(user, timeline) do case Repo.find_resource(get_query(user, timeline)) do - {:ok, marker} -> %__MODULE__{marker | user: user} + {:ok, %__MODULE__{} = marker} -> %{marker | user: user} _ -> %__MODULE__{timeline: timeline, user_id: user.id} end end diff --git a/lib/pleroma/mfa/changeset.ex b/lib/pleroma/mfa/changeset.ex index 3ec3cfe91..2045c3a7c 100644 --- a/lib/pleroma/mfa/changeset.ex +++ b/lib/pleroma/mfa/changeset.ex @@ -8,7 +8,8 @@ defmodule Pleroma.MFA.Changeset do alias Pleroma.User def disable(%Ecto.Changeset{} = changeset, force \\ false) do - settings = + %Settings{} = + settings = changeset |> Ecto.Changeset.apply_changes() |> MFA.fetch_settings() @@ -20,20 +21,20 @@ defmodule Pleroma.MFA.Changeset do end end - def disable_totp(%User{multi_factor_authentication_settings: settings} = user) do + def disable_totp(%User{multi_factor_authentication_settings: %Settings{} = settings} = user) do user |> put_change(%Settings{settings | totp: %Settings.TOTP{}}) end - def confirm_totp(%User{multi_factor_authentication_settings: settings} = user) do - totp_settings = %Settings.TOTP{settings.totp | confirmed: true} + def confirm_totp(%User{multi_factor_authentication_settings: %Settings{} = settings} = user) do + totp_settings = %Settings.TOTP{(%Settings.TOTP{} = settings.totp) | confirmed: true} user |> put_change(%Settings{settings | totp: totp_settings, enabled: true}) end def setup_totp(%User{} = user, attrs) do - mfa_settings = MFA.fetch_settings(user) + %Settings{} = mfa_settings = MFA.fetch_settings(user) totp_settings = %Settings.TOTP{} @@ -46,7 +47,7 @@ defmodule Pleroma.MFA.Changeset do def cast_backup_codes(%User{} = user, codes) do user |> put_change(%Settings{ - user.multi_factor_authentication_settings + (%Settings{} = user.multi_factor_authentication_settings) | backup_codes: codes }) end diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index 52a71bc2d..90219312c 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -132,11 +132,18 @@ defmodule Pleroma.ModerationLog do end def insert_log(%{actor: %User{}, action: action, subject: %Activity{} = subject} = attrs) - when action in ["report_note_delete", "report_update", "report_note"] do + when action in [ + "report_note_delete", + "report_update", + "report_note", + "report_unassigned", + "report_assigned" + ] do data = attrs |> prepare_log_data |> Pleroma.Maps.put_if_present("text", attrs[:text]) + |> Pleroma.Maps.put_if_present("assigned_account", attrs[:assigned_account]) |> Map.merge(%{"subject" => report_to_map(subject)}) insert_log_entry_with_message(%ModerationLog{data: data}) @@ -441,6 +448,35 @@ defmodule Pleroma.ModerationLog do " with '#{state}' state" end + def get_log_entry_message( + %ModerationLog{ + data: %{ + "actor" => %{"nickname" => actor_nickname}, + "action" => "report_assigned", + "subject" => %{"id" => subject_id, "type" => "report"}, + "assigned_account" => assigned_account + } + } = log + ) do + "@#{actor_nickname} assigned report ##{subject_id}" <> + subject_actor_nickname(log, " (on user ", ")") <> + " to user #{assigned_account}" + end + + def get_log_entry_message( + %ModerationLog{ + data: %{ + "actor" => %{"nickname" => actor_nickname}, + "action" => "report_unassigned", + "subject" => %{"id" => subject_id, "type" => "report"} + } + } = log + ) do + "@#{actor_nickname} unassigned report ##{subject_id}" <> + subject_actor_nickname(log, " (on user ", ")") <> + " from a user" + end + def get_log_entry_message( %ModerationLog{ data: %{ diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index d0cb16b79..f1e07a257 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -374,10 +374,18 @@ defmodule Pleroma.Object do voters = [actor | object.data["voters"] || []] |> Enum.uniq() + voters_count = + if Map.has_key?(object.data, "votersCount") do + object.data["votersCount"] + 1 + else + length(voters) + end + data = object.data |> Map.put(key, options) |> Map.put("voters", voters) + |> Map.put("votersCount", voters_count) object |> Object.change(%{data: data}) diff --git a/lib/pleroma/release_tasks.ex b/lib/pleroma/release_tasks.ex index af2d35c8f..49400940f 100644 --- a/lib/pleroma/release_tasks.ex +++ b/lib/pleroma/release_tasks.ex @@ -5,7 +5,10 @@ defmodule Pleroma.ReleaseTasks do @repo Pleroma.Repo - def run(args) do + # TODO: Kept for some backwards compatibility with buggy pleroma_ctl, + # if a mismatch between pleroma_ctl and Pleroma accidentaly happens. + # Remove in the future. + def run(args) when is_binary(args) do [task | args] = String.split(args) case task do @@ -16,6 +19,20 @@ defmodule Pleroma.ReleaseTasks do end end + # HACK: Script arguments need to be received as a list, otherwise (quoted) arguments with + # whitespace will be broken. Previously the broken string form above was used, + # escaping in the shell does not help. + def run(args) when is_list(args) do + [task | args] = args + + case task do + "migrate" -> migrate(args) + "create" -> create() + "rollback" -> rollback(args) + task -> mix_task(task, args) + end + end + def find_module(task) do module_name = task diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex index bb55a4984..c7ee47c6e 100644 --- a/lib/pleroma/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy.ex @@ -12,7 +12,7 @@ defmodule Pleroma.ReverseProxy do @keep_resp_headers @resp_cache_headers ++ ~w(content-length content-type content-disposition content-encoding) ++ ~w(content-range accept-ranges vary) - @default_cache_control_header "public, max-age=1209600" + @default_cache_control_header "public, max-age=1209600, immutable" @valid_resp_codes [200, 206, 304] @max_read_duration :timer.seconds(30) @max_body_length :infinity diff --git a/lib/pleroma/reverse_proxy/client/hackney.ex b/lib/pleroma/reverse_proxy/client/hackney.ex index 0aa5f5715..4c81a6225 100644 --- a/lib/pleroma/reverse_proxy/client/hackney.ex +++ b/lib/pleroma/reverse_proxy/client/hackney.ex @@ -4,6 +4,26 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do @behaviour Pleroma.ReverseProxy.Client + @redirect_limit 5 + + require Logger + + # In-app redirect handler to avoid Hackney redirect bugs: + # - https://github.com/benoitc/hackney/issues/527 (relative/protocol-less redirects can crash Hackney) + # - https://github.com/benoitc/hackney/issues/273 (redirects not followed when using HTTP proxy) + # + # Based on a redirect handler from Pleb, slightly modified to work with Hackney: + # https://declin.eu/objects/d4f38e62-5429-4614-86d1-e8fc16e6bf33 + @redirect_statuses [301, 302, 303, 307, 308] + defp absolute_redirect_url(original_url, resp_headers) do + location = + Enum.find(resp_headers, fn {header, _location} -> + String.downcase(header) == "location" + end) + + URI.merge(original_url, elem(location, 1)) + |> URI.to_string() + end @impl true def request(method, url, headers, body, opts \\ []) do @@ -12,7 +32,24 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do path end) - :hackney.request(method, url, headers, body, opts) + if opts[:follow_redirect] != false do + {_state, req_opts} = Access.get_and_update(opts, :follow_redirect, fn a -> {a, false} end) + env = %{method: method, headers: headers, body: body, req_opts: req_opts} + res = :hackney.request(method, url, headers, body, req_opts) + + case res do + {:ok, code, resp_headers, _client} when code in @redirect_statuses -> + redirect(url, resp_headers, env, @redirect_limit) + + {:ok, code, resp_headers} when code in @redirect_statuses -> + redirect(url, resp_headers, env, @redirect_limit) + + _ -> + res + end + else + :hackney.request(method, url, headers, body, opts) + end end @impl true @@ -26,4 +63,32 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do @impl true def close(ref), do: :hackney.close(ref) + + defp redirect(url, resp_headers, env, limit) when limit == 0 do + new_url = absolute_redirect_url(url, resp_headers) + + Logger.debug( + "#{__MODULE__}: Handling redirect #{url} -> #{new_url}; redirect limit was reached - returning response after final redirect" + ) + + :hackney.request(env.method, new_url, env.headers, env.body, env.req_opts) + end + + defp redirect(url, resp_headers, env, limit) do + new_url = absolute_redirect_url(url, resp_headers) + Logger.debug("#{__MODULE__}: handling redirect #{url} -> #{new_url}; limit = #{limit}") + + res = :hackney.request(env.method, new_url, env.headers, env.body, env.req_opts) + + case res do + {:ok, code, new_resp_headers, _client} when code in @redirect_statuses -> + redirect(new_url, new_resp_headers, env, limit - 1) + + {:ok, code, new_resp_headers} when code in @redirect_statuses -> + redirect(new_url, new_resp_headers, env, limit - 1) + + _ -> + res + end + end end diff --git a/lib/pleroma/search.ex b/lib/pleroma/search.ex index 30b3ba958..9cd2768c4 100644 --- a/lib/pleroma/search.ex +++ b/lib/pleroma/search.ex @@ -1,11 +1,28 @@ defmodule Pleroma.Search do + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Workers.SearchIndexingWorker - def add_to_index(%Pleroma.Activity{id: activity_id}) do - SearchIndexingWorker.new(%{"op" => "add_to_index", "activity" => activity_id}) - |> Oban.insert() + @spec add_to_index(Activity.t()) :: {:ok, Oban.Job.t() | :noop} | {:error, Oban.Job.changeset()} + def add_to_index(%Activity{id: activity_id, object: %Object{} = object} = activity) do + with {_, true} <- {:indexable, indexable?(activity)}, + {_, "public"} <- {:visibility, Visibility.get_visibility(object)} do + SearchIndexingWorker.new(%{"op" => "add_to_index", "activity" => activity_id}) + |> Oban.insert() + else + _ -> {:ok, :noop} + end end + def add_to_index(%Activity{id: activity_id}) do + case Activity.get_by_id_with_object(activity_id) do + %Activity{} = preloaded -> add_to_index(preloaded) + _ -> {:ok, :noop} + end + end + + @spec remove_from_index(Object.t()) :: {:ok, Oban.Job.t()} | {:error, Oban.Job.changeset()} def remove_from_index(%Pleroma.Object{id: object_id}) do SearchIndexingWorker.new(%{"op" => "remove_from_index", "object" => object_id}) |> Oban.insert() @@ -20,4 +37,44 @@ defmodule Pleroma.Search do search_module = Pleroma.Config.get([Pleroma.Search, :module]) search_module.healthcheck_endpoints() end + + def object_to_search_data(%Object{} = object) do + data = object.data + + content_str = + case data["content"] do + [nil | rest] -> to_string(rest) + str -> str + end + + content = + with {:ok, scrubbed} <- + FastSanitize.Sanitizer.scrub(content_str, Pleroma.HTML.Scrubber.SearchIndexing), + trimmed <- String.trim(scrubbed) do + trimmed + end + + # Make sure we have a non-empty string + if content != "" do + {:ok, published, _} = DateTime.from_iso8601(data["published"]) + + %{ + id: object.id, + content: content, + ap: data["id"], + published: published |> DateTime.to_unix() + } + end + end + + defp indexable?(%Activity{ + data: %{"type" => "Create"}, + object: %Object{ + data: %{"content" => content, "published" => published, "type" => "Note"} + } + }) + when not is_nil(content) and content not in ["", "."] and not is_nil(published), + do: true + + defp indexable?(_), do: false end diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index cafae8099..dc10076e1 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -4,6 +4,8 @@ defmodule Pleroma.Search.Meilisearch do alias Pleroma.Activity alias Pleroma.Config.Getting, as: Config + alias Pleroma.Object + alias Pleroma.Search import Pleroma.Search.DatabaseSearch import Ecto.Query @@ -118,66 +120,24 @@ defmodule Pleroma.Search.Meilisearch do end end - def object_to_search_data(object) do - # Only index public or unlisted Notes - if not is_nil(object) and object.data["type"] == "Note" and - not is_nil(object.data["content"]) and - not is_nil(object.data["published"]) and - (Pleroma.Constants.as_public() in object.data["to"] or - Pleroma.Constants.as_public() in object.data["cc"]) and - object.data["content"] not in ["", "."] do - data = object.data - - content_str = - case data["content"] do - [nil | rest] -> to_string(rest) - str -> str - end - - content = - with {:ok, scrubbed} <- - FastSanitize.Sanitizer.scrub(content_str, Pleroma.HTML.Scrubber.SearchIndexing), - trimmed <- String.trim(scrubbed) do - trimmed - end - - # Make sure we have a non-empty string - if content != "" do - {:ok, published, _} = DateTime.from_iso8601(data["published"]) - - %{ - id: object.id, - content: content, - ap: data["id"], - published: published |> DateTime.to_unix() - } - end - end - end - @impl true - def add_to_index(activity) do - maybe_search_data = object_to_search_data(activity.object) + def add_to_index(%Activity{object: %Object{} = object} = activity) do + search_data = Search.object_to_search_data(object) - if activity.data["type"] == "Create" and maybe_search_data do - result = - meili_put( - "/indexes/objects/documents", - [maybe_search_data] - ) + result = + meili_put( + "/indexes/objects/documents", + [search_data] + ) - with {:ok, %{"status" => "enqueued"}} <- result do - # Added successfully - :ok - else - _ -> - # There was an error, report it - Logger.error("Failed to add activity #{activity.id} to index: #{inspect(result)}") - {:error, result} - end - else - # The post isn't something we can search, that's ok + with {:ok, %{"status" => "enqueued"}} <- result do + # Added successfully :ok + else + _ -> + # There was an error, report it + Logger.error("Failed to add activity #{activity.id} to index: #{inspect(result)}") + {:error, result} end end diff --git a/lib/pleroma/search/qdrant_search.ex b/lib/pleroma/search/qdrant_search.ex index 5142a273f..4d57cfa88 100644 --- a/lib/pleroma/search/qdrant_search.ex +++ b/lib/pleroma/search/qdrant_search.ex @@ -4,11 +4,12 @@ defmodule Pleroma.Search.QdrantSearch do alias Pleroma.Activity alias Pleroma.Config.Getting, as: Config + alias Pleroma.Object + alias Pleroma.Search alias __MODULE__.OpenAIClient alias __MODULE__.QdrantClient - import Pleroma.Search.Meilisearch, only: [object_to_search_data: 1] import Pleroma.Search.DatabaseSearch, only: [maybe_fetch: 3] @impl true @@ -82,23 +83,18 @@ defmodule Pleroma.Search.QdrantSearch do end @impl true - def add_to_index(activity) do - # This will only index public or unlisted notes - maybe_search_data = object_to_search_data(activity.object) + def add_to_index(%Activity{object: %Object{} = object} = activity) do + search_data = Search.object_to_search_data(object) - if activity.data["type"] == "Create" and maybe_search_data do - with {:ok, embedding} <- get_embedding(maybe_search_data.content), - {:ok, %{status: 200}} <- - QdrantClient.put( - "/collections/posts/points", - build_index_payload(activity, embedding) - ) do - :ok - else - e -> {:error, e} - end - else + with {:ok, embedding} <- get_embedding(search_data.content), + {:ok, %{status: 200}} <- + QdrantClient.put( + "/collections/posts/points", + build_index_payload(activity, embedding) + ) do :ok + else + e -> {:error, e} end end diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index 06d8005bc..350ff6cb3 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -93,7 +93,7 @@ defmodule Pleroma.Upload do def store(upload, opts \\ []) do opts = get_opts(opts) - with {:ok, upload} <- prepare_upload(upload, opts), + with {:ok, %__MODULE__{} = upload} <- prepare_upload(upload, opts), upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"}, {:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload), description = get_description(upload), diff --git a/lib/pleroma/upload/filter/exiftool/read_description.ex b/lib/pleroma/upload/filter/exiftool/read_description.ex index 8c1ed82f8..8283b8643 100644 --- a/lib/pleroma/upload/filter/exiftool/read_description.ex +++ b/lib/pleroma/upload/filter/exiftool/read_description.ex @@ -29,22 +29,26 @@ defmodule Pleroma.Upload.Filter.Exiftool.ReadDescription do do: current_description defp read_when_empty(_, file, tag) do - try do - {tag_content, 0} = - System.cmd("exiftool", ["-b", "-s3", tag, file], - stderr_to_stdout: false, - parallelism: true - ) + if File.exists?(file) do + try do + {tag_content, 0} = + System.cmd("exiftool", ["-m", "-b", "-s3", tag, file], + stderr_to_stdout: false, + parallelism: true + ) - tag_content = String.trim(tag_content) + tag_content = String.trim(tag_content) - if tag_content != "" and - String.length(tag_content) <= - Pleroma.Config.get([:instance, :description_limit]), - do: tag_content, - else: nil - rescue - _ in ErlangError -> nil + if tag_content != "" and + String.length(tag_content) <= + Pleroma.Config.get([:instance, :description_limit]), + do: tag_content, + else: nil + rescue + _ in ErlangError -> nil + end + else + nil end end end diff --git a/lib/pleroma/upload/filter/exiftool/strip_location.ex b/lib/pleroma/upload/filter/exiftool/strip_location.ex index 1744a286d..23346d234 100644 --- a/lib/pleroma/upload/filter/exiftool/strip_location.ex +++ b/lib/pleroma/upload/filter/exiftool/strip_location.ex @@ -16,11 +16,12 @@ defmodule Pleroma.Upload.Filter.Exiftool.StripLocation do def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do try do - case System.cmd("exiftool", ["-overwrite_original", "-gps:all=", "-png:all=", file], + case System.cmd("exiftool", ["-m", "-overwrite_original", "-gps:all=", "-png:all=", file], + stderr_to_stdout: true, parallelism: true ) do {_response, 0} -> {:ok, :filtered} - {error, 1} -> {:error, error} + {error, _} -> {:error, error} end rescue e in ErlangError -> diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 904e9e056..75da41da9 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -287,8 +287,14 @@ defmodule Pleroma.User do defdelegate following(user), to: FollowingRelationship defdelegate following?(follower, followed), to: FollowingRelationship defdelegate following_ap_ids(user), to: FollowingRelationship - defdelegate get_follow_requests(user), to: FollowingRelationship - defdelegate get_outgoing_follow_requests(user), to: FollowingRelationship + defdelegate get_follow_requests_query(user), to: FollowingRelationship + defdelegate get_outgoing_follow_requests_query(user), to: FollowingRelationship + + def get_follow_requests(user) do + get_follow_requests_query(user) + |> Repo.all() + end + defdelegate search(query, opts \\ []), to: User.Search @doc """ diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex index 3f67cdf0c..35535528b 100644 --- a/lib/pleroma/user/backup.ex +++ b/lib/pleroma/user/backup.ex @@ -342,7 +342,7 @@ defmodule Pleroma.User.Backup do dir, "outbox", fn a -> - with {:ok, activity} <- Transmogrifier.prepare_outgoing(a.data) do + with {:ok, activity} <- Transmogrifier.prepare_activity(a.data) do {:ok, Map.delete(activity, "@context")} end end diff --git a/lib/pleroma/user_relationship.ex b/lib/pleroma/user_relationship.ex index 07b6e46f7..cf5025670 100644 --- a/lib/pleroma/user_relationship.ex +++ b/lib/pleroma/user_relationship.ex @@ -45,7 +45,7 @@ defmodule Pleroma.UserRelationship do do: exists?(unquote(relationship_type), source, target) # `def get_block_expire_date/2`, `def get_mute_expire_date/2`, - # `def get_reblog_mute_expire_date/2`, `def get_notification_mute_exists?/2`, + # `def get_reblog_mute_expire_date/2`, `def get_notification_mute_expire_date/2`, # `def get_inverse_subscription_expire_date/2`, `def get_inverse_endorsement_expire_date/2` def unquote(:"get_#{relationship_type}_expire_date")(source, target), do: get_expire_date(unquote(relationship_type), source, target) diff --git a/lib/pleroma/utils.ex b/lib/pleroma/utils.ex index 73001c987..61d122a47 100644 --- a/lib/pleroma/utils.ex +++ b/lib/pleroma/utils.ex @@ -17,7 +17,7 @@ defmodule Pleroma.Utils do dir |> File.ls!() |> Enum.map(&Path.join(dir, &1)) - |> Kernel.ParallelCompiler.compile() + |> Kernel.ParallelCompiler.compile(return_diagnostics: true) end @doc """ diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index e58e3dd57..071d634db 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1003,6 +1003,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_state(query, _), do: query + defp restrict_assigned_account(query, %{assigned_account: assigned_account}) do + from(activity in query, + where: fragment("?->>'assigned_account' = ?", activity.data, ^assigned_account) + ) + end + + defp restrict_assigned_account(query, _), do: query + defp restrict_favorited_by(query, %{favorited_by: ap_id}) do from( [_activity, object] in query, @@ -1471,6 +1479,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> restrict_actor(opts) |> restrict_type(opts) |> restrict_state(opts) + |> restrict_assigned_account(opts) |> restrict_favorited_by(opts) |> restrict_blocked(restrict_blocked_opts) |> restrict_blockers_visibility(opts) @@ -1609,6 +1618,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp normalize_image(urls) when is_list(urls), do: urls |> List.first() |> normalize_image() defp normalize_image(_), do: nil + defp normalize_also_known_as(urls) when is_list(urls), do: urls + defp normalize_also_known_as(url) when is_binary(url), do: [url] + defp normalize_also_known_as(nil), do: [] + defp maybe_put_description(map, %{"name" => description}) when is_binary(description) do Map.put(map, "name", description) end @@ -1684,7 +1697,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do featured_address: featured_address, bio: data["summary"] || "", actor_type: actor_type, - also_known_as: Map.get(data, "alsoKnownAs", []), + also_known_as: normalize_also_known_as(data["alsoKnownAs"]), public_key: public_key, inbox: data["inbox"], shared_inbox: shared_inbox, diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 046316024..3b208ab77 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -17,7 +17,6 @@ defmodule Pleroma.Web.ActivityPub.Builder do alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.CommonAPI.ActivityDraft - alias Pleroma.Web.Endpoint require Pleroma.Constants @@ -64,15 +63,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do defp add_emoji_content(data, emoji, url) do tag = [ - %{ - "id" => url, - "type" => "Emoji", - "name" => Emoji.maybe_quote(emoji), - "icon" => %{ - "type" => "Image", - "url" => url - } - } + Emoji.build_emoji_tag({Emoji.maybe_strip_name(emoji), url}) ] data @@ -113,7 +104,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do defp local_custom_emoji_react(data, emoji) do with %{file: path} = emojo <- Emoji.get(emoji) do - url = "#{Endpoint.url()}#{path}" + url = Emoji.local_url(path) add_emoji_content(data, emojo.code, url) else _ -> {:error, "Emoji does not exist"} @@ -341,21 +332,18 @@ defmodule Pleroma.Web.ActivityPub.Builder do @spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()} def announce(actor, object, options \\ []) do - public? = Keyword.get(options, :public, false) + visibility = Keyword.get(options, :visibility, "public") - to = - cond do - actor.ap_id == Relay.ap_id() -> - [actor.follower_address] - - public? and Visibility.local_public?(object) -> - [actor.follower_address, object.data["actor"], Utils.as_local_public()] - - public? -> - [actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()] - - true -> - [actor.follower_address, object.data["actor"]] + {to, cc} = + if actor.ap_id == Relay.ap_id() do + {[actor.follower_address], []} + else + Pleroma.Web.CommonAPI.Utils.get_to_and_cc_for_visibility( + visibility, + actor.follower_address, + nil, + [object.data["actor"]] + ) end {:ok, @@ -364,6 +352,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do "actor" => actor.ap_id, "object" => object.data["id"], "to" => to, + "cc" => cc, "context" => object.data["context"], "type" => "Announce", "published" => Utils.make_date() diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex index b0d07a6f8..43c2c0449 100644 --- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex @@ -27,7 +27,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do end defp fetch(url) do - http_client_opts = Pleroma.Config.get([:media_proxy, :proxy_opts, :http], pool: :media) + # This module uses Tesla (Pleroma.HTTP) to fetch the MediaProxy URL. + # Redirect following is handled by Tesla middleware, so we must not enable + # adapter-level redirect logic (Hackney can crash on relative redirects when proxied). + http_client_opts = + [:media_proxy, :proxy_opts, :http] + |> Pleroma.Config.get(pool: :media) + |> Keyword.drop([:follow_redirect, :force_redirect]) + HTTP.get(url, [], http_client_opts) end diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex index 21940f4f1..065c75910 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex @@ -28,6 +28,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do end field(:closed, ObjectValidators.DateTime) + field(:votersCount, :integer) field(:voters, {:array, ObjectValidators.ObjectID}, default: []) field(:nonAnonymous, :boolean) embeds_many(:anyOf, QuestionOptionsValidator) diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index b6c814aed..e3c4e01a4 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -79,7 +79,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do Determine if an activity can be represented by running it through Transmogrifier. """ def representable?(%Activity{} = activity) do - with {:ok, _data} <- @transmogrifier_impl.prepare_outgoing(activity.data) do + with {:ok, _data} <- @transmogrifier_impl.prepare_activity(activity.data) do true else _e -> @@ -102,14 +102,14 @@ defmodule Pleroma.Web.ActivityPub.Publisher do Logger.debug("Federating #{ap_id} to #{inbox}") uri = %{path: path} = URI.parse(inbox) - {:ok, data} = @transmogrifier_impl.prepare_outgoing(activity.data) + {:ok, data} = @transmogrifier_impl.prepare_activity(activity.data) {actor, data} = with {_, false} <- {:actor_changed?, data["actor"] != activity.data["actor"]} do {actor, data} else {:actor_changed?, true} -> - # If prepare_outgoing changes the actor, re-get it from the db + # If prepare_activity changes the actor, re-get it from the db new_actor = User.get_cached_by_ap_id(data["actor"]) {new_actor, data} end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 6e04d95e6..4421da26c 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do @behaviour Pleroma.Web.ActivityPub.Transmogrifier.API alias Pleroma.Activity alias Pleroma.EctoType.ActivityPub.ObjectValidators + alias Pleroma.Emoji alias Pleroma.Maps alias Pleroma.Object alias Pleroma.Object.Containment @@ -782,7 +783,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def set_replies(obj_data), do: obj_data - # Prepares the object of an outgoing create activity. + defp set_voters_count(%{"voters" => [_ | _] = voters} = obj) do + Map.merge(obj, %{"votersCount" => length(voters)}) + end + + defp set_voters_count(obj), do: obj + + # Prepares and sanitizes the object for federation. def prepare_object(object) do object |> add_hashtags @@ -794,6 +801,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> set_reply_to_uri |> set_quote_url |> set_replies + |> set_voters_count |> CommonFixes.maybe_add_content_map() |> strip_internal_fields |> strip_internal_tags @@ -823,7 +831,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do # internal -> Mastodon # """ - def prepare_outgoing(%{"type" => activity_type, "object" => object_id} = data) + def prepare_activity(%{"type" => activity_type, "object" => object_id} = data) when activity_type in ["Create", "Listen"] do object = object_id @@ -839,7 +847,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do {:ok, data} end - def prepare_outgoing(%{"type" => "Update", "object" => %{"type" => objtype} = object} = data) + def prepare_activity(%{"type" => "Update", "object" => %{"type" => objtype} = object} = data) when objtype in Pleroma.Constants.updatable_object_types() do data = data @@ -850,7 +858,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do {:ok, data} end - def prepare_outgoing(%{"type" => "Update", "object" => %{"type" => objtype} = object} = data) + def prepare_activity(%{"type" => "Update", "object" => %{"type" => objtype} = object} = data) when objtype in Pleroma.Constants.actor_types() do object = object @@ -867,11 +875,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do {:ok, data} end - def prepare_outgoing(%{"type" => "Update", "object" => %{}} = data) do + def prepare_activity(%{"type" => "Update", "object" => %{}} = data) do raise "Requested to serve an Update for non-updateable object type: #{inspect(data)}" end - def prepare_outgoing(%{"type" => "Announce", "actor" => ap_id, "object" => object_id} = data) do + def prepare_activity(%{"type" => "Announce", "actor" => ap_id, "object" => object_id} = data) do object = object_id |> Object.normalize(fetch: false) @@ -894,7 +902,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do # Mastodon Accept/Reject requires a non-normalized object containing the actor URIs, # because of course it does. - def prepare_outgoing(%{"type" => "Accept"} = data) do + def prepare_activity(%{"type" => "Accept"} = data) do with follow_activity <- Activity.normalize(data["object"]) do object = %{ "actor" => follow_activity.actor, @@ -912,7 +920,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end - def prepare_outgoing(%{"type" => "Reject"} = data) do + def prepare_activity(%{"type" => "Reject"} = data) do with follow_activity <- Activity.normalize(data["object"]) do object = %{ "actor" => follow_activity.actor, @@ -930,7 +938,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end - def prepare_outgoing(%{"type" => "Flag"} = data) do + def prepare_activity(%{"type" => "Flag"} = data) do with {:ok, stripped_activity} <- Utils.strip_report_status_data(data), stripped_activity <- Utils.maybe_anonymize_reporter(stripped_activity), stripped_activity <- Map.merge(stripped_activity, Utils.make_json_ld_header()) do @@ -938,7 +946,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end - def prepare_outgoing(%{"type" => _type} = data) do + def prepare_activity(%{"type" => _type} = data) do data = data |> strip_internal_fields @@ -1005,32 +1013,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def take_emoji_tags(%User{emoji: emoji}) do emoji |> Map.to_list() - |> Enum.map(&build_emoji_tag/1) + |> Enum.map(&Emoji.build_emoji_tag/1) end # TODO: we should probably send mtime instead of unix epoch time for updated def add_emoji_tags(%{"emoji" => emoji} = object) do tags = object["tag"] || [] - out = Enum.map(emoji, &build_emoji_tag/1) + out = Enum.map(emoji, &Emoji.build_emoji_tag/1) Map.put(object, "tag", tags ++ out) end def add_emoji_tags(object), do: object - def build_emoji_tag({name, url}) do - url = URI.encode(url) - - %{ - "icon" => %{"url" => "#{url}", "type" => "Image"}, - "name" => ":" <> name <> ":", - "type" => "Emoji", - "updated" => "1970-01-01T00:00:00Z", - "id" => url - } - end - def set_conversation(object) do Map.put(object, "conversation", object["context"]) end diff --git a/lib/pleroma/web/activity_pub/transmogrifier/api.ex b/lib/pleroma/web/activity_pub/transmogrifier/api.ex index b9f65d17c..f2e416575 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier/api.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier/api.ex @@ -7,5 +7,5 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.API do Behaviour for the subset of Transmogrifier used by Publisher. """ - @callback prepare_outgoing(map()) :: {:ok, map()} | {:error, term()} + @callback prepare_activity(map()) :: {:ok, map()} | {:error, term()} end diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index c5a6901d4..43c0f456d 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -863,6 +863,34 @@ defmodule Pleroma.Web.ActivityPub.Utils do def update_report_state(_, _), do: {:error, "Unsupported state"} + def assign_report_to_account(%Activity{} = activity, nil = _account) do + new_data = Map.delete(activity.data, "assigned_account") + + activity + |> Changeset.change(data: new_data) + |> Repo.update() + end + + def assign_report_to_account(%Activity{} = activity, account) do + new_data = Map.put(activity.data, "assigned_account", account) + + activity + |> Changeset.change(data: new_data) + |> Repo.update() + end + + def assign_report_to_account(activity_ids, account) do + activities_num = length(activity_ids) + + from(a in Activity, where: a.id in ^activity_ids) + |> update(set: [data: fragment("jsonb_set(data, '{assigned_account}', ?)", ^account)]) + |> Repo.update_all([]) + |> case do + {^activities_num, _} -> :ok + _ -> {:error, activity_ids} + end + end + def strip_report_status_data(%Activity{} = activity) do with {:ok, new_data} <- strip_report_status_data(activity.data) do {:ok, %{activity | data: new_data}} diff --git a/lib/pleroma/web/activity_pub/views/object_view.ex b/lib/pleroma/web/activity_pub/views/object_view.ex index a672ccc8b..9334b797a 100644 --- a/lib/pleroma/web/activity_pub/views/object_view.ex +++ b/lib/pleroma/web/activity_pub/views/object_view.ex @@ -19,7 +19,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do end def render("object.json", %{object: %Activity{} = activity}) do - {:ok, ap_data} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, ap_data} = Transmogrifier.prepare_activity(activity.data) ap_data end diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 4362db324..8847692c8 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -35,32 +35,14 @@ defmodule Pleroma.Web.ActivityPub.UserView do def render("endpoints.json", _), do: %{} def render("service.json", %{user: user}) do - {:ok, _, public_key} = Keys.keys_from_pem(user.keys) - public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) - public_key = :public_key.pem_encode([public_key]) - - endpoints = render("endpoints.json", %{user: user}) - - %{ - "id" => user.ap_id, + Map.merge(common_actor_fields(user), %{ "type" => "Application", - "following" => "#{user.ap_id}/following", - "followers" => "#{user.ap_id}/followers", - "inbox" => "#{user.ap_id}/inbox", - "outbox" => "#{user.ap_id}/outbox", "name" => "Pleroma", "summary" => "An internal service actor for this Pleroma instance. No user-serviceable parts inside.", - "url" => user.ap_id, "manuallyApprovesFollowers" => false, - "publicKey" => %{ - "id" => "#{user.ap_id}#main-key", - "owner" => user.ap_id, - "publicKeyPem" => public_key - }, - "endpoints" => endpoints, "invisible" => User.invisible?(user) - } + }) |> Map.merge(Utils.make_json_ld_header()) end @@ -77,13 +59,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do end def render("user.json", %{user: user}) do - {:ok, _, public_key} = Keys.keys_from_pem(user.keys) - public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) - public_key = :public_key.pem_encode([public_key]) user = User.sanitize_html(user) - endpoints = render("endpoints.json", %{user: user}) - emoji_tags = Transmogrifier.take_emoji_tags(user) fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue")) @@ -102,25 +79,9 @@ defmodule Pleroma.Web.ActivityPub.UserView do do: Date.to_iso8601(user.birthday), else: nil - %{ - "id" => user.ap_id, - "type" => user.actor_type, - "following" => "#{user.ap_id}/following", - "followers" => "#{user.ap_id}/followers", - "inbox" => "#{user.ap_id}/inbox", - "outbox" => "#{user.ap_id}/outbox", + Map.merge(common_actor_fields(user), %{ "featured" => "#{user.ap_id}/collections/featured", "preferredUsername" => user.nickname, - "name" => user.name, - "summary" => user.bio, - "url" => user.ap_id, - "manuallyApprovesFollowers" => user.is_locked, - "publicKey" => %{ - "id" => "#{user.ap_id}#main-key", - "owner" => user.ap_id, - "publicKeyPem" => public_key - }, - "endpoints" => endpoints, "attachment" => fields, "tag" => emoji_tags, # Note: key name is indeed "discoverable" (not an error) @@ -130,7 +91,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do "vcard:bday" => birthday, "webfinger" => "acct:#{User.full_nickname(user)}", "published" => Pleroma.Web.CommonAPI.Utils.to_masto_date(user.inserted_at) - } + }) |> Map.merge( maybe_make_image( &User.avatar_url/2, @@ -283,7 +244,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do }) do collection = Enum.map(activities, fn activity -> - {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, data} = Transmogrifier.prepare_activity(activity.data) data end) @@ -309,6 +270,33 @@ defmodule Pleroma.Web.ActivityPub.UserView do |> Map.merge(Utils.make_json_ld_header()) end + defp common_actor_fields(%User{} = user) do + endpoints = render("endpoints.json", %{user: user}) + + {:ok, _, public_key} = Keys.keys_from_pem(user.keys) + public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) + public_key = :public_key.pem_encode([public_key]) + + %{ + "id" => user.ap_id, + "type" => user.actor_type, + "following" => "#{user.ap_id}/following", + "followers" => "#{user.ap_id}/followers", + "inbox" => "#{user.ap_id}/inbox", + "outbox" => "#{user.ap_id}/outbox", + "name" => user.name, + "summary" => user.bio, + "url" => user.ap_id, + "manuallyApprovesFollowers" => user.is_locked, + "endpoints" => endpoints, + "publicKey" => %{ + "id" => "#{user.ap_id}#main-key", + "owner" => user.ap_id, + "publicKeyPem" => public_key + } + } + end + defp maybe_put_total_items(map, false, _total), do: map defp maybe_put_total_items(map, true, total) do diff --git a/lib/pleroma/web/admin_api/controllers/config_controller.ex b/lib/pleroma/web/admin_api/controllers/config_controller.ex index 2c9c27294..3eba03525 100644 --- a/lib/pleroma/web/admin_api/controllers/config_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/config_controller.ex @@ -174,6 +174,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do end end + defp whitelisted_config?(":pleroma", ":database_config_whitelist"), do: false + defp whitelisted_config?(group, key) do if whitelisted_configs = Config.get(:database_config_whitelist) do Enum.any?(whitelisted_configs, fn diff --git a/lib/pleroma/web/admin_api/controllers/report_controller.ex b/lib/pleroma/web/admin_api/controllers/report_controller.ex index 89d8cc820..dbac03ef4 100644 --- a/lib/pleroma/web/admin_api/controllers/report_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/report_controller.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.AdminAPI.ReportController do alias Pleroma.Activity alias Pleroma.ModerationLog alias Pleroma.ReportNote + alias Pleroma.User alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.AdminAPI alias Pleroma.Web.AdminAPI.Report @@ -24,7 +25,7 @@ defmodule Pleroma.Web.AdminAPI.ReportController do plug( OAuthScopesPlug, %{scopes: ["admin:write:reports"]} - when action in [:update, :notes_create, :notes_delete] + when action in [:update, :assign_account, :notes_create, :notes_delete] ) action_fallback(AdminAPI.FallbackController) @@ -79,6 +80,22 @@ defmodule Pleroma.Web.AdminAPI.ReportController do end end + def assign_account( + %{ + assigns: %{user: admin}, + private: %{open_api_spex: %{body_params: %{reports: reports}}} + } = conn, + _ + ) do + result = Enum.map(reports, &do_assign_account(&1, admin)) + + if Enum.any?(result, &Map.has_key?(&1, :error)) do + json_response(conn, :bad_request, result) + else + json_response(conn, :no_content, "") + end + end + def notes_create( %{ assigns: %{user: user}, @@ -131,4 +148,40 @@ defmodule Pleroma.Web.AdminAPI.ReportController do _ -> json_response(conn, :bad_request, "") end end + + defp do_assign_account(%{assigned_account: nil, id: id}, admin) do + with {:ok, activity} <- CommonAPI.assign_report_to_account(id, nil), + report <- Activity.get_by_id_with_user_actor(activity.id) do + ModerationLog.insert_log(%{ + action: "report_unassigned", + actor: admin, + subject: activity, + subject_actor: report.user_actor + }) + + activity + else + {:error, message} -> + %{id: id, error: message} + end + end + + defp do_assign_account(%{assigned_account: assigned_account, id: id}, admin) do + with %User{id: account} = user <- User.get_cached_by_nickname(assigned_account), + {:ok, activity} <- CommonAPI.assign_report_to_account(id, account), + report <- Activity.get_by_id_with_user_actor(activity.id) do + ModerationLog.insert_log(%{ + action: "report_assigned", + actor: admin, + subject: activity, + subject_actor: report.user_actor, + assigned_account: user.nickname + }) + + activity + else + {:error, message} -> + %{id: id, error: message} + end + end end diff --git a/lib/pleroma/web/admin_api/report.ex b/lib/pleroma/web/admin_api/report.ex index fa89e3405..753b92d88 100644 --- a/lib/pleroma/web/admin_api/report.ex +++ b/lib/pleroma/web/admin_api/report.ex @@ -13,6 +13,11 @@ defmodule Pleroma.Web.AdminAPI.Report do user = User.get_cached_by_ap_id(actor) account = User.get_cached_by_ap_id(account_ap_id) + assigned_account = + if Map.has_key?(report.data, "assigned_account") do + User.get_cached_by_id(report.data["assigned_account"]) + end + statuses = status_ap_ids |> Enum.reject(&is_nil(&1)) @@ -26,7 +31,13 @@ defmodule Pleroma.Web.AdminAPI.Report do Activity.get_by_ap_id_with_object(act) end) - %{report: report, user: user, account: account, statuses: statuses} + %{ + report: report, + user: user, + account: account, + statuses: statuses, + assigned_account: assigned_account + } end defp make_fake_activity(act, user) do diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index b4b0be267..da6166050 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -26,7 +26,13 @@ defmodule Pleroma.Web.AdminAPI.ReportView do } end - def render("show.json", %{report: report, user: user, account: account, statuses: statuses}) do + def render("show.json", %{ + report: report, + user: user, + account: account, + statuses: statuses, + assigned_account: assigned_account + }) do created_at = Utils.to_masto_date(report.data["published"]) content = @@ -36,6 +42,11 @@ defmodule Pleroma.Web.AdminAPI.ReportView do nil end + assigned_account = + if assigned_account do + merge_account_views(assigned_account) + end + %{ id: report.id, account: merge_account_views(account), @@ -49,7 +60,8 @@ defmodule Pleroma.Web.AdminAPI.ReportView do }), state: report.data["state"], notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes}), - rules: rules(Map.get(report.data, "rules", nil)) + rules: rules(Map.get(report.data, "rules", nil)), + assigned_account: assigned_account } end diff --git a/lib/pleroma/web/api_spec/cast_and_validate.ex b/lib/pleroma/web/api_spec/cast_and_validate.ex index 672d1c4a1..95bd4d9cf 100644 --- a/lib/pleroma/web/api_spec/cast_and_validate.ex +++ b/lib/pleroma/web/api_spec/cast_and_validate.ex @@ -106,7 +106,14 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do OpenApiSpex.cast_and_validate(spec, operation, conn, content_type, cast_opts) end - defp cast_and_validate(spec, operation, conn, content_type, false = _strict, cast_opts) do + defp cast_and_validate( + spec, + operation, + %Conn{} = conn, + content_type, + false = _strict, + cast_opts + ) do case OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) do {:ok, conn} -> {:ok, conn} diff --git a/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex index 2b2496c26..7d83066ca 100644 --- a/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex @@ -123,7 +123,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do name: %Schema{type: :string, description: "Application Name"}, scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"}, redirect_uris: %Schema{ - type: :string, + oneOf: [ + %Schema{type: :string}, + %Schema{type: :array, items: %Schema{type: :string}} + ], description: "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter." }, @@ -141,7 +144,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do }, example: %{ "name" => "My App", - "redirect_uris" => "https://myapp.com/auth/callback", + "redirect_uris" => ["https://myapp.com/auth/callback"], "website" => "https://myapp.com/", "scopes" => ["read", "write"], "trusted" => true @@ -157,7 +160,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do name: %Schema{type: :string, description: "Application Name"}, scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"}, redirect_uris: %Schema{ - type: :string, + oneOf: [ + %Schema{type: :string}, + %Schema{type: :array, items: %Schema{type: :string}} + ], description: "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter." }, @@ -175,7 +181,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do }, example: %{ "name" => "My App", - "redirect_uris" => "https://myapp.com/auth/callback", + "redirect_uris" => ["https://myapp.com/auth/callback"], "website" => "https://myapp.com/", "scopes" => ["read", "write"], "trusted" => true diff --git a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex index 25a604beb..58669a1fc 100644 --- a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex @@ -53,6 +53,12 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do :query, %Schema{type: :integer, default: 50}, "Number number of log entries per page" + ), + Operation.parameter( + :assigned_account, + :query, + %Schema{type: :string}, + "Filter by assigned account ID" ) | admin_api_params() ], @@ -103,6 +109,22 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do } end + def assign_account_operation do + %Operation{ + tags: ["Report management"], + summary: "Assign account to specified reports", + operationId: "AdminAPI.ReportController.assign_account", + security: [%{"oAuth" => ["admin:write:reports"]}], + parameters: admin_api_params(), + requestBody: request_body("Parameters", assign_account_request(), required: true), + responses: %{ + 204 => no_content_response(), + 400 => Operation.response("Bad Request", "application/json", update_400_response()), + 403 => Operation.response("Forbidden", "application/json", ApiError) + } + } + end + def notes_create_operation do %Operation{ tags: ["Report management"], @@ -186,7 +208,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do hint: %Schema{type: :string, nullable: true} } } - } + }, + assigned_account: + account_admin() + |> Map.put(:nullable, true) } } end @@ -242,6 +267,34 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do } end + defp assign_account_request do + %Schema{ + type: :object, + required: [:reports], + properties: %{ + reports: %Schema{ + type: :array, + items: %Schema{ + type: :object, + properties: %{ + id: %Schema{allOf: [FlakeID], description: "Required, report ID"}, + assigned_account: %Schema{ + type: :string, + description: "User nickname", + nullable: true + } + } + }, + example: %{ + "reports" => [ + %{"id" => "123", "assigned_account" => "pleroma"} + ] + } + } + } + } + end + defp update_400_response do %Schema{ type: :array, diff --git a/lib/pleroma/web/api_spec/operations/app_operation.ex b/lib/pleroma/web/api_spec/operations/app_operation.ex index dfa2237c0..71c15a665 100644 --- a/lib/pleroma/web/api_spec/operations/app_operation.ex +++ b/lib/pleroma/web/api_spec/operations/app_operation.ex @@ -97,7 +97,10 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do properties: %{ client_name: %Schema{type: :string, description: "A name for your application."}, redirect_uris: %Schema{ - type: :string, + oneOf: [ + %Schema{type: :string}, + %Schema{type: :array, items: %Schema{type: :string}} + ], description: "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter." }, diff --git a/lib/pleroma/web/api_spec/operations/follow_request_operation.ex b/lib/pleroma/web/api_spec/operations/follow_request_operation.ex index 72dc8b5fa..fbb997447 100644 --- a/lib/pleroma/web/api_spec/operations/follow_request_operation.ex +++ b/lib/pleroma/web/api_spec/operations/follow_request_operation.ex @@ -19,6 +19,7 @@ defmodule Pleroma.Web.ApiSpec.FollowRequestOperation do summary: "Retrieve follow requests", security: [%{"oAuth" => ["read:follows", "follow"]}], operationId: "FollowRequestController.index", + parameters: pagination_params(), responses: %{ 200 => Operation.response("Array of Account", "application/json", %Schema{ @@ -62,4 +63,22 @@ defmodule Pleroma.Web.ApiSpec.FollowRequestOperation do required: true ) end + + defp pagination_params do + [ + Operation.parameter(:max_id, :query, :string, "Return items older than this ID"), + Operation.parameter( + :since_id, + :query, + :string, + "Return the oldest items newer than this ID" + ), + Operation.parameter( + :limit, + :query, + %Schema{type: :integer, default: 20}, + "Maximum number of items to return. Will be ignored if it's more than 40" + ) + ] + end end diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index 911ffb994..6be4ea996 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -57,6 +57,22 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do } end + def domain_blocks_operation do + %Operation{ + tags: ["Instance misc"], + summary: "Retrieve instance domain blocks", + operationId: "InstanceController.domain_blocks", + responses: %{ + 200 => + Operation.response( + "Array of domain blocks", + "application/json", + array_of_domain_blocks() + ) + } + } + end + def translation_languages_operation do %Operation{ tags: ["Instance misc"], @@ -326,6 +342,18 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do max_pinned_statuses: %Schema{ type: :integer, description: "The maximum number of pinned statuses for each account." + }, + max_profile_fields: %Schema{ + type: :integer, + description: "The maximum number of custom profile fields allowed to be set." + }, + profile_field_name_limit: %Schema{ + type: :integer, + description: "The maximum size of a profile field name, in characters." + }, + profile_field_value_limit: %Schema{ + type: :integer, + description: "The maximum size of a profile field value, in characters." } } }, @@ -420,4 +448,19 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do } } end + + defp array_of_domain_blocks do + %Schema{ + type: :array, + items: %Schema{ + type: :object, + properties: %{ + domain: %Schema{type: :string}, + digest: %Schema{type: :string}, + severity: %Schema{type: :string}, + comment: %Schema{type: :string} + } + } + } + end end diff --git a/lib/pleroma/web/api_spec/operations/list_operation.ex b/lib/pleroma/web/api_spec/operations/list_operation.ex index 7d876ae2d..87189edc2 100644 --- a/lib/pleroma/web/api_spec/operations/list_operation.ex +++ b/lib/pleroma/web/api_spec/operations/list_operation.ex @@ -36,7 +36,7 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do summary: "Create a list", description: "Fetch the list with the given ID. Used for verifying the title of a list.", operationId: "ListController.create", - requestBody: create_update_request(), + requestBody: create_request(), security: [%{"oAuth" => ["write:lists"]}], responses: %{ 200 => Operation.response("List", "application/json", List), @@ -68,7 +68,7 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do description: "Change the title of a list", operationId: "ListController.update", parameters: [id_param()], - requestBody: create_update_request(), + requestBody: update_request(), security: [%{"oAuth" => ["write:lists"]}], responses: %{ 200 => Operation.response("List", "application/json", List), @@ -164,14 +164,18 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do ) end - defp create_update_request do + defp create_request do request_body( "Parameters", %Schema{ - description: "POST body for creating or updating a List", + description: "POST body for creating a List", type: :object, 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" + } }, required: [:title] }, @@ -179,6 +183,24 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do ) end + defp update_request do + request_body( + "Parameters", + %Schema{ + description: "PUT body for updating a List", + type: :object, + properties: %{ + title: %Schema{type: :string, description: "List title"}, + exclusive: %Schema{ + type: :boolean, + description: "Whether members of the list should be removed from the “Home” feed" + } + } + }, + required: true + ) + end + defp add_remove_accounts_request(required) when is_boolean(required) do request_body( "Parameters", diff --git a/lib/pleroma/web/api_spec/operations/pleroma_follow_request_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_follow_request_operation.ex index b5a413490..b3fa0457d 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_follow_request_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_follow_request_operation.ex @@ -18,6 +18,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaFollowRequestOperation do summary: "Retrieve outgoing follow requests", security: [%{"oAuth" => ["read:follows", "follow"]}], operationId: "PleromaFollowRequestController.outgoing", + parameters: pagination_params(), responses: %{ 200 => Operation.response("Array of Account", "application/json", %Schema{ @@ -28,4 +29,22 @@ defmodule Pleroma.Web.ApiSpec.PleromaFollowRequestOperation do } } end + + defp pagination_params do + [ + Operation.parameter(:max_id, :query, :string, "Return items older than this ID"), + Operation.parameter( + :since_id, + :query, + :string, + "Return the oldest items newer than this ID" + ), + Operation.parameter( + :limit, + :query, + %Schema{type: :integer, default: 20}, + "Maximum number of items to return. Will be ignored if it's more than 40" + ) + ] + end end diff --git a/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_util_operation.ex similarity index 84% rename from lib/pleroma/web/api_spec/operations/twitter_util_operation.ex rename to lib/pleroma/web/api_spec/operations/pleroma_util_operation.ex index 724d873c0..4bb4f112c 100644 --- a/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_util_operation.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do +defmodule Pleroma.Web.ApiSpec.PleromaUtilOperation do alias OpenApiSpex.Operation alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Schemas.ApiError @@ -19,7 +19,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do %Operation{ tags: ["Custom emojis"], summary: "List all custom emojis", - operationId: "UtilController.emoji", + operationId: "PleromaAPI.UtilController.emoji", parameters: [], responses: %{ 200 => @@ -48,7 +48,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do %Operation{ tags: ["Others"], summary: "Dump frontend configurations", - operationId: "UtilController.frontend_configurations", + operationId: "PleromaAPI.UtilController.frontend_configurations", parameters: [], responses: %{ 200 => @@ -70,7 +70,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Account credentials"], summary: "Change account password", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.change_password", + operationId: "PleromaAPI.UtilController.change_password", requestBody: request_body("Parameters", change_password_request(), required: true), responses: %{ 200 => @@ -106,7 +106,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Account credentials"], summary: "Change account email", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.change_email", + operationId: "PleromaAPI.UtilController.change_email", requestBody: request_body("Parameters", change_email_request(), required: true), responses: %{ 200 => @@ -141,7 +141,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Settings"], summary: "Update Notification Settings", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.update_notification_settings", + operationId: "PleromaAPI.UtilController.update_notification_settings", parameters: [ Operation.parameter( :block_from_strangers, @@ -173,7 +173,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Account credentials"], summary: "Disable Account", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.disable_account", + operationId: "PleromaAPI.UtilController.disable_account", parameters: [ Operation.parameter(:password, :query, :string, "Password") ], @@ -193,7 +193,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Account credentials"], summary: "Delete Account", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.delete_account", + operationId: "PleromaAPI.UtilController.delete_account", parameters: [ Operation.parameter(:password, :query, :string, "Password") ], @@ -212,7 +212,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do def captcha_operation do %Operation{ summary: "Get a captcha", - operationId: "UtilController.captcha", + operationId: "PleromaAPI.UtilController.captcha", tags: ["Others"], parameters: [], responses: %{ @@ -226,7 +226,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Account credentials"], summary: "Move account", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.move_account", + operationId: "PleromaAPI.UtilController.move_account", requestBody: request_body("Parameters", move_account_request(), required: true), responses: %{ 200 => @@ -262,7 +262,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Account credentials"], summary: "List account aliases", security: [%{"oAuth" => ["read:accounts"]}], - operationId: "UtilController.list_aliases", + operationId: "PleromaAPI.UtilController.list_aliases", responses: %{ 200 => Operation.response("Success", "application/json", %Schema{ @@ -286,7 +286,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Account credentials"], summary: "Add an alias to this account", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.add_alias", + operationId: "PleromaAPI.UtilController.add_alias", requestBody: request_body("Parameters", add_alias_request(), required: true), responses: %{ 200 => @@ -326,7 +326,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Account credentials"], summary: "Delete an alias from this account", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.delete_alias", + operationId: "PleromaAPI.UtilController.delete_alias", requestBody: request_body("Parameters", delete_alias_request(), required: true), responses: %{ 200 => @@ -366,7 +366,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Others"], summary: "Quick status check on the instance", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.healthcheck", + operationId: "PleromaAPI.UtilController.healthcheck", parameters: [], responses: %{ 200 => Operation.response("Healthy", "application/json", %Schema{type: :object}), @@ -376,52 +376,6 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do } end - def remote_subscribe_operation do - %Operation{ - tags: ["Remote interaction"], - summary: "Remote Subscribe", - operationId: "UtilController.remote_subscribe", - parameters: [], - responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})} - } - end - - def remote_interaction_operation do - %Operation{ - tags: ["Remote interaction"], - summary: "Remote interaction", - operationId: "UtilController.remote_interaction", - requestBody: request_body("Parameters", remote_interaction_request(), required: true), - responses: %{ - 200 => - Operation.response("Remote interaction URL", "application/json", %Schema{type: :object}) - } - } - end - - defp remote_interaction_request do - %Schema{ - title: "RemoteInteractionRequest", - description: "POST body for remote interaction", - type: :object, - required: [:ap_id, :profile], - properties: %{ - ap_id: %Schema{type: :string, description: "Profile or status ActivityPub ID"}, - profile: %Schema{type: :string, description: "Remote profile webfinger"} - } - } - end - - def show_subscribe_form_operation do - %Operation{ - tags: ["Remote interaction"], - summary: "Show remote subscribe form", - operationId: "UtilController.show_subscribe_form", - parameters: [], - responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})} - } - end - defp delete_account_request do %Schema{ title: "AccountDeleteRequest", diff --git a/lib/pleroma/web/api_spec/operations/remote_interaction_operation.ex b/lib/pleroma/web/api_spec/operations/remote_interaction_operation.ex new file mode 100644 index 000000000..54edbcf32 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/remote_interaction_operation.ex @@ -0,0 +1,99 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.RemoteInteractionOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def remote_interaction_operation do + %Operation{ + tags: ["Remote interaction"], + summary: "Remote interaction", + operationId: "RemoteInteractionController.remote_interaction", + requestBody: request_body("Parameters", remote_interaction_request(), required: true), + responses: %{ + 200 => + Operation.response("Remote interaction URL", "application/json", %Schema{type: :object}) + } + } + end + + defp remote_interaction_request do + %Schema{ + title: "RemoteInteractionRequest", + description: "POST body for remote interaction", + type: :object, + required: [:ap_id, :profile], + properties: %{ + ap_id: %Schema{type: :string, description: "Profile or status ActivityPub ID"}, + profile: %Schema{type: :string, description: "Remote profile webfinger"} + } + } + end + + def follow_operation do + %Operation{ + tags: ["Remote interaction"], + summary: "Display follow form", + operationId: "RemoteInteractionController.follow", + parameters: [], + responses: %{ + 200 => Operation.response("Web Page", "text/html", %Schema{type: :string}), + 302 => Operation.response("Redirect to the status page", nil, nil) + } + } + end + + def do_follow_operation do + %Operation{ + tags: ["Remote interaction"], + summary: "Perform follow activity", + operationId: "RemoteInteractionController.do_follow", + parameters: [], + responses: %{ + 200 => Operation.response("Web page", "text/html", %Schema{type: :string}), + 302 => Operation.response("Redirect to the account page", nil, nil) + } + } + end + + def authorize_interaction_operation do + %Operation{ + tags: ["Remote interaction"], + summary: "Authorize remote interaction", + operationId: "RemoteInteractionController.authorize_interaction", + parameters: [], + responses: %{ + 302 => Operation.response("Redirect to remote_interaction path", nil, nil) + } + } + end + + def show_subscribe_form_operation do + %Operation{ + tags: ["Remote interaction"], + summary: "Show remote subscribe form", + operationId: "RemoteInteractionController.show_subscribe_form", + parameters: [], + responses: %{200 => Operation.response("Web Page", "text/html", %Schema{type: :string})} + } + end + + def remote_subscribe_operation do + %Operation{ + tags: ["Remote interaction"], + summary: "Remote Subscribe", + operationId: "RemoteInteractionController.remote_subscribe", + parameters: [], + responses: %{200 => Operation.response("Web Page", "text/html", %Schema{type: :string})} + } + end +end diff --git a/lib/pleroma/web/api_spec/render_error.ex b/lib/pleroma/web/api_spec/render_error.ex index 3539af6e4..2ba76f250 100644 --- a/lib/pleroma/web/api_spec/render_error.ex +++ b/lib/pleroma/web/api_spec/render_error.ex @@ -17,11 +17,11 @@ defmodule Pleroma.Web.ApiSpec.RenderError do def call(conn, errors) do errors = Enum.map(errors, fn - %{name: nil, reason: :invalid_enum} = err -> - %OpenApiSpex.Cast.Error{err | name: err.value} + %OpenApiSpex.Cast.Error{name: nil, reason: :invalid_enum} = err -> + %{err | name: err.value} - %{name: nil} = err -> - %OpenApiSpex.Cast.Error{err | name: List.last(err.path)} + %OpenApiSpex.Cast.Error{name: nil} = err -> + %{err | name: List.last(err.path)} err -> err diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index 7d0b83afe..efdced316 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -21,6 +21,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do acct: %Schema{type: :string}, avatar_static: %Schema{type: :string, format: :uri}, avatar: %Schema{type: :string, format: :uri}, + avatar_description: %Schema{type: :string}, bot: %Schema{type: :boolean}, created_at: %Schema{type: :string, format: "date-time"}, display_name: %Schema{type: :string}, @@ -31,6 +32,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do following_count: %Schema{type: :integer}, header_static: %Schema{type: :string, format: :uri}, header: %Schema{type: :string, format: :uri}, + header_description: %Schema{type: :string}, id: FlakeID, locked: %Schema{type: :boolean}, note: %Schema{type: :string, format: :html}, @@ -111,8 +113,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do nullable: true, description: "Favicon image of the user's instance" }, - avatar_description: %Schema{type: :string}, - header_description: %Schema{type: :string} + avatar_description: %Schema{type: :string, deprecated: true}, + header_description: %Schema{type: :string, deprecated: true} } }, source: %Schema{ diff --git a/lib/pleroma/web/api_spec/schemas/account_relationship.ex b/lib/pleroma/web/api_spec/schemas/account_relationship.ex index 68219a099..247a94bb9 100644 --- a/lib/pleroma/web/api_spec/schemas/account_relationship.ex +++ b/lib/pleroma/web/api_spec/schemas/account_relationship.ex @@ -26,7 +26,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do requested: %Schema{type: :boolean}, showing_reblogs: %Schema{type: :boolean}, subscribing: %Schema{type: :boolean}, - notifying: %Schema{type: :boolean} + notifying: %Schema{type: :boolean}, + mute_expires_at: %Schema{type: :string, format: "date-time", nullable: true}, + block_expires_at: %Schema{type: :string, format: "date-time", nullable: true} }, example: %{ "blocked_by" => false, diff --git a/lib/pleroma/web/api_spec/schemas/bookmark_folder.ex b/lib/pleroma/web/api_spec/schemas/bookmark_folder.ex index e8b4f43b7..f5ce9e8a1 100644 --- a/lib/pleroma/web/api_spec/schemas/bookmark_folder.ex +++ b/lib/pleroma/web/api_spec/schemas/bookmark_folder.ex @@ -15,12 +15,18 @@ defmodule Pleroma.Web.ApiSpec.Schemas.BookmarkFolder do properties: %{ id: FlakeID, name: %Schema{type: :string, description: "Folder name"}, - emoji: %Schema{type: :string, description: "Folder emoji", nullable: true} + emoji: %Schema{type: :string, description: "Folder emoji", nullable: true}, + emoji_url: %Schema{ + type: :string, + description: "URL of the folder emoji if it's a custom emoji, null otherwise", + nullable: true + } }, example: %{ "id" => "9toJCu5YZW7O7gfvH6", "name" => "Read later", - "emoji" => nil + "emoji" => nil, + "emoji_url" => nil } }) end diff --git a/lib/pleroma/web/api_spec/schemas/list.ex b/lib/pleroma/web/api_spec/schemas/list.ex index e57de7917..5df674894 100644 --- a/lib/pleroma/web/api_spec/schemas/list.ex +++ b/lib/pleroma/web/api_spec/schemas/list.ex @@ -13,7 +13,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.List do type: :object, properties: %{ id: %Schema{type: :string, description: "The internal database ID of the list"}, - title: %Schema{type: :string, description: "The user-defined title of the list"} + title: %Schema{type: :string, description: "The user-defined title of the list"}, + exclusive: %Schema{ + type: :boolean, + description: "Whether members of the list should be removed from the “Home” feed" + } }, example: %{ "id" => "12249", diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 8e96ef5b6..cb9d521b3 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -222,8 +222,8 @@ defmodule Pleroma.Web.CommonAPI do with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id), object = %Object{} <- Object.normalize(activity, fetch: false), {_, nil} <- {:existing_announce, Utils.get_existing_announce(user.ap_id, object)}, - public = public_announce?(object, params), - {:ok, announce, _} <- Builder.announce(user, object, public: public), + visibility = announce_visibility(object, params), + {:ok, announce, _} <- Builder.announce(user, object, visibility: visibility), {:ok, activity, _} <- Pipeline.common_pipeline(announce, local: true) do {:ok, activity} else @@ -407,13 +407,11 @@ defmodule Pleroma.Web.CommonAPI do end end - defp public_announce?(_, %{visibility: visibility}) - when visibility in ~w{public unlisted private direct}, - do: visibility in ~w(public unlisted) + def announce_visibility(_, %{visibility: visibility}) + when visibility in ~w{public unlisted private direct local}, + do: visibility - defp public_announce?(object, _) do - Visibility.public?(object) - end + def announce_visibility(object, _), do: Visibility.get_visibility(object) @spec get_visibility(map(), map() | nil, Participation.t() | nil) :: {String.t() | nil, String.t() | nil} @@ -709,6 +707,22 @@ defmodule Pleroma.Web.CommonAPI do end end + def assign_report_to_account(activity_ids, user) when is_list(activity_ids) do + case Utils.assign_report_to_account(activity_ids, user) do + :ok -> {:ok, activity_ids} + _ -> {:error, dgettext("errors", "Could not assign account")} + end + end + + def assign_report_to_account(activity_id, user) do + with %Activity{} = activity <- Activity.get_by_id(activity_id) do + Utils.assign_report_to_account(activity, user) + else + nil -> {:error, :not_found} + _ -> {:error, dgettext("errors", "Could not assign account")} + end + end + @spec update_activity_scope(String.t(), map()) :: {:ok, any()} | {:error, any()} def update_activity_scope(activity_id, opts \\ %{}) do with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id), diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index c0b98508c..16489663a 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -88,7 +88,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do |> validate() end - defp listen_object(draft) do + defp listen_object(%__MODULE__{} = draft) do object = draft.params |> Map.take([:album, :artist, :title, :length]) @@ -99,34 +99,34 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do |> Map.put("cc", draft.cc) |> Map.put("actor", draft.user.ap_id) - %__MODULE__{draft | object: object} + %{draft | object: object} end - defp put_params(draft, params) do + defp put_params(%__MODULE__{} = draft, params) do params = Map.put_new(params, :in_reply_to_status_id, params[:in_reply_to_id]) - %__MODULE__{draft | params: params} + %{draft | params: params} end - defp status(%{params: %{status: status}} = draft) do - %__MODULE__{draft | status: String.trim(status)} + defp status(%__MODULE__{params: %{status: status}} = draft) do + %{draft | status: String.trim(status)} end - defp summary(%{params: params} = draft) do - %__MODULE__{draft | summary: Map.get(params, :spoiler_text, "")} + defp summary(%__MODULE__{params: params} = draft) do + %{draft | summary: Map.get(params, :spoiler_text, "")} end - defp full_payload(%{status: status, summary: summary} = draft) do + defp full_payload(%__MODULE__{status: status, summary: summary} = draft) do full_payload = String.trim(status <> summary) case Utils.validate_character_limit(full_payload, draft.attachments) do - :ok -> %__MODULE__{draft | full_payload: full_payload} + :ok -> %{draft | full_payload: full_payload} {:error, message} -> add_error(draft, message) end end - defp attachments(%{params: params} = draft) do + defp attachments(%__MODULE__{params: params} = draft) do attachments = Utils.attachments_from_ids(params, draft.user) - draft = %__MODULE__{draft | attachments: attachments} + draft = %{draft | attachments: attachments} case Utils.validate_attachments_count(attachments) do :ok -> draft @@ -134,9 +134,10 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do end end - defp in_reply_to(%{params: %{in_reply_to_status_id: ""}} = draft), do: draft + defp in_reply_to(%__MODULE__{params: %{in_reply_to_status_id: ""}} = draft), do: draft - defp in_reply_to(%{params: %{in_reply_to_status_id: id}} = draft) when is_binary(id) do + defp in_reply_to(%__MODULE__{params: %{in_reply_to_status_id: id}} = draft) + when is_binary(id) do # If a post was deleted all its activities (except the newly added Delete) are purged too, # thus lookup by Create db ID will yield nil just as if it never existed in the first place. # @@ -148,7 +149,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do with %Activity{} = activity <- Activity.get_by_id(id), true <- Visibility.visible_for_user?(activity, draft.user), {_, type} when type in ["Create", "Announce"] <- {:type, activity.data["type"]} do - %__MODULE__{draft | in_reply_to: activity} + %{draft | in_reply_to: activity} else nil -> add_error(draft, dgettext("errors", "Cannot reply to a deleted status")) @@ -166,40 +167,43 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do end end - defp in_reply_to(%{params: %{in_reply_to_status_id: %Activity{} = in_reply_to}} = draft) do - %__MODULE__{draft | in_reply_to: in_reply_to} + defp in_reply_to( + %__MODULE__{params: %{in_reply_to_status_id: %Activity{} = in_reply_to}} = draft + ) do + %{draft | in_reply_to: in_reply_to} end defp in_reply_to(draft), do: draft - defp quote_post(%{params: %{quoted_status_id: id}} = draft) when not_empty_string(id) do + defp quote_post(%__MODULE__{params: %{quoted_status_id: id}} = draft) + when not_empty_string(id) do case Activity.get_by_id_with_object(id) do %Activity{} = activity -> - %__MODULE__{draft | quote_post: activity} + %{draft | quote_post: activity} _ -> draft end end - defp quote_post(%{params: %{quote_id: id}} = draft) when not_empty_string(id) do + defp quote_post(%__MODULE__{params: %{quote_id: id}} = draft) when not_empty_string(id) do quote_post(%{draft | params: Map.put(draft.params, :quoted_status_id, id)}) end defp quote_post(draft), do: draft - defp in_reply_to_conversation(draft) do + defp in_reply_to_conversation(%__MODULE__{} = draft) do in_reply_to_conversation = Participation.get(draft.params[:in_reply_to_conversation_id]) - %__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation} + %{draft | in_reply_to_conversation: in_reply_to_conversation} end - defp visibility(%{params: params} = draft) do + defp visibility(%__MODULE__{params: params} = draft) do case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do {visibility, "direct"} when visibility != "direct" -> add_error(draft, dgettext("errors", "The message visibility must be direct")) {visibility, _} -> - %__MODULE__{draft | visibility: visibility} + %{draft | visibility: visibility} end end @@ -215,7 +219,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do false end - defp quoting_visibility(%{quote_post: %Activity{}} = draft) do + defp quoting_visibility(%__MODULE__{quote_post: %Activity{}} = draft) do with %Object{} = object <- Object.normalize(draft.quote_post, fetch: false), true <- can_quote?(draft, object, Visibility.get_visibility(object)) do draft @@ -226,24 +230,24 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do defp quoting_visibility(draft), do: draft - defp expires_at(draft) do + defp expires_at(%__MODULE__{} = draft) do case CommonAPI.check_expiry_date(draft.params[:expires_in]) do - {:ok, expires_at} -> %__MODULE__{draft | expires_at: expires_at} + {:ok, expires_at} -> %{draft | expires_at: expires_at} {:error, message} -> add_error(draft, message) end end - defp poll(draft) do + defp poll(%__MODULE__{} = draft) do case Utils.make_poll_data(draft.params) do {:ok, {poll, poll_emoji}} -> - %__MODULE__{draft | extra: poll, emoji: Map.merge(draft.emoji, poll_emoji)} + %{draft | extra: poll, emoji: Map.merge(draft.emoji, poll_emoji)} {:error, message} -> add_error(draft, message) end end - defp content(%{mentions: mentions} = draft) do + defp content(%__MODULE__{mentions: mentions} = draft) do {content_html, mentioned_users, tags} = Utils.make_content_html(draft) mentioned_ap_ids = @@ -254,25 +258,25 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do |> Kernel.++(mentioned_ap_ids) |> Utils.get_addressed_users(draft.params[:to]) - %__MODULE__{draft | content_html: content_html, mentions: mentions, tags: tags} + %{draft | content_html: content_html, mentions: mentions, tags: tags} end - defp to_and_cc(draft) do + defp to_and_cc(%__MODULE__{} = draft) do {to, cc} = Utils.get_to_and_cc(draft) - %__MODULE__{draft | to: to, cc: cc} + %{draft | to: to, cc: cc} end - defp context(draft) do + defp context(%__MODULE__{} = draft) do context = Utils.make_context(draft.in_reply_to, draft.in_reply_to_conversation) - %__MODULE__{draft | context: context} + %{draft | context: context} end - defp sensitive(draft) do + defp sensitive(%__MODULE__{} = draft) do sensitive = draft.params[:sensitive] - %__MODULE__{draft | sensitive: sensitive} + %{draft | sensitive: sensitive} end - defp language(draft) do + defp language(%__MODULE__{} = draft) do language = with language <- draft.params[:language], true <- good_locale_code?(language) do @@ -281,10 +285,10 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do _ -> LanguageDetector.detect(draft.content_html <> " " <> draft.summary) end - %__MODULE__{draft | language: language} + %{draft | language: language} end - defp object(draft) do + defp object(%__MODULE__{} = draft) do emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji) # Sometimes people create posts with subject containing emoji, @@ -325,15 +329,15 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do |> Map.put("generator", draft.params[:generator]) |> Map.put("language", draft.language) - %__MODULE__{draft | object: object} + %{draft | object: object} end - defp preview?(draft) do + defp preview?(%__MODULE__{} = draft) do preview? = Pleroma.Web.Utils.Params.truthy_param?(draft.params[:preview]) - %__MODULE__{draft | preview?: preview?} + %{draft | preview?: preview?} end - defp changes(draft) do + defp changes(%__MODULE__{} = draft) do direct? = draft.visibility == "direct" additional = %{"cc" => draft.cc, "directMessage" => direct?} @@ -353,14 +357,14 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do } |> Utils.maybe_add_list_data(draft.user, draft.visibility) - %__MODULE__{draft | changes: changes} + %{draft | changes: changes} end defp with_valid(%{valid?: true} = draft, func), do: func.(draft) defp with_valid(draft, _func), do: draft - defp add_error(draft, message) do - %__MODULE__{draft | valid?: false, errors: [message | draft.errors]} + defp add_error(%__MODULE__{} = draft, message) do + %{draft | valid?: false, errors: [message | draft.errors]} end defp validate(%{valid?: true} = draft), do: {:ok, draft} diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 91bf9c502..32572a721 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -75,48 +75,70 @@ defmodule Pleroma.Web.CommonAPI.Utils do {Enum.map(participation.recipients, & &1.ap_id), []} end - def get_to_and_cc(%{visibility: visibility} = draft) when visibility in ["public", "local"] do - to = - case visibility do - "public" -> [Pleroma.Constants.as_public() | draft.mentions] - "local" -> [Utils.as_local_public() | draft.mentions] + def get_to_and_cc(%{visibility: visibility} = draft) do + # If the OP is a DM already, add the implicit actor + mentions = + if visibility == "direct" && draft.in_reply_to && Visibility.direct?(draft.in_reply_to) do + Enum.uniq([draft.in_reply_to.data["actor"] | draft.mentions]) + else + draft.mentions end - cc = [draft.user.follower_address] - - if draft.in_reply_to do - {Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc} - else - {to, cc} - end + get_to_and_cc_for_visibility( + visibility, + draft.user.follower_address, + draft.in_reply_to && draft.in_reply_to.data["actor"], + mentions + ) end - def get_to_and_cc(%{visibility: "unlisted"} = draft) do - to = [draft.user.follower_address | draft.mentions] - cc = [Pleroma.Constants.as_public()] + def get_to_and_cc_for_visibility("public", follower_collection, parent_actor, mentions) do + scope_addr = Pleroma.Constants.as_public() - if draft.in_reply_to do - {Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc} - else - {to, cc} - end + to = + if parent_actor, + do: Enum.uniq([parent_actor, scope_addr | mentions]), + else: [scope_addr | mentions] + + {to, [follower_collection]} end - def get_to_and_cc(%{visibility: "private"} = draft) do - {to, cc} = get_to_and_cc(struct(draft, visibility: "direct")) - {[draft.user.follower_address | to], cc} + def get_to_and_cc_for_visibility("local", follower_collection, parent_actor, mentions) do + recipients = + if parent_actor, + do: Enum.uniq([parent_actor | mentions]), + else: mentions + + to = [ + Utils.as_local_public() + | Enum.filter(recipients, fn addr -> + String.starts_with?(addr, Pleroma.Web.Endpoint.url() <> "/") + end) + ] + + {to, [follower_collection]} end - def get_to_and_cc(%{visibility: "direct"} = draft) do - # If the OP is a DM already, add the implicit actor. - if draft.in_reply_to && Visibility.direct?(draft.in_reply_to) do - {Enum.uniq([draft.in_reply_to.data["actor"] | draft.mentions]), []} - else - {draft.mentions, []} - end + def get_to_and_cc_for_visibility("unlisted", follower_collection, parent_actor, mentions) do + to = + if parent_actor, + do: Enum.uniq([parent_actor, follower_collection | mentions]), + else: [follower_collection | mentions] + + {to, [Pleroma.Constants.as_public()]} end - def get_to_and_cc(%{visibility: {:list, _}, mentions: mentions}), do: {mentions, []} + def get_to_and_cc_for_visibility("private", follower_collection, _, mentions) do + {[follower_collection | mentions], []} + end + + def get_to_and_cc_for_visibility("direct", _, _, mentions) do + {mentions, []} + end + + def get_to_and_cc_for_visibility({:list, _}, _, _, mentions) do + {mentions, []} + end def get_addressed_users(_, to) when is_list(to) do User.get_ap_ids_by_nicknames(to) diff --git a/lib/pleroma/web/embed_controller.ex b/lib/pleroma/web/embed_controller.ex index 2ca4501a6..8420f17a5 100644 --- a/lib/pleroma/web/embed_controller.ex +++ b/lib/pleroma/web/embed_controller.ex @@ -20,6 +20,7 @@ defmodule Pleroma.Web.EmbedController do conn |> delete_resp_header("x-frame-options") |> delete_resp_header("content-security-policy") + |> put_layout({Pleroma.Web.LayoutView, :embed}) |> render("show.html", activity: activity, author: User.sanitize_html(author), diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index bab3c9fd0..a5c04a0c4 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -46,8 +46,10 @@ defmodule Pleroma.Web.Endpoint do plug(Pleroma.Web.Plugs.HTTPSecurityPlug) plug(Pleroma.Web.Plugs.UploadedMedia) - @static_cache_control "public, max-age=1209600" + @static_cache_control "public, max-age=1209600, immutable" @static_cache_disabled "public, no-cache" + # 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 # If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well @@ -64,6 +66,15 @@ defmodule Pleroma.Web.Endpoint do } ) + plug(Pleroma.Web.Plugs.FaviconPlug, + at: "/", + only: ["favicon.png"], + cache_control_for_etags: @favicon_cache_control, + headers: %{ + "cache-control" => @favicon_cache_control + } + ) + plug(Pleroma.Web.Plugs.InstanceStatic, at: "/", gzip: true, diff --git a/lib/pleroma/web/fallback/redirect_controller.ex b/lib/pleroma/web/fallback/redirect_controller.ex index 60fc15b9e..c7f80ad77 100644 --- a/lib/pleroma/web/fallback/redirect_controller.ex +++ b/lib/pleroma/web/fallback/redirect_controller.ex @@ -29,6 +29,20 @@ defmodule Pleroma.Web.Fallback.RedirectController do ) end + def live_dashboard(conn, %{"path" => path}) do + query_params = conn.query_string + + redirect_path = + if query_params == "" do + "/pleroma/live_dashboard/#{path}" + else + "/pleroma/live_dashboard/#{path}?#{query_params}" + end + + conn + |> redirect(to: redirect_path) + end + def redirector(conn, _params, code \\ 200) do {:ok, index_content} = File.read(index_file_path(conn)) diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 6dc731ed4..6d5851029 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -26,7 +26,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do alias Pleroma.Web.OAuth.OAuthController alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.RateLimiter - alias Pleroma.Web.TwitterAPI.TwitterAPI + alias Pleroma.Web.Registration alias Pleroma.Web.Utils.Params plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) @@ -111,8 +111,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do _params ) do with :ok <- validate_email_param(params), - :ok <- TwitterAPI.validate_captcha(app, params), - {:ok, user} <- TwitterAPI.register_user(params), + :ok <- Registration.validate_captcha(app, params), + {:ok, user} <- Registration.register_user(params), {_, {:ok, token}} <- {:login, OAuthController.login(user, app, app.scopes)} do OAuthController.after_token_exchange(conn, %{user: user, token: token}) diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex index fbb54a171..653b5fc29 100644 --- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do import Pleroma.Web.ControllerHelper, only: [json_response: 3] - alias Pleroma.Web.TwitterAPI.TwitterAPI + alias Pleroma.Web.Registration action_fallback(Pleroma.Web.MastodonAPI.FallbackController) @@ -17,7 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do def password_reset(conn, params) do nickname_or_email = params["email"] || params["nickname"] - TwitterAPI.password_reset(nickname_or_email) + Registration.password_reset(nickname_or_email) json_response(conn, :no_content, "") end diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex index 6eee55d1b..a15029d92 100644 --- a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex @@ -5,6 +5,10 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do use Pleroma.Web, :controller + import Pleroma.Web.ControllerHelper, + only: [add_link_headers: 2] + + alias Pleroma.Pagination alias Pleroma.User alias Pleroma.Web.CommonAPI alias Pleroma.Web.Plugs.OAuthScopesPlug @@ -24,10 +28,15 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.FollowRequestOperation @doc "GET /api/v1/follow_requests" - def index(%{assigns: %{user: followed}} = conn, _params) do - follow_requests = User.get_follow_requests(followed) + def index(%{assigns: %{user: followed}} = conn, params) do + follow_requests = + followed + |> User.get_follow_requests_query() + |> Pagination.fetch_paginated(params, :keyset, :follower) - render(conn, "index.json", for: followed, users: follow_requests, as: :user) + conn + |> add_link_headers(follow_requests) + |> render("index.json", for: followed, users: follow_requests, as: :user) end @doc "POST /api/v1/follow_requests/:id/authorize" diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex index 0f74c1dff..cf5bbf16e 100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do plug(Pleroma.Web.ApiSpec.CastAndValidate) - plug(:skip_auth when action in [:show, :show2, :peers]) + plug(:skip_auth) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.InstanceOperation @@ -31,6 +31,11 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do render(conn, "rules.json") end + @doc "GET /api/v1/instance/domain_blocks" + def domain_blocks(conn, _params) do + render(conn, "domain_blocks.json") + end + @doc "GET /api/v1/instance/translation_languages" def translation_languages(conn, _params) do render(conn, "translation_languages.json") diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex index 3bfc365a5..048012ae6 100644 --- a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex @@ -28,27 +28,27 @@ defmodule Pleroma.Web.MastodonAPI.ListController do # POST /api/v1/lists def create( - %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: %{title: title}}}} = + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: params}}} = conn, _ ) do - with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(title, user) do + with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(params, user) do render(conn, "show.json", list: list) end end - # GET /api/v1/lists/:idOB + # GET /api/v1/lists/:id def show(%{assigns: %{list: list}} = conn, _) do render(conn, "show.json", list: list) end # PUT /api/v1/lists/:id def update( - %{assigns: %{list: list}, private: %{open_api_spex: %{body_params: %{title: title}}}} = + %{assigns: %{list: list}, private: %{open_api_spex: %{body_params: params}}} = conn, _ ) do - with {:ok, list} <- Pleroma.List.rename(list, title) do + with {:ok, list} <- Pleroma.List.update(list, params) do render(conn, "show.json", list: list) end end diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index db2b61b3c..26e38aac8 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -81,10 +81,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action in [:pin, :unpin]) - # Note: scope not present in Mastodon: read:bookmarks plug(OAuthScopesPlug, %{scopes: ["read:bookmarks"]} when action == :bookmarks) - # Note: scope not present in Mastodon: write:bookmarks plug( OAuthScopesPlug, %{scopes: ["write:bookmarks"]} when action in [:bookmark, :unbookmark] diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 5ee74a80e..99a5b6957 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -45,6 +45,10 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do |> User.followed_hashtags() |> Enum.map(& &1.id) + excluded_list_members = + user + |> Pleroma.List.get_exclusive_list_members() + params = params |> Map.put(:type, ["Create", "Announce"]) @@ -58,7 +62,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do |> Map.delete(:local) activities = - [user.ap_id | User.following(user)] + [user.ap_id | User.following(user) -- excluded_list_members] |> ActivityPub.fetch_activities(params) |> Enum.reverse() diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 03a2fc55a..5386c5a6c 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do alias Pleroma.User alias Pleroma.UserNote alias Pleroma.UserRelationship + alias Pleroma.Utils.URIEncoding alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MediaProxy @@ -95,6 +96,24 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do followed_by = FollowingRelationship.following?(target, reading_user) following = FollowingRelationship.following?(reading_user, target) + blocking = + UserRelationship.exists?( + user_relationships, + :block, + reading_user, + target, + &User.blocks_user?(&1, &2) + ) + + muting = + UserRelationship.exists?( + user_relationships, + :mute, + reading_user, + target, + &User.mutes?(&1, &2) + ) + requested = cond do following -> false @@ -115,14 +134,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do id: to_string(target.id), following: following, followed_by: followed_by, - blocking: - UserRelationship.exists?( - user_relationships, - :block, - reading_user, - target, - &User.blocks_user?(&1, &2) - ), + blocking: blocking, blocked_by: UserRelationship.exists?( user_relationships, @@ -131,14 +143,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do reading_user, &User.blocks_user?(&1, &2) ), - muting: - UserRelationship.exists?( - user_relationships, - :mute, - reading_user, - target, - &User.mutes?(&1, &2) - ), + block_expires_at: nil, + muting: muting, muting_notifications: UserRelationship.exists?( user_relationships, @@ -147,6 +153,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do target, &User.muted_notifications?(&1, &2) ), + mute_expires_at: nil, subscribing: subscribing, notifying: subscribing, requested: requested, @@ -173,6 +180,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do &User.endorses?(&1, &2) ) } + |> maybe_put_mute_expires_at(target, reading_user, %{mutes: muting}) + |> maybe_put_block_expires_at(target, reading_user, %{blocks: blocking}) end def render("relationships.json", %{user: user, targets: targets} = opts) do @@ -238,7 +247,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do emojis = Enum.map(user.emoji, fn {shortcode, raw_url} -> - url = MediaProxy.url(raw_url) + url = + raw_url + |> encode_emoji_url() + |> MediaProxy.url() %{ shortcode: shortcode, @@ -288,8 +300,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do note: user.bio, url: user.uri || user.ap_id, avatar: avatar, + avatar_description: avatar_description, avatar_static: avatar_static, header: header, + header_description: header_description, header_static: header_static, emojis: emojis, fields: user.fields, @@ -339,8 +353,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do |> maybe_put_unread_conversation_count(user, opts[:for]) |> maybe_put_unread_notification_count(user, opts[:for]) |> maybe_put_email_address(user, opts[:for]) - |> maybe_put_mute_expires_at(user, opts[:for], opts) - |> maybe_put_block_expires_at(user, opts[:for], opts) + |> maybe_put_mute_expires_at(user, opts[:for], opts, relationship) + |> maybe_put_block_expires_at(user, opts[:for], opts, relationship) |> maybe_show_birthday(user, opts[:for]) end @@ -356,8 +370,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do %User{id: user_id} ) do count = - User.get_follow_requests(user) - |> length() + user + |> User.get_follow_requests_query() + |> Pleroma.Repo.aggregate(:count) data |> Kernel.put_in([:follow_requests_count], count) @@ -467,25 +482,47 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do defp maybe_put_email_address(data, _, _), do: data - defp maybe_put_mute_expires_at(data, %User{} = user, target, %{mutes: true}) do + defp maybe_put_mute_expires_at(data, target, user, opts, relationship \\ nil) + + defp maybe_put_mute_expires_at(data, _target, _user, %{mutes: true}, %{ + mute_expires_at: mute_expires_at + }) do + Map.put(data, :mute_expires_at, mute_expires_at) + end + + defp maybe_put_mute_expires_at(data, %User{} = target, user, %{mutes: true}, _relationship) do Map.put( data, :mute_expires_at, - UserRelationship.get_mute_expire_date(target, user) + UserRelationship.get_mute_expire_date(user, target) ) end - defp maybe_put_mute_expires_at(data, _, _, _), do: data + defp maybe_put_mute_expires_at(data, _, _, _, _), do: data - defp maybe_put_block_expires_at(data, %User{} = user, target, %{blocks: true}) do + defp maybe_put_block_expires_at(data, target, user, opts, relationship \\ nil) + + defp maybe_put_block_expires_at(data, _target, _user, %{blocks: true}, %{ + block_expires_at: block_expires_at + }) do + Map.put(data, :block_expires_at, block_expires_at) + end + + defp maybe_put_block_expires_at( + data, + %User{} = target, + %User{} = user, + %{blocks: true}, + _relationship + ) do Map.put( data, :block_expires_at, - UserRelationship.get_block_expire_date(target, user) + UserRelationship.get_block_expire_date(user, target) ) end - defp maybe_put_block_expires_at(data, _, _, _), do: data + defp maybe_put_block_expires_at(data, _, _, _, _), do: data defp maybe_show_birthday(data, %User{id: user_id} = user, %User{id: user_id}) do data @@ -511,4 +548,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do # See https://git.pleroma.social/pleroma/pleroma-meta/-/issues/14 user.actor_type == "Service" || user.actor_type == "Group" end + + defp encode_emoji_url(nil), do: nil + defp encode_emoji_url("http" <> _ = url), do: URIEncoding.encode_url(url) + + defp encode_emoji_url("/" <> _ = path), + do: URIEncoding.encode_url(path, bypass_parse: true, bypass_decode: true) + + defp encode_emoji_url(path) when is_binary(path), + do: URIEncoding.encode_url(path, bypass_parse: true, bypass_decode: true) end diff --git a/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex b/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex index cd59ab946..6ef48e98a 100644 --- a/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex +++ b/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex @@ -6,14 +6,13 @@ defmodule Pleroma.Web.MastodonAPI.CustomEmojiView do use Pleroma.Web, :view alias Pleroma.Emoji - alias Pleroma.Web.Endpoint def render("index.json", %{custom_emojis: custom_emojis}) do render_many(custom_emojis, __MODULE__, "show.json") end def render("show.json", %{custom_emoji: {shortcode, %Emoji{file: relative_url, tags: tags}}}) do - url = Endpoint.url() |> URI.merge(relative_url) |> to_string() + url = Emoji.local_url(relative_url) %{ "shortcode" => shortcode, diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 57372248f..d64ce4fb5 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -5,11 +5,18 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do use Pleroma.Web, :view + import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1] + alias Pleroma.Config alias Pleroma.Web.ActivityPub.MRF @mastodon_api_level "2.7.2" + @block_severities %{ + federated_timeline_removal: "silence", + reject: "suspend" + } + def render("show.json", _) do instance = Config.get(:instance) @@ -90,6 +97,53 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do } end + def render("domain_blocks.json", _) do + if Config.get([:mrf, :transparency]) do + exclusions = Config.get([:mrf, :transparency_exclusions]) |> MRF.instance_list_from_tuples() + + domain_blocks = + Config.get(:mrf_simple) + |> Enum.map(fn {rule, instances} -> + instances + |> Enum.map(fn + {host, reason} when not_empty_string(host) and not_empty_string(reason) -> + {host, reason} + + {host, _reason} when not_empty_string(host) -> + {host, ""} + + host when not_empty_string(host) -> + {host, ""} + + _ -> + nil + end) + |> Enum.reject(&is_nil/1) + |> Enum.reject(fn {host, _} -> + host in exclusions or not Map.has_key?(@block_severities, rule) + end) + |> Enum.map(fn {host, reason} -> + domain_block = %{ + domain: host, + digest: :crypto.hash(:sha256, host) |> Base.encode16(case: :lower), + severity: Map.get(@block_severities, rule) + } + + if not_empty_string(reason) do + Map.put(domain_block, :comment, reason) + else + domain_block + end + end) + end) + |> List.flatten() + + domain_blocks + else + [] + end + end + def render("translation_languages.json", _) do with true <- Pleroma.Language.Translation.configured?(), {:ok, languages} <- Pleroma.Language.Translation.languages_matrix() do @@ -249,6 +303,15 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do defp configuration2 do configuration() |> put_in([:accounts, :max_pinned_statuses], Config.get([:instance, :max_pinned_statuses], 0)) + |> put_in([:accounts, :max_profile_fields], Config.get([:instance, :max_account_fields])) + |> put_in( + [:accounts, :profile_field_name_limit], + Config.get([:instance, :account_field_name_length]) + ) + |> put_in( + [:accounts, :profile_field_value_limit], + Config.get([:instance, :account_field_value_length]) + ) |> put_in([:statuses, :characters_reserved_per_url], 0) |> Map.merge(%{ urls: %{ diff --git a/lib/pleroma/web/mastodon_api/views/list_view.ex b/lib/pleroma/web/mastodon_api/views/list_view.ex index a7ae7c5f7..740f458d4 100644 --- a/lib/pleroma/web/mastodon_api/views/list_view.ex +++ b/lib/pleroma/web/mastodon_api/views/list_view.ex @@ -13,7 +13,8 @@ defmodule Pleroma.Web.MastodonAPI.ListView do def render("show.json", %{list: list}) do %{ id: to_string(list.id), - title: list.title + title: list.title, + exclusive: list.exclusive } end end diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex index 1e3c9f36d..3b4271227 100644 --- a/lib/pleroma/web/mastodon_api/views/poll_view.ex +++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex @@ -75,6 +75,8 @@ defmodule Pleroma.Web.MastodonAPI.PollView do length(voters) end + defp voters_count(%{data: %{"votersCount" => voters}}), do: voters + defp voters_count(_), do: 0 defp voted_and_own_votes(%{object: object} = params, options) do diff --git a/lib/pleroma/web/o_auth/app.ex b/lib/pleroma/web/o_auth/app.ex index 7661c2566..a2841b2bb 100644 --- a/lib/pleroma/web/o_auth/app.ex +++ b/lib/pleroma/web/o_auth/app.ex @@ -31,9 +31,32 @@ defmodule Pleroma.Web.OAuth.App do @spec changeset(t(), map()) :: Ecto.Changeset.t() def changeset(struct, params) do + params = normalize_redirect_uris_param(params) + cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted, :user_id]) end + defp normalize_redirect_uris_param(%{} = params) do + case params do + %{redirect_uris: redirect_uris} when is_list(redirect_uris) -> + Map.put(params, :redirect_uris, normalize_redirect_uris(redirect_uris)) + + %{"redirect_uris" => redirect_uris} when is_list(redirect_uris) -> + Map.put(params, "redirect_uris", normalize_redirect_uris(redirect_uris)) + + _ -> + params + end + end + + defp normalize_redirect_uris(redirect_uris) when is_list(redirect_uris) do + redirect_uris + |> Enum.filter(&is_binary/1) + |> Enum.map(&String.trim/1) + |> Enum.reject(&(&1 == "")) + |> Enum.join("\n") + end + @spec register_changeset(t(), map()) :: Ecto.Changeset.t() def register_changeset(struct, params \\ %{}) do changeset = diff --git a/lib/pleroma/web/twitter_api/controllers/password_controller.ex b/lib/pleroma/web/o_auth/password_controller.ex similarity index 90% rename from lib/pleroma/web/twitter_api/controllers/password_controller.ex rename to lib/pleroma/web/o_auth/password_controller.ex index e5482de9d..b209e7564 100644 --- a/lib/pleroma/web/twitter_api/controllers/password_controller.ex +++ b/lib/pleroma/web/o_auth/password_controller.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.PasswordController do +defmodule Pleroma.Web.OAuth.PasswordController do @moduledoc """ The module contains functions for password reset. """ @@ -16,7 +16,7 @@ defmodule Pleroma.Web.TwitterAPI.PasswordController do alias Pleroma.PasswordResetToken alias Pleroma.Repo alias Pleroma.User - alias Pleroma.Web.TwitterAPI.TwitterAPI + alias Pleroma.Web.Registration plug(Pleroma.Web.Plugs.RateLimiter, [name: :request] when action == :request) @@ -24,7 +24,7 @@ defmodule Pleroma.Web.TwitterAPI.PasswordController do def request(conn, params) do nickname_or_email = params["email"] || params["nickname"] - TwitterAPI.password_reset(nickname_or_email) + Registration.password_reset(nickname_or_email) json_response(conn, :no_content, "") end diff --git a/lib/pleroma/web/twitter_api/views/password_view.ex b/lib/pleroma/web/o_auth/password_view.ex similarity index 83% rename from lib/pleroma/web/twitter_api/views/password_view.ex rename to lib/pleroma/web/o_auth/password_view.ex index 55790941f..0b85c76e8 100644 --- a/lib/pleroma/web/twitter_api/views/password_view.ex +++ b/lib/pleroma/web/o_auth/password_view.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.PasswordView do +defmodule Pleroma.Web.OAuth.PasswordView do use Pleroma.Web, :view import Phoenix.HTML.Form alias Pleroma.Web.Gettext diff --git a/lib/pleroma/web/twitter_api/controller.ex b/lib/pleroma/web/o_auth/token_controller.ex similarity index 94% rename from lib/pleroma/web/twitter_api/controller.ex rename to lib/pleroma/web/o_auth/token_controller.ex index 6db3d6067..d2313a9cc 100644 --- a/lib/pleroma/web/twitter_api/controller.ex +++ b/lib/pleroma/web/o_auth/token_controller.ex @@ -2,13 +2,13 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.Controller do +defmodule Pleroma.Web.OAuth.TokenController do use Pleroma.Web, :controller alias Pleroma.User alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.OAuth.TokenView alias Pleroma.Web.Plugs.OAuthScopesPlug - alias Pleroma.Web.TwitterAPI.TokenView require Logger diff --git a/lib/pleroma/web/twitter_api/views/token_view.ex b/lib/pleroma/web/o_auth/token_view.ex similarity index 81% rename from lib/pleroma/web/twitter_api/views/token_view.ex rename to lib/pleroma/web/o_auth/token_view.ex index 36776ce3b..f9894399a 100644 --- a/lib/pleroma/web/twitter_api/views/token_view.ex +++ b/lib/pleroma/web/o_auth/token_view.ex @@ -2,12 +2,12 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.TokenView do +defmodule Pleroma.Web.OAuth.TokenView do use Pleroma.Web, :view def render("index.json", %{tokens: tokens}) do tokens - |> render_many(Pleroma.Web.TwitterAPI.TokenView, "show.json") + |> render_many(Pleroma.Web.OAuth.TokenView, "show.json") |> Enum.filter(&Enum.any?/1) end diff --git a/lib/pleroma/web/pleroma_api/controllers/bookmark_folder_controller.ex b/lib/pleroma/web/pleroma_api/controllers/bookmark_folder_controller.ex index 6d6e2e940..55f323a16 100644 --- a/lib/pleroma/web/pleroma_api/controllers/bookmark_folder_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/bookmark_folder_controller.ex @@ -10,10 +10,8 @@ defmodule Pleroma.Web.PleromaAPI.BookmarkFolderController do plug(Pleroma.Web.ApiSpec.CastAndValidate) - # Note: scope not present in Mastodon: read:bookmarks plug(OAuthScopesPlug, %{scopes: ["read:bookmarks"]} when action == :index) - # Note: scope not present in Mastodon: write:bookmarks plug( OAuthScopesPlug, %{scopes: ["write:bookmarks"]} when action in [:create, :update, :delete] diff --git a/lib/pleroma/web/pleroma_api/controllers/follow_request_controller.ex b/lib/pleroma/web/pleroma_api/controllers/follow_request_controller.ex index 656d477da..c72b6941b 100644 --- a/lib/pleroma/web/pleroma_api/controllers/follow_request_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/follow_request_controller.ex @@ -5,6 +5,10 @@ defmodule Pleroma.Web.PleromaAPI.FollowRequestController do use Pleroma.Web, :controller + import Pleroma.Web.ControllerHelper, + only: [add_link_headers: 2] + + alias Pleroma.Pagination alias Pleroma.User alias Pleroma.Web.Plugs.OAuthScopesPlug @@ -17,11 +21,15 @@ defmodule Pleroma.Web.PleromaAPI.FollowRequestController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaFollowRequestOperation @doc "GET /api/v1/pleroma/outgoing_follow_requests" - def outgoing(%{assigns: %{user: follower}} = conn, _params) do - follow_requests = User.get_outgoing_follow_requests(follower) + def outgoing(%{assigns: %{user: follower}} = conn, params) do + follow_requests = + follower + |> User.get_outgoing_follow_requests_query() + |> Pagination.fetch_paginated(params, :keyset, :following) conn |> put_view(Pleroma.Web.MastodonAPI.FollowRequestView) + |> add_link_headers(follow_requests) |> render("index.json", for: follower, users: follow_requests, as: :user) end end diff --git a/lib/pleroma/web/pleroma_api/controllers/password_controller.ex b/lib/pleroma/web/pleroma_api/controllers/password_controller.ex new file mode 100644 index 000000000..444477245 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/password_controller.ex @@ -0,0 +1,52 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.PasswordController do + @moduledoc """ + The module contains functions for password reset. + """ + + use Pleroma.Web, :controller + + require Logger + + import Pleroma.Web.ControllerHelper, only: [json_response: 3] + + alias Pleroma.PasswordResetToken + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.Registration + + plug(Pleroma.Web.Plugs.RateLimiter, [name: :request] when action == :request) + + @doc "POST /auth/password" + def request(conn, params) do + nickname_or_email = params["email"] || params["nickname"] + + Registration.password_reset(nickname_or_email) + + json_response(conn, :no_content, "") + end + + def reset(conn, %{"token" => token}) do + with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), + false <- PasswordResetToken.expired?(token), + %User{} = user <- User.get_cached_by_id(token.user_id) do + render(conn, "reset.html", %{ + token: token, + user: user + }) + else + _e -> render(conn, "invalid_token.html") + end + end + + def do_reset(conn, %{"data" => data}) do + with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do + render(conn, "reset_success.html") + else + _e -> render(conn, "reset_failed.html") + end + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/token_controller.ex b/lib/pleroma/web/pleroma_api/controllers/token_controller.ex new file mode 100644 index 000000000..10eeb907e --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/token_controller.ex @@ -0,0 +1,59 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.TokenController do + use Pleroma.Web, :controller + + alias Pleroma.User + alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.PleromaAPI.TokenView + alias Pleroma.Web.Plugs.OAuthScopesPlug + + require Logger + + plug(:skip_auth when action == :confirm_email) + plug(:skip_plug, OAuthScopesPlug when action in [:oauth_tokens, :revoke_token]) + + action_fallback(:errors) + + def confirm_email(conn, %{"user_id" => uid, "token" => token}) do + with %User{} = user <- User.get_cached_by_id(uid), + true <- user.local and !user.is_confirmed and user.confirmation_token == token, + {:ok, _} <- User.confirm(user) do + redirect(conn, to: "/") + end + end + + def oauth_tokens(%{assigns: %{user: user}} = conn, _params) do + with oauth_tokens <- Token.get_user_tokens(user) do + conn + |> put_view(TokenView) + |> render("index.json", %{tokens: oauth_tokens}) + end + end + + def revoke_token(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do + Token.delete_user_token(user, id) + + json_reply(conn, 201, "") + end + + defp errors(conn, {:param_cast, _}) do + conn + |> put_status(400) + |> json("Invalid parameters") + end + + defp errors(conn, _) do + conn + |> put_status(500) + |> json("Something went wrong") + end + + defp json_reply(conn, status, json) do + conn + |> put_resp_content_type("application/json") + |> send_resp(status, json) + end +end diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/pleroma_api/controllers/util_controller.ex similarity index 63% rename from lib/pleroma/web/twitter_api/controllers/util_controller.ex rename to lib/pleroma/web/pleroma_api/controllers/util_controller.ex index aeafa195d..882e9476e 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/util_controller.ex @@ -2,33 +2,22 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.UtilController do +defmodule Pleroma.Web.PleromaAPI.UtilController do use Pleroma.Web, :controller require Logger - alias Pleroma.Activity alias Pleroma.Config alias Pleroma.Emoji alias Pleroma.Healthcheck alias Pleroma.User + alias Pleroma.Utils.URIEncoding alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.Auth.WrapperAuthenticator, as: Authenticator alias Pleroma.Web.CommonAPI alias Pleroma.Web.Plugs.OAuthScopesPlug - alias Pleroma.Web.WebFinger - plug( - Pleroma.Web.ApiSpec.CastAndValidate, - [replace_params: false] - when action != :remote_subscribe and action != :show_subscribe_form - ) - - plug( - Pleroma.Web.Plugs.FederatingPlug - when action == :remote_subscribe - when action == :show_subscribe_form - ) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug( OAuthScopesPlug, @@ -53,125 +42,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do ] ) - defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TwitterUtilOperation - - def show_subscribe_form(conn, %{"nickname" => nick}) do - with %User{} = user <- User.get_cached_by_nickname(nick), - avatar = User.avatar_url(user) do - conn - |> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false}) - else - _e -> - render(conn, "subscribe.html", %{ - nickname: nick, - avatar: nil, - error: - Pleroma.Web.Gettext.dpgettext( - "static_pages", - "remote follow error message - user not found", - "Could not find user" - ) - }) - end - end - - def show_subscribe_form(conn, %{"status_id" => id}) do - with %Activity{} = activity <- Activity.get_by_id(id), - {:ok, ap_id} <- get_ap_id(activity), - %User{} = user <- User.get_cached_by_ap_id(activity.actor), - avatar = User.avatar_url(user) do - conn - |> render("status_interact.html", %{ - status_link: ap_id, - status_id: id, - nickname: user.nickname, - avatar: avatar, - error: false - }) - else - _e -> - render(conn, "status_interact.html", %{ - status_id: id, - avatar: nil, - error: - Pleroma.Web.Gettext.dpgettext( - "static_pages", - "status interact error message - status not found", - "Could not find status" - ) - }) - end - end - - def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do - show_subscribe_form(conn, %{"nickname" => nick}) - end - - def remote_subscribe(conn, %{"status_id" => id, "profile" => _}) do - show_subscribe_form(conn, %{"status_id" => id}) - end - - def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profile}}) do - with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile), - %User{ap_id: ap_id} <- User.get_cached_by_nickname(nick) do - conn - |> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id)) - else - _e -> - render(conn, "subscribe.html", %{ - nickname: nick, - avatar: nil, - error: - Pleroma.Web.Gettext.dpgettext( - "static_pages", - "remote follow error message - unknown error", - "Something went wrong." - ) - }) - end - end - - def remote_subscribe(conn, %{"status" => %{"status_id" => id, "profile" => profile}}) do - with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile), - %Activity{} = activity <- Activity.get_by_id(id), - {:ok, ap_id} <- get_ap_id(activity) do - conn - |> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id)) - else - _e -> - render(conn, "status_interact.html", %{ - status_id: id, - avatar: nil, - error: - Pleroma.Web.Gettext.dpgettext( - "static_pages", - "status interact error message - unknown error", - "Something went wrong." - ) - }) - end - end - - def remote_interaction( - %{private: %{open_api_spex: %{body_params: %{ap_id: ap_id, profile: profile}}}} = conn, - _params - ) do - with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile) do - conn - |> json(%{url: String.replace(template, "{uri}", ap_id)}) - else - _e -> json(conn, %{error: "Couldn't find user"}) - end - end - - defp get_ap_id(activity) do - object = Pleroma.Object.normalize(activity, fetch: false) - - case object do - %{data: %{"id" => ap_id}} -> {:ok, ap_id} - _ -> {:no_ap_id, nil} - end - end + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaUtilOperation def frontend_configurations(conn, _params) do render(conn, "frontend_configurations.json") @@ -180,12 +51,22 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do def emoji(conn, _params) do emoji = Enum.reduce(Emoji.get_all(), %{}, fn {code, %Emoji{file: file, tags: tags}}, acc -> + file = encode_emoji_url(file) Map.put(acc, code, %{image_url: file, tags: tags}) end) json(conn, emoji) end + defp encode_emoji_url(nil), do: nil + defp encode_emoji_url("http" <> _ = url), do: URIEncoding.encode_url(url) + + defp encode_emoji_url("/" <> _ = path), + do: URIEncoding.encode_url(path, bypass_parse: true, bypass_decode: true) + + defp encode_emoji_url(path) when is_binary(path), + do: URIEncoding.encode_url(path, bypass_parse: true, bypass_decode: true) + def update_notification_settings(%{assigns: %{user: user}} = conn, params) do with {:ok, _} <- User.update_notification_settings(user, params) do json(conn, %{status: "success"}) diff --git a/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex b/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex index 12decb816..ddffa25d3 100644 --- a/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex +++ b/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex @@ -7,14 +7,15 @@ defmodule Pleroma.Web.PleromaAPI.BookmarkFolderView do alias Pleroma.BookmarkFolder alias Pleroma.Emoji - alias Pleroma.Web.Endpoint def render("show.json", %{folder: %BookmarkFolder{} = folder}) do + {emoji, emoji_url} = get_emoji(folder.emoji) + %{ id: folder.id |> to_string(), name: folder.name, - emoji: folder.emoji, - emoji_url: get_emoji_url(folder.emoji) + emoji: emoji, + emoji_url: emoji_url } end @@ -22,20 +23,15 @@ defmodule Pleroma.Web.PleromaAPI.BookmarkFolderView do render_many(folders, __MODULE__, "show.json", Map.delete(opts, :folders)) end - defp get_emoji_url(nil) do - nil - end + defp get_emoji(nil), do: {nil, nil} - defp get_emoji_url(emoji) do + defp get_emoji(emoji) do if Emoji.unicode?(emoji) do - nil + {emoji, nil} else - emoji = Emoji.get(emoji) - - if emoji != nil do - Endpoint.url() |> URI.merge(emoji.file) |> to_string() - else - nil + case Emoji.get(emoji) do + nil -> {nil, nil} + emoji_data -> {emoji, Emoji.local_url(emoji_data.file)} end end end diff --git a/lib/pleroma/web/pleroma_api/views/util_view.ex b/lib/pleroma/web/pleroma_api/views/util_view.ex new file mode 100644 index 000000000..b5e07c006 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/views/util_view.ex @@ -0,0 +1,13 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.UtilView do + use Pleroma.Web, :view + alias Pleroma.Config + + def render("frontend_configurations.json", _) do + Config.get(:frontend_configurations, %{}) + |> Enum.into(%{}) + end +end diff --git a/lib/pleroma/web/plugs/favicon_plug.ex b/lib/pleroma/web/plugs/favicon_plug.ex new file mode 100644 index 000000000..e2e1f1adb --- /dev/null +++ b/lib/pleroma/web/plugs/favicon_plug.ex @@ -0,0 +1,71 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2026 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.FaviconPlug do + @behaviour Plug + + @moduledoc """ + This is a shim to call `Plug.Static` but with runtime `from` configuration for instance favicon. + + Serves default or custom favicon.png with cacheable cache-control. + """ + + import Plug.Conn, only: [put_resp_header: 3, send_resp: 3, halt: 1] + + require Logger + + def init(opts) do + opts + |> Keyword.put(:from, "__unconfigured_favicon_static_plug") + |> Plug.Static.init() + end + + def call(%{request_path: "/favicon.png"} = conn, opts) do + case find_favicon_dir() do + {:ok, dir} -> + call_static(conn, opts, dir) + + :error -> + # Favicon should always be available and this should never occur. + # If it does, halt the pipeline before having unintended side-effects. + Logger.error("No favicon.png found! Is the default favicon deleted?") + + conn + |> send_resp(404, "Not found") + |> halt() + end + end + + def call(conn, _) do + conn + end + + defp find_favicon_dir do + instance_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static") + instance_path = Path.join(instance_dir, "favicon.png") + + priv_dir = Application.app_dir(:pleroma, "priv/static") + priv_path = Path.join(priv_dir, "favicon.png") + + cond do + File.exists?(instance_path) -> {:ok, instance_dir} + File.exists?(priv_path) -> {:ok, priv_dir} + true -> :error + end + end + + defp call_static(conn, opts, from) do + opts = + opts + |> Map.put(:from, from) + |> Map.put(:content_types, false) + + conn = set_content_type(conn) + Plug.Static.call(conn, opts) + end + + defp set_content_type(conn) do + put_resp_header(conn, "content-type", "image/png") + end +end diff --git a/lib/pleroma/web/plugs/instance_static.ex b/lib/pleroma/web/plugs/instance_static.ex index d2a674d39..948188287 100644 --- a/lib/pleroma/web/plugs/instance_static.ex +++ b/lib/pleroma/web/plugs/instance_static.ex @@ -4,7 +4,7 @@ defmodule Pleroma.Web.Plugs.InstanceStatic do require Pleroma.Constants - import Plug.Conn, only: [put_resp_header: 3] + import Plug.Conn, only: [put_resp_header: 3, put_status: 2, send_resp: 3, halt: 1] @moduledoc """ This is a shim to call `Plug.Static` but with runtime `from` configuration. @@ -51,10 +51,37 @@ defmodule Pleroma.Web.Plugs.InstanceStatic do |> Map.put(:from, from) |> Map.put(:content_types, false) - conn = set_content_type(conn, conn.request_path) + conn = + conn + |> set_content_type(conn.request_path) + |> Plug.Static.call(opts) - # Call Plug.Static with our sanitized content-type - Plug.Static.call(conn, opts) + if conn.halted do + conn + else + path = String.trim_leading(conn.request_path, "/") + + if not File.exists?(file_path(path)) do + conn + |> put_status(:not_found) + |> send_404() + |> halt() + else + conn + end + end + end + + defp send_404(conn) do + if String.ends_with?(String.downcase(conn.request_path), ".json") do + conn + |> put_resp_header("content-type", "application/json") + |> send_resp(404, Jason.encode!(%{error: "not found"})) + else + conn + |> put_resp_header("content-type", "text/plain") + |> send_resp(404, "Not found") + end end defp set_content_type(conn, "/emoji/" <> filepath) do diff --git a/lib/pleroma/web/plugs/rate_limiter.ex b/lib/pleroma/web/plugs/rate_limiter.ex index aa79dbf6b..0ebb73bbc 100644 --- a/lib/pleroma/web/plugs/rate_limiter.ex +++ b/lib/pleroma/web/plugs/rate_limiter.ex @@ -67,6 +67,8 @@ defmodule Pleroma.Web.Plugs.RateLimiter do import Plug.Conn alias Pleroma.Config + alias Pleroma.Config.Holder + alias Pleroma.EctoType.Config.RateLimit alias Pleroma.User alias Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor @@ -143,7 +145,7 @@ defmodule Pleroma.Web.Plugs.RateLimiter do def action_settings(plug_opts) do with limiter_name when is_atom(limiter_name) <- plug_opts[:name], - limits when not is_nil(limits) <- Config.get([:rate_limit, limiter_name]) do + {:ok, limits} <- fetch_and_normalize_limits(limiter_name) do bucket_name_root = Keyword.get(plug_opts, :bucket_name, limiter_name) %{ @@ -151,6 +153,72 @@ defmodule Pleroma.Web.Plugs.RateLimiter do limits: limits, opts: plug_opts } + else + :disabled -> nil + end + end + + defp fetch_and_normalize_limits(limiter_name) do + limits = Config.get([:rate_limit, limiter_name]) + + case normalize_limits(limits) do + {:ok, limits} -> + {:ok, limits} + + :disabled -> + :disabled + + :error -> + default_limits = + Holder.default_config(:pleroma, :rate_limit) + |> get_default_limits(limiter_name) + + case normalize_limits(default_limits) do + {:ok, normalized_limits} -> + warn_invalid_limits_once(limiter_name, limits) + {:ok, normalized_limits} + + _ -> + warn_invalid_limits_once(limiter_name, limits) + :disabled + end + end + end + + defp get_default_limits(%{} = rate_limit, limiter_name), do: Map.get(rate_limit, limiter_name) + + defp get_default_limits(rate_limit, limiter_name) when is_list(rate_limit) do + if Keyword.keyword?(rate_limit) do + Keyword.get(rate_limit, limiter_name) + else + nil + end + end + + defp get_default_limits(_, _), do: nil + + @invalid_limits_warned_key {__MODULE__, :invalid_limits_warned} + + defp warn_invalid_limits_once(limiter_name, limits) do + warned = :persistent_term.get(@invalid_limits_warned_key, MapSet.new()) + + if MapSet.member?(warned, limiter_name) do + :ok + else + :persistent_term.put(@invalid_limits_warned_key, MapSet.put(warned, limiter_name)) + + Logger.warning( + "Invalid rate limiter config for #{inspect(limiter_name)}: #{inspect(limits)}. Falling back to defaults or disabling this limiter." + ) + end + end + + defp normalize_limits(nil), do: :disabled + + defp normalize_limits(limits) do + case RateLimit.cast(limits) do + {:ok, normalized_limits} -> {:ok, normalized_limits} + :error -> :error end end diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex index abacf965b..5a3ea12aa 100644 --- a/lib/pleroma/web/plugs/uploaded_media.ex +++ b/lib/pleroma/web/plugs/uploaded_media.ex @@ -17,7 +17,7 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do # no slashes @path "media" - @default_cache_control_header "public, max-age=1209600" + @default_cache_control_header "public, max-age=1209600, immutable" def init(_opts) do static_plug_opts = diff --git a/lib/pleroma/web/preload/providers/instance.ex b/lib/pleroma/web/preload/providers/instance.ex index 6183f7b70..f2a60393f 100644 --- a/lib/pleroma/web/preload/providers/instance.ex +++ b/lib/pleroma/web/preload/providers/instance.ex @@ -5,9 +5,9 @@ defmodule Pleroma.Web.Preload.Providers.Instance do alias Pleroma.Web.MastodonAPI.InstanceView alias Pleroma.Web.Nodeinfo.Nodeinfo + alias Pleroma.Web.PleromaAPI.UtilView alias Pleroma.Web.Plugs.InstanceStatic alias Pleroma.Web.Preload.Providers.Provider - alias Pleroma.Web.TwitterAPI.UtilView @behaviour Provider @instance_url "/api/v1/instance" diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/registration.ex similarity index 98% rename from lib/pleroma/web/twitter_api/twitter_api.ex rename to lib/pleroma/web/registration.ex index ef2eb75f4..df71300ce 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/registration.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.TwitterAPI do +defmodule Pleroma.Web.Registration do import Pleroma.Web.Gettext alias Pleroma.Emails.Mailer diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/remote_interaction/remote_interaction_controller.ex similarity index 57% rename from lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex rename to lib/pleroma/web/remote_interaction/remote_interaction_controller.ex index 38ebc8c5d..90f15cdb6 100644 --- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex +++ b/lib/pleroma/web/remote_interaction/remote_interaction_controller.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do +defmodule Pleroma.Web.RemoteInteraction.RemoteInteractionController do use Pleroma.Web, :controller require Logger @@ -14,18 +14,27 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do alias Pleroma.Web.Auth.TOTPAuthenticator alias Pleroma.Web.Auth.WrapperAuthenticator alias Pleroma.Web.CommonAPI + alias Pleroma.Web.WebFinger @status_types ["Article", "Event", "Note", "Video", "Page", "Question"] + plug( + Pleroma.Web.ApiSpec.CastAndValidate, + [replace_params: false] + when action == :remote_interaction + ) + plug(Pleroma.Web.Plugs.FederatingPlug) # Note: follower can submit the form (with password auth) not being signed in (having no token) plug( Pleroma.Web.Plugs.OAuthScopesPlug, %{fallback: :proceed_unauthenticated, scopes: ["follow", "write:follows"]} - when action in [:do_follow] + when action == :do_follow ) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.RemoteInteractionOperation + # GET /ostatus_subscribe # def follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do @@ -125,7 +134,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do # def authorize_interaction(conn, %{"uri" => uri}) do conn - |> redirect(to: Routes.remote_follow_path(conn, :follow, %{acct: uri})) + |> redirect(to: Routes.remote_interaction_path(conn, :follow, %{acct: uri})) end defp handle_follow_error(conn, {:mfa_token, followee, _} = _) do @@ -162,4 +171,122 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do Logger.debug("Remote follow failed with error #{inspect(error)}") render(conn, "followed.html", %{error: "Something went wrong."}) end + + def show_subscribe_form(conn, %{"nickname" => nick}) do + with %User{} = user <- User.get_cached_by_nickname(nick), + avatar = User.avatar_url(user) do + conn + |> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false}) + else + _e -> + render(conn, "subscribe.html", %{ + nickname: nick, + avatar: nil, + error: + Pleroma.Web.Gettext.dpgettext( + "static_pages", + "remote follow error message - user not found", + "Could not find user" + ) + }) + end + end + + def show_subscribe_form(conn, %{"status_id" => id}) do + with %Activity{} = activity <- Activity.get_by_id(id), + {:ok, ap_id} <- get_ap_id(activity), + %User{} = user <- User.get_cached_by_ap_id(activity.actor), + avatar = User.avatar_url(user) do + conn + |> render("status_interact.html", %{ + status_link: ap_id, + status_id: id, + nickname: user.nickname, + avatar: avatar, + error: false + }) + else + _e -> + render(conn, "status_interact.html", %{ + status_id: id, + avatar: nil, + error: + Pleroma.Web.Gettext.dpgettext( + "static_pages", + "status interact error message - status not found", + "Could not find status" + ) + }) + end + end + + def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do + show_subscribe_form(conn, %{"nickname" => nick}) + end + + def remote_subscribe(conn, %{"status_id" => id, "profile" => _}) do + show_subscribe_form(conn, %{"status_id" => id}) + end + + def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profile}}) do + with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile), + %User{ap_id: ap_id} <- User.get_cached_by_nickname(nick) do + conn + |> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id)) + else + _e -> + render(conn, "subscribe.html", %{ + nickname: nick, + avatar: nil, + error: + Pleroma.Web.Gettext.dpgettext( + "static_pages", + "remote follow error message - unknown error", + "Something went wrong." + ) + }) + end + end + + def remote_subscribe(conn, %{"status" => %{"status_id" => id, "profile" => profile}}) do + with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile), + %Activity{} = activity <- Activity.get_by_id(id), + {:ok, ap_id} <- get_ap_id(activity) do + conn + |> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id)) + else + _e -> + render(conn, "status_interact.html", %{ + status_id: id, + avatar: nil, + error: + Pleroma.Web.Gettext.dpgettext( + "static_pages", + "status interact error message - unknown error", + "Something went wrong." + ) + }) + end + end + + def remote_interaction( + %{private: %{open_api_spex: %{body_params: %{ap_id: ap_id, profile: profile}}}} = conn, + _params + ) do + with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile) do + conn + |> json(%{url: String.replace(template, "{uri}", ap_id)}) + else + _e -> json(conn, %{error: "Couldn't find user"}) + end + end + + defp get_ap_id(activity) do + object = Pleroma.Object.normalize(activity, fetch: false) + + case object do + %{data: %{"id" => ap_id}} -> {:ok, ap_id} + _ -> {:no_ap_id, nil} + end + end end diff --git a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex b/lib/pleroma/web/remote_interaction/remote_interaction_view.ex similarity index 75% rename from lib/pleroma/web/twitter_api/views/remote_follow_view.ex rename to lib/pleroma/web/remote_interaction/remote_interaction_view.ex index 8902261b0..6e7f40749 100644 --- a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex +++ b/lib/pleroma/web/remote_interaction/remote_interaction_view.ex @@ -2,9 +2,11 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.RemoteFollowView do +defmodule Pleroma.Web.RemoteInteraction.RemoteInteractionView do use Pleroma.Web, :view + import Phoenix.HTML import Phoenix.HTML.Form + import Phoenix.HTML.Link alias Pleroma.Web.Gettext def avatar_url(user) do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index da9626147..c91ca8c97 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.Router do use Pleroma.Web, :router import Phoenix.LiveDashboard.Router + import Oban.Web.Router pipeline :accepts_html do plug(:accepts, ["html"]) @@ -225,23 +226,28 @@ defmodule Pleroma.Web.Router do plug(Pleroma.Web.Plugs.StaticFEPlug) end - scope "/api/v1/pleroma", Pleroma.Web.TwitterAPI do + scope "/api/v1/pleroma", Pleroma.Web.OAuth do pipe_through(:pleroma_api) get("/password_reset/:token", PasswordController, :reset, as: :reset_password) post("/password_reset", PasswordController, :do_reset, as: :reset_password) - get("/emoji", UtilController, :emoji) - get("/captcha", UtilController, :captcha) - get("/healthcheck", UtilController, :healthcheck) - post("/remote_interaction", UtilController, :remote_interaction) end scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do pipe_through(:pleroma_api) + get("/emoji", UtilController, :emoji) + get("/captcha", UtilController, :captcha) + get("/healthcheck", UtilController, :healthcheck) get("/federation_status", InstancesController, :show) end + scope "/api/v1/pleroma", Pleroma.Web.RemoteInteraction do + pipe_through(:pleroma_api) + + post("/remote_interaction", RemoteInteractionController, :remote_interaction) + end + scope "/api/v1/pleroma", Pleroma.Web do pipe_through(:pleroma_api) post("/uploader_callback/:upload_path", UploaderController, :callback) @@ -394,6 +400,7 @@ defmodule Pleroma.Web.Router do get("/reports", ReportController, :index) get("/reports/:id", ReportController, :show) patch("/reports", ReportController, :update) + post("/reports/assign_account", ReportController, :assign_account) post("/reports/:id/notes", ReportController, :notes_create) delete("/reports/:report_id/notes/:id", ReportController, :notes_delete) end @@ -482,18 +489,18 @@ defmodule Pleroma.Web.Router do end end - scope "/", Pleroma.Web.TwitterAPI do + scope "/", Pleroma.Web.RemoteInteraction do pipe_through(:pleroma_html) - post("/main/ostatus", UtilController, :remote_subscribe) - get("/main/ostatus", UtilController, :show_subscribe_form) - get("/ostatus_subscribe", RemoteFollowController, :follow) - post("/ostatus_subscribe", RemoteFollowController, :do_follow) + post("/main/ostatus", RemoteInteractionController, :remote_subscribe) + get("/main/ostatus", RemoteInteractionController, :show_subscribe_form) + get("/ostatus_subscribe", RemoteInteractionController, :follow) + post("/ostatus_subscribe", RemoteInteractionController, :do_follow) - get("/authorize_interaction", RemoteFollowController, :authorize_interaction) + get("/authorize_interaction", RemoteInteractionController, :authorize_interaction) end - scope "/api/pleroma", Pleroma.Web.TwitterAPI do + scope "/api/pleroma", Pleroma.Web.PleromaAPI do pipe_through(:authenticated_api) post("/change_email", UtilController, :change_email) @@ -813,6 +820,7 @@ defmodule Pleroma.Web.Router do get("/instance", InstanceController, :show) get("/instance/peers", InstanceController, :peers) get("/instance/rules", InstanceController, :rules) + get("/instance/domain_blocks", InstanceController, :domain_blocks) get("/instance/translation_languages", InstanceController, :translation_languages) get("/statuses", StatusController, :index) @@ -850,7 +858,7 @@ defmodule Pleroma.Web.Router do scope "/api", Pleroma.Web do pipe_through(:config) - get("/pleroma/frontend_configurations", TwitterAPI.UtilController, :frontend_configurations) + get("/pleroma/frontend_configurations", PleromaAPI.UtilController, :frontend_configurations) end scope "/api", Pleroma.Web do @@ -858,7 +866,7 @@ defmodule Pleroma.Web.Router do get( "/account/confirm_email/:user_id/:token", - TwitterAPI.Controller, + OAuth.TokenController, :confirm_email, as: :confirm_email ) @@ -870,11 +878,11 @@ defmodule Pleroma.Web.Router do get("/openapi", OpenApiSpex.Plug.RenderSpec, []) end - scope "/api", Pleroma.Web, as: :authenticated_twitter_api do + scope "/api", Pleroma.Web, as: :authenticated_pleroma_api do pipe_through(:authenticated_api) - get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens) - delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token) + get("/oauth_tokens", OAuth.TokenController, :oauth_tokens) + delete("/oauth_tokens/:id", OAuth.TokenController, :revoke_token) end scope "/", Pleroma.Web do @@ -1023,7 +1031,9 @@ defmodule Pleroma.Web.Router do scope "/", Pleroma.Web do pipe_through(:pleroma_html) - post("/auth/password", TwitterAPI.PasswordController, :request) + post("/auth/password", OAuth.PasswordController, :request) + + get("/embed/:id", EmbedController, :show) end scope "/proxy/", Pleroma.Web do @@ -1043,7 +1053,8 @@ defmodule Pleroma.Web.Router do scope "/" do pipe_through([:pleroma_html, :authenticate, :require_admin]) - live_dashboard("/phoenix/live_dashboard", additional_pages: [oban: Oban.LiveDashboard]) + live_dashboard("/pleroma/live_dashboard", additional_pages: [oban: Oban.LiveDashboard]) + oban_dashboard("/pleroma/oban") end # Test-only routes needed to test action dispatching and plug chain execution @@ -1084,14 +1095,20 @@ defmodule Pleroma.Web.Router do get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta) match(:*, "/api/pleroma/*path", LegacyPleromaApiRerouterPlug, []) get("/api/*path", RedirectController, :api_not_implemented) + get("/phoenix/live_dashboard/*path", RedirectController, :live_dashboard) get("/*path", RedirectController, :redirector_with_preload) options("/*path", RedirectController, :empty) end + # /pleroma/{phoenix,oban}/* need to get filtered out from api routes for frontend configuration + # to not drop admin overrides for /pleroma/admin. + @non_api_routes ["/pleroma/live_dashboard", "/pleroma/oban"] + def get_api_routes do Phoenix.Router.routes(__MODULE__) |> Enum.reject(fn r -> r.plug == Pleroma.Web.Fallback.RedirectController end) + |> Enum.reject(fn r -> String.starts_with?(r.path, @non_api_routes) end) |> Enum.map(fn r -> r.path |> String.split("/", trim: true) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index aba42ee78..120e94751 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -300,7 +300,7 @@ defmodule Pleroma.Web.Streamer do end) end - defp do_stream("user", item) do + defp do_stream("user", %Activity{} = item) do Logger.debug("Trying to push to users") recipient_topics = diff --git a/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex b/lib/pleroma/web/templates/o_auth/password/invalid_token.html.eex similarity index 100% rename from lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex rename to lib/pleroma/web/templates/o_auth/password/invalid_token.html.eex diff --git a/lib/pleroma/web/templates/twitter_api/password/reset.html.eex b/lib/pleroma/web/templates/o_auth/password/reset.html.eex similarity index 100% rename from lib/pleroma/web/templates/twitter_api/password/reset.html.eex rename to lib/pleroma/web/templates/o_auth/password/reset.html.eex diff --git a/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex b/lib/pleroma/web/templates/o_auth/password/reset_failed.html.eex similarity index 100% rename from lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex rename to lib/pleroma/web/templates/o_auth/password/reset_failed.html.eex diff --git a/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex b/lib/pleroma/web/templates/o_auth/password/reset_success.html.eex similarity index 100% rename from lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex rename to lib/pleroma/web/templates/o_auth/password/reset_success.html.eex diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex b/lib/pleroma/web/templates/remote_interaction/remote_interaction/follow.html.eex similarity index 83% rename from lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex rename to lib/pleroma/web/templates/remote_interaction/remote_interaction/follow.html.eex index e2d251fac..00cc7d383 100644 --- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex +++ b/lib/pleroma/web/templates/remote_interaction/remote_interaction/follow.html.eex @@ -4,7 +4,7 @@

<%= Gettext.dpgettext("static_pages", "remote follow header", "Remote follow") %>

<%= @followee.nickname %>

- <%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "user"], fn f -> %> + <%= form_for @conn, Routes.remote_interaction_path(@conn, :do_follow), [as: "user"], fn f -> %> <%= hidden_input f, :id, value: @followee.id %> <%= submit Gettext.dpgettext("static_pages", "remote follow authorization button", "Authorize") %> <% end %> diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex b/lib/pleroma/web/templates/remote_interaction/remote_interaction/follow_login.html.eex similarity index 88% rename from lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex rename to lib/pleroma/web/templates/remote_interaction/remote_interaction/follow_login.html.eex index 26340a906..e5fe720e8 100644 --- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex +++ b/lib/pleroma/web/templates/remote_interaction/remote_interaction/follow_login.html.eex @@ -4,7 +4,7 @@

<%= Gettext.dpgettext("static_pages", "remote follow header, need login", "Log in to follow") %>

<%= @followee.nickname %>

-<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %> +<%= form_for @conn, Routes.remote_interaction_path(@conn, :do_follow), [as: "authorization"], fn f -> %> <%= text_input f, :name, placeholder: Gettext.dpgettext("static_pages", "placeholder text for username entry", "Username"), required: true, autocomplete: "username" %>
<%= password_input f, :password, placeholder: Gettext.dpgettext("static_pages", "placeholder text for password entry", "Password"), required: true, autocomplete: "password" %> diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex b/lib/pleroma/web/templates/remote_interaction/remote_interaction/follow_mfa.html.eex similarity index 86% rename from lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex rename to lib/pleroma/web/templates/remote_interaction/remote_interaction/follow_mfa.html.eex index 638212c1e..e5f26f104 100644 --- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex +++ b/lib/pleroma/web/templates/remote_interaction/remote_interaction/follow_mfa.html.eex @@ -4,7 +4,7 @@

<%= Gettext.dpgettext("static_pages", "remote follow mfa header", "Two-factor authentication") %>

<%= @followee.nickname %>

-<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "mfa"], fn f -> %> +<%= form_for @conn, Routes.remote_interaction_path(@conn, :do_follow), [as: "mfa"], fn f -> %> <%= text_input f, :code, placeholder: Gettext.dpgettext("static_pages", "placeholder text for auth code entry", "Authentication code"), required: true %>
<%= hidden_input f, :id, value: @followee.id %> diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex b/lib/pleroma/web/templates/remote_interaction/remote_interaction/followed.html.eex similarity index 100% rename from lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex rename to lib/pleroma/web/templates/remote_interaction/remote_interaction/followed.html.eex diff --git a/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex b/lib/pleroma/web/templates/remote_interaction/remote_interaction/status_interact.html.eex similarity index 88% rename from lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex rename to lib/pleroma/web/templates/remote_interaction/remote_interaction/status_interact.html.eex index d77174967..b3d590186 100644 --- a/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex +++ b/lib/pleroma/web/templates/remote_interaction/remote_interaction/status_interact.html.eex @@ -2,7 +2,7 @@

<%= Gettext.dpgettext("static_pages", "status interact error", "Error: %{error}", error: @error) %>

<% else %>

<%= raw Gettext.dpgettext("static_pages", "status interact header", "Interacting with %{nickname}'s %{status_link}", nickname: safe_to_string(html_escape(@nickname)), status_link: safe_to_string(link(Gettext.dpgettext("static_pages", "status interact header - status link text", "status"), to: @status_link))) %>

- <%= form_for @conn, Routes.util_path(@conn, :remote_subscribe), [as: "status"], fn f -> %> + <%= form_for @conn, Routes.remote_interaction_path(@conn, :remote_subscribe), [as: "status"], fn f -> %> <%= hidden_input f, :status_id, value: @status_id %> <%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %> <%= submit Gettext.dpgettext("static_pages", "status interact authorization button", "Interact") %> diff --git a/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex b/lib/pleroma/web/templates/remote_interaction/remote_interaction/subscribe.html.eex similarity index 85% rename from lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex rename to lib/pleroma/web/templates/remote_interaction/remote_interaction/subscribe.html.eex index 848660f26..7e1dd723c 100644 --- a/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex +++ b/lib/pleroma/web/templates/remote_interaction/remote_interaction/subscribe.html.eex @@ -2,7 +2,7 @@

<%= Gettext.dpgettext("static_pages", "remote follow error", "Error: %{error}", error: @error) %>

<% else %>

<%= Gettext.dpgettext("static_pages", "remote follow header", "Remotely follow %{nickname}", nickname: @nickname) %>

- <%= form_for @conn, Routes.util_path(@conn, :remote_subscribe), [as: "user"], fn f -> %> + <%= form_for @conn, Routes.remote_interaction_path(@conn, :remote_subscribe), [as: "user"], fn f -> %> <%= hidden_input f, :nickname, value: @nickname %> <%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %> <%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for following with a remote account", "Follow") %> diff --git a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex b/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex index a14ca305e..85bfd7b3a 100644 --- a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex +++ b/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex @@ -2,7 +2,7 @@

<%= link instance_name(), to: "/" %>

-
+ diff --git a/lib/pleroma/web/twitter_api/views/util_view.ex b/lib/pleroma/web/twitter_api/views/util_view.ex deleted file mode 100644 index 31b7c0c0c..000000000 --- a/lib/pleroma/web/twitter_api/views/util_view.ex +++ /dev/null @@ -1,31 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.TwitterAPI.UtilView do - use Pleroma.Web, :view - import Phoenix.HTML - import Phoenix.HTML.Form - import Phoenix.HTML.Link - alias Pleroma.Config - alias Pleroma.Web.Endpoint - alias Pleroma.Web.Gettext - - def status_net_config(instance) do - """ - - - #{Keyword.get(instance, :name)} - #{Endpoint.url()} - #{Keyword.get(instance, :limit)} - #{!Keyword.get(instance, :registrations_open)} - - - """ - end - - def render("frontend_configurations.json", _) do - Config.get(:frontend_configurations, %{}) - |> Enum.into(%{}) - end -end diff --git a/lib/pleroma/workers/cron/digest_emails_worker.ex b/lib/pleroma/workers/cron/digest_emails_worker.ex index b50b52a7b..5cb13f5f9 100644 --- a/lib/pleroma/workers/cron/digest_emails_worker.ex +++ b/lib/pleroma/workers/cron/digest_emails_worker.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Workers.Cron.DigestEmailsWorker do The worker to send digest emails. """ - use Oban.Worker, queue: "mailer" + use Oban.Worker, queue: :background alias Pleroma.Config alias Pleroma.Emails diff --git a/mix.exs b/mix.exs index 9e7ce7d4e..5da2ca657 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("2.10.0"), + version: version("2.10.1"), elixir: "~> 1.15", elixirc_paths: elixirc_paths(Mix.env()), compilers: Mix.compilers(), @@ -114,7 +114,7 @@ defmodule Pleroma.Mixfile do if Version.match?(System.version(), "<1.15.0-rc.0") do [] else - [{:logger_backends, "~> 1.0"}] + [{:logger_backends, "~> 1.0.0"}] end end @@ -125,60 +125,61 @@ defmodule Pleroma.Mixfile do [ {:phoenix, git: "https://github.com/feld/phoenix", branch: "v1.7.14-websocket-headers", override: true}, - {:phoenix_ecto, "~> 4.4"}, - {:ecto_sql, "~> 3.10"}, + {:phoenix_ecto, "~> 4.6"}, + {:ecto_sql, "~> 3.13"}, {:ecto_enum, "~> 1.4"}, - {:postgrex, ">= 0.20.0"}, + {:postgrex, ">= 0.21.1"}, {:phoenix_html, "~> 3.3"}, - {:phoenix_live_view, "~> 0.19.0"}, - {:phoenix_live_dashboard, "~> 0.8.0"}, + {:phoenix_live_view, "~> 1.1.19"}, + {:phoenix_live_dashboard, "~> 0.8.7"}, {:telemetry_metrics, "~> 0.6"}, - {:telemetry_poller, "~> 1.0"}, - {:tzdata, "~> 1.0.3"}, + {:telemetry_poller, "~> 1.3"}, + {:tzdata, "~> 1.0.5"}, {:plug_cowboy, "~> 2.7"}, - {:oban, "~> 2.19.0"}, + {:oban, "~> 2.19.4"}, {:oban_plugins_lazarus, git: "https://git.pleroma.social/pleroma/elixir-libraries/oban_plugins_lazarus.git", ref: "e49fc355baaf0e435208bf5f534d31e26e897711"}, - {:gettext, "~> 0.20"}, - {:bcrypt_elixir, "~> 2.2"}, + {:oban_web, "~> 2.11"}, + {:gettext, "~> 0.24"}, + {:bcrypt_elixir, "~> 2.3"}, {:trailing_format_plug, "~> 0.0.7"}, - {:fast_sanitize, "~> 0.2.0"}, + {:fast_sanitize, "~> 0.2.3"}, {:html_entities, "~> 0.5", override: true}, {:calendar, "~> 1.0"}, - {:cachex, "~> 3.2"}, - {:tesla, "~> 1.11"}, + {:cachex, "~> 3.6"}, + {:tesla, "~> 1.15"}, {:castore, "~> 1.0"}, {:cowlib, "~> 2.15"}, {:gun, "~> 2.2"}, - {:finch, "~> 0.15"}, - {:jason, "~> 1.2"}, - {:mogrify, "~> 0.9.0", override: "true"}, - {:ex_aws, "~> 2.1.6"}, - {:ex_aws_s3, "~> 2.0"}, - {:sweet_xml, "~> 0.7.2"}, + {:finch, "~> 0.20"}, + {:jason, "~> 1.4"}, + {:mogrify, "~> 0.9.3", override: true}, + {:ex_aws, "~> 2.1.9"}, + {:ex_aws_s3, "~> 2.5"}, + {:sweet_xml, "~> 0.7.5"}, {:earmark, "1.4.46"}, {:bbcode_pleroma, "~> 0.2.0"}, {:cors_plug, "~> 2.0"}, {:web_push_encryption, "~> 0.3.1"}, - {:swoosh, "~> 1.16.9"}, - {:phoenix_swoosh, "~> 1.1"}, - {:gen_smtp, "~> 0.13"}, - {:mua, "~> 0.2.0"}, - {:mail, "~> 0.3.0"}, - {:ex_syslogger, "~> 1.4"}, - {:floki, "~> 0.35"}, - {:timex, "~> 3.6"}, - {:ueberauth, "~> 0.4"}, + {:swoosh, "~> 1.16.12"}, + {:phoenix_swoosh, "~> 1.2"}, + {:gen_smtp, "~> 0.15"}, + {:mua, "~> 0.2.4"}, + {:mail, "~> 0.3.1"}, + {:ex_syslogger, "~> 1.5"}, + {:floki, "~> 0.38"}, + {:timex, "~> 3.7"}, + {:ueberauth, "~> 0.10"}, {:linkify, "~> 0.5.3"}, {:http_signatures, "~> 0.1.2"}, {:telemetry, "~> 1.0.0", override: true}, {:poolboy, "~> 1.5"}, {:prom_ex, "~> 1.9"}, {:recon, "~> 2.5"}, - {:joken, "~> 2.0"}, + {:joken, "~> 2.6"}, {:pot, "~> 1.0"}, - {:ex_const, "~> 0.2"}, + {:ex_const, "~> 0.3"}, {:plug_static_index_html, "~> 1.0.0"}, {:flake_id, "~> 0.1.0"}, {:concurrent_limiter, "~> 0.1.1"}, @@ -189,31 +190,31 @@ defmodule Pleroma.Mixfile do git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", ref: "e7b7cc34cc16b383461b966484c297e4ec9aeef6"}, {:restarter, path: "./restarter"}, - {:majic, "~> 1.0"}, - {:open_api_spex, "~> 3.16"}, + {:majic, "~> 1.1"}, + {:open_api_spex, "~> 3.22"}, {:ecto_psql_extras, "~> 0.8"}, - {:vix, "~> 0.26.0"}, - {:elixir_make, "~> 0.7.7", override: true}, + {:vix, "~> 0.36"}, + {:elixir_make, "~> 0.7.8", override: true}, {:blurhash, "~> 0.1.0", hex: :rinpatch_blurhash}, {:exile, "~> 0.10.0"}, - {:bandit, "~> 1.5.2"}, - {:websock_adapter, "~> 0.5.6"}, + {:bandit, "~> 1.10"}, + {:websock_adapter, "~> 0.5.8"}, {:oban_live_dashboard, "~> 0.1.1"}, {:multipart, "~> 0.4.0", optional: true}, - {:argon2_elixir, "~> 4.0"}, + {:argon2_elixir, "~> 4.1"}, ## dev & test {:phoenix_live_reload, "~> 1.3.3", only: :dev}, - {:poison, "~> 3.0", only: :test}, - {:ex_doc, "~> 0.22", only: :dev, runtime: false}, - {:ex_machina, "~> 2.4", only: :test}, + {:poison, "~> 3.1", only: :test}, + {:ex_doc, "~> 0.38", only: :dev, runtime: false}, + {:ex_machina, "~> 2.8", only: :test}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, - {:mock, "~> 0.3.5", only: :test}, + {:mock, "~> 0.3.9", only: :test}, {:covertool, "~> 2.0", only: :test}, - {:hackney, "~> 1.18.0", override: true}, - {:mox, "~> 1.0", only: :test}, + {:hackney, "~> 1.20.1", override: true}, + {:mox, "~> 1.2", only: :test}, {:websockex, "~> 0.4.3", only: :test}, - {:benchee, "~> 1.0", only: :benchmark}, + {:benchee, "~> 1.4", only: :benchmark}, {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false} ] ++ oauth_deps() ++ logger_deps() end @@ -230,7 +231,7 @@ defmodule Pleroma.Mixfile do "ecto.rollback": ["pleroma.ecto.rollback"], "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], "ecto.reset": ["ecto.drop", "ecto.setup"], - test: ["ecto.create --quiet", "ecto.migrate", "test --warnings-as-errors"], + test: ["ecto.create --quiet", "pleroma.ecto.migrate --quiet", "test --warnings-as-errors"], docs: ["pleroma.docs", "docs"], analyze: ["credo --strict --only=warnings,todo,fixme,consistency,readability"], copyright: &add_copyright/1, diff --git a/mix.lock b/mix.lock index 3599f62b4..dd5f65f11 100644 --- a/mix.lock +++ b/mix.lock @@ -1,7 +1,7 @@ %{ "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"}, - "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"}, "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"}, @@ -59,7 +59,7 @@ "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"}, "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.18.2", "d7ff544ddae5e1cb49e9cf7fa4e356d7f41b283989a1c304bfc47a8cc1cf966f", [: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", "af94d5c9f97857db257090a4a10e5426ecb6f4918aa5cc666798566ae14b65fd"}, + "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"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "http_signatures": {:hex, :http_signatures, "0.1.2", "ed1cc7043abcf5bb4f30d68fb7bad9d618ec1a45c4ff6c023664e78b67d9c406", [:mix], [], "hexpm", "f08aa9ac121829dae109d608d83c84b940ef2f183ae50f2dd1e9a8bc619d8be7"}, @@ -80,7 +80,7 @@ "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "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"}, "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"}, @@ -95,7 +95,9 @@ "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, "oban": {:hex, :oban, "2.19.4", "045adb10db1161dceb75c254782f97cdc6596e7044af456a59decb6d06da73c1", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5fcc6219e6464525b808d97add17896e724131f498444a292071bf8991c99f97"}, "oban_live_dashboard": {:hex, :oban_live_dashboard, "0.1.1", "8aa4ceaf381c818f7d5c8185cc59942b8ac82ef0cf559881aacf8d3f8ac7bdd3", [:mix], [{:oban, "~> 2.15", [hex: :oban, repo: "hexpm", optional: false]}, {:phoenix_live_dashboard, "~> 0.7", [hex: :phoenix_live_dashboard, repo: "hexpm", optional: false]}], "hexpm", "16dc4ce9c9a95aa2e655e35ed4e675652994a8def61731a18af85e230e1caa63"}, + "oban_met": {:hex, :oban_met, "1.0.5", "bb633ab06448dab2ef9194f6688d33b3d07fc3f2ad793a1a08f4dfbb2cc9fe50", [:mix], [{:oban, "~> 2.19", [hex: :oban, repo: "hexpm", optional: false]}], "hexpm", "64664d50805bbfd3903aeada1f3c39634652a87844797ee400b0bcc95a28f5ea"}, "oban_plugins_lazarus": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/oban_plugins_lazarus.git", "e49fc355baaf0e435208bf5f534d31e26e897711", [ref: "e49fc355baaf0e435208bf5f534d31e26e897711"]}, + "oban_web": {:hex, :oban_web, "2.11.6", "53933cb4253c4d9f1098ee311c06f07935259f0e564dcf2d66bae4cc98e317fe", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, "~> 2.19", [hex: :oban, repo: "hexpm", optional: false]}, {:oban_met, "~> 1.0", [hex: :oban_met, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "576d94b705688c313694c2c114ca21aa0f8f2ad1b9ca45c052c5ba316d3e8d10"}, "octo_fetch": {:hex, :octo_fetch, "0.4.0", "074b5ecbc08be10b05b27e9db08bc20a3060142769436242702931c418695b19", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "cf8be6f40cd519d7000bb4e84adcf661c32e59369ca2827c4e20042eda7a7fc6"}, "open_api_spex": {:hex, :open_api_spex, "3.22.0", "fbf90dc82681dc042a4ee79853c8e989efbba73d9e87439085daf849bbf8bc20", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0 or ~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "dd751ddbdd709bb4a5313e9a24530da6e66594773c7242a0c2592cbd9f589063"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, @@ -105,12 +107,12 @@ "phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "0.19.5", "6e730595e8e9b8c5da230a814e557768828fd8dfeeb90377d2d8dbb52d4ec00a", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b2eaa0dd3cfb9bd7fb949b88217df9f25aed915e986a28ad5c8a0d054e7ca9d3"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.19", "c95e9acbc374fb796ee3e24bfecc8213123c74d9f9e45667ca40bb0a4d242953", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d5ad357d6b21562a5b431f0ad09dfe76db9ce5648c6949f1aac334c8c4455d32"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, "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_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"}, - "plug": {:hex, :plug, "1.18.1", "5067f26f7745b7e31bc3368bc1a2b818b9779faa959b49c934c17730efc911cf", [: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", "57a57db70df2b422b564437d2d33cf8d33cd16339c1edb190cd11b1a3a546cc2"}, + "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_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"}, @@ -126,6 +128,7 @@ "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"}, "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"}, "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"]}, @@ -142,7 +145,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_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"}, - "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"}, "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"}, @@ -150,7 +153,7 @@ "ueberauth": {:hex, :ueberauth, "0.10.8", "ba78fbcbb27d811a6cd06ad851793aaf7d27c3b30c9e95349c2c362b344cd8f0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f2d3172e52821375bccb8460e5fa5cb91cfd60b19b636b6e57e9759b6f8c10c1"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"}, "unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"}, - "vix": {:hex, :vix, "0.26.0", "027f10b6969b759318be84bd0bd8c88af877445e4e41cf96a0460392cea5399c", [:make, :mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:cc_precompiler, "~> 0.1.4 or ~> 0.2", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7.3 or ~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "71b0a79ae7f199cacfc8e679b0e4ba25ee47dc02e182c5b9097efb29fbe14efd"}, + "vix": {:hex, :vix, "0.36.0", "3132dc065beda06dab1895a53d8c852d8e6a5bbca375c609435e968b1290e113", [:make, :mix], [{:cc_precompiler, "~> 0.1.4 or ~> 0.2", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7.3 or ~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "92f912b4e90c453f92942742105bcdb367ad53406759da251bd2e587e33f4134"}, "web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, diff --git a/priv/repo/migrations/20200825061316_move_activity_expirations_to_oban.exs b/priv/repo/migrations/20200825061316_move_activity_expirations_to_oban.exs index f15876180..b512313c7 100644 --- a/priv/repo/migrations/20200825061316_move_activity_expirations_to_oban.exs +++ b/priv/repo/migrations/20200825061316_move_activity_expirations_to_oban.exs @@ -23,10 +23,12 @@ defmodule Pleroma.Repo.Migrations.MoveActivityExpirationsToOban do |> Pleroma.Repo.stream() |> Stream.each(fn expiration -> with {:ok, expires_at} <- DateTime.from_naive(expiration.scheduled_at, "Etc/UTC") do - Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ - activity_id: FlakeId.to_string(expiration.activity_id), - expires_at: expires_at - }) + Pleroma.Workers.PurgeExpiredActivity.enqueue( + %{ + activity_id: FlakeId.to_string(expiration.activity_id) + }, + scheduled_at: expires_at + ) end end) |> Stream.run() diff --git a/priv/repo/migrations/20200907092050_move_tokens_expiration_into_oban.exs b/priv/repo/migrations/20200907092050_move_tokens_expiration_into_oban.exs index c140bc66a..0a55e4f71 100644 --- a/priv/repo/migrations/20200907092050_move_tokens_expiration_into_oban.exs +++ b/priv/repo/migrations/20200907092050_move_tokens_expiration_into_oban.exs @@ -21,7 +21,7 @@ defmodule Pleroma.Repo.Migrations.MoveTokensExpirationIntoOban do from(t in Pleroma.Web.OAuth.Token, where: t.valid_until > ^NaiveDateTime.utc_now()) |> Pleroma.Repo.stream() |> Stream.each(fn token -> - Pleroma.Workers.PurgeExpiredToken.enqueue(%{ + enqueue(%{ token_id: token.id, valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), mod: Pleroma.Web.OAuth.Token @@ -33,7 +33,7 @@ defmodule Pleroma.Repo.Migrations.MoveTokensExpirationIntoOban do from(t in Pleroma.MFA.Token, where: t.valid_until > ^NaiveDateTime.utc_now()) |> Pleroma.Repo.stream() |> Stream.each(fn token -> - Pleroma.Workers.PurgeExpiredToken.enqueue(%{ + enqueue(%{ token_id: token.id, valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), mod: Pleroma.MFA.Token @@ -41,4 +41,14 @@ defmodule Pleroma.Repo.Migrations.MoveTokensExpirationIntoOban do end) |> Stream.run() end + + @spec enqueue(%{token_id: integer(), valid_until: DateTime.t()}) :: + {:ok, Oban.Job.t()} | {:error, Ecto.Changeset.t()} + defp enqueue(args) do + {scheduled_at, args} = Map.pop(args, :valid_until) + + args + |> Pleroma.Workers.PurgeExpiredToken.new(scheduled_at: scheduled_at) + |> Oban.insert() + end end diff --git a/priv/repo/migrations/20220225164000_add_activity_assigned_account_index.exs b/priv/repo/migrations/20220225164000_add_activity_assigned_account_index.exs new file mode 100644 index 000000000..b54daf43d --- /dev/null +++ b/priv/repo/migrations/20220225164000_add_activity_assigned_account_index.exs @@ -0,0 +1,11 @@ +defmodule Pleroma.Repo.Migrations.AddActivityAssignedAccountIndex do + use Ecto.Migration + + def change do + create_if_not_exists( + index(:activities, ["(data->>'assigned_account')"], + name: :activities_assigned_account_index + ) + ) + end +end diff --git a/priv/repo/migrations/20260218000000_add_exclusive_to_lists.exs b/priv/repo/migrations/20260218000000_add_exclusive_to_lists.exs new file mode 100644 index 000000000..253a8acb7 --- /dev/null +++ b/priv/repo/migrations/20260218000000_add_exclusive_to_lists.exs @@ -0,0 +1,9 @@ +defmodule Pleroma.Repo.Migrations.AddExclusiveToLists do + use Ecto.Migration + + def change do + alter table(:lists) do + add(:exclusive, :boolean, default: false) + end + end +end diff --git a/priv/repo/migrations/20260401185429_cleanup_stale_digest_email_jobs.exs b/priv/repo/migrations/20260401185429_cleanup_stale_digest_email_jobs.exs new file mode 100644 index 000000000..882c7ec66 --- /dev/null +++ b/priv/repo/migrations/20260401185429_cleanup_stale_digest_email_jobs.exs @@ -0,0 +1,9 @@ +defmodule Pleroma.Repo.Migrations.CleanupStaleDigestEmailJobs do + use Ecto.Migration + + def up do + execute( + "DELETE from oban_jobs WHERE queue = 'mailer' AND worker = 'Pleroma.Workers.Cron.DigestEmailsWorker'" + ) + end +end diff --git a/rel/files/bin/pleroma_ctl b/rel/files/bin/pleroma_ctl index 6f0dba3a8..f5d3f321b 100755 --- a/rel/files/bin/pleroma_ctl +++ b/rel/files/bin/pleroma_ctl @@ -78,14 +78,19 @@ update() { RELEASE_ROOT=$(dirname "$SCRIPTPATH") uri="https://git.pleroma.social" - project_id="2" - project_branch="${BRANCH:-$(detect_branch)}" - flavour="${FLAVOUR:-$(detect_flavour)}" + project_name="pleroma" + package_base="${uri}/api/packages/${project_name}/generic" + 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}" 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}" - curl "$full_uri" -o "${artifact}" + curl -fL "$full_uri" -o "${artifact}" echo "Unpacking ${artifact} to ${tmp}" unzip -q "$artifact" -d "$tmp" echo "Copying files over to $RELEASE_ROOT" @@ -137,7 +142,14 @@ else SCRIPT=$(realpath "$0") 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" if [ $# -gt 0 ]; then @@ -154,8 +166,8 @@ else if [ "$ACTION" = "update" ]; then update "$@" 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 - "$SCRIPTPATH"/pleroma rpc 'Pleroma.ReleaseTasks.run("'"$FULL_ARGS"'")' + "$SCRIPTPATH"/pleroma rpc 'Pleroma.ReleaseTasks.run(['"${PREPARED_ARGS%%,}"'])' fi fi diff --git a/test/fixtures/server.pem b/test/fixtures/server.pem new file mode 100644 index 000000000..8bd3d6d05 --- /dev/null +++ b/test/fixtures/server.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIICpDCCAYwCCQC0vCQAnSoGdzANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls +b2NhbGhvc3QwHhcNMjYwMTE2MTY1ODE5WhcNMzYwMTE0MTY1ODE5WjAUMRIwEAYD +VQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCq +dZ4O2upZqwIo1eK5KrW1IIsjkfsFK8hE7Llh+4axcesiUKot0ib1CUhRSYiL1DLO +CIYQOw8IKQDVSC4JWAX9SsnX4W8dwexMQuSQG7/IKX2auC1bNNySFvoqM6Gq3GL9 +MqBFonZGXDPZu8fmxsI/2p9+2GK13F+HXgoLlXSCoO3XELJaBmjv29tgxxWRxCiH +m4u0briSxgUEx+CctpKPvGDmLaoIOIhjtuoG6OjkeWUOp6jDcteazO23VxPyF5cS +NbRJgm8AckrTQ6wbWSnhyqF8rPEsIc0ZAlUdDEs5fL3sjugc566FvE+GOkZIEyDD +tgWbc4Ne+Kp/nnt6oVxpAgMBAAEwDQYJKoZIhvcNAQELBQADggEBADv+J1DTok8V +MKVKo0hsRnHTeJQ2+EIgOspuYlEzez3PysOZH6diAQxO2lzuo9LKxP3hnmw17XO/ +P2oCzYyb9/P58VY/gr4UDIfuhgcE0cVfdsRhVId/I2FW6VP2f5q1TGbDUxSsVIlG +6hufn1aLBu90LtEbDkHqbnD05yYPwdqzWg4TrOXbX+jBhQrXJJdB3W7KTgozjRQw +F7+/2IyXoxXuxcwQBQlYhUbvGlsFqFpP/6cz2al5i5pNUkiNaSYwlRmuwa7zoTft +tHf57dhfXIpXET2BaJM6DSjDOOG/QleRXkvkTI5J21q+Bo+XnOzo19p4cZKJpTFC +SNgrftyNh3k= +-----END CERTIFICATE----- + diff --git a/test/fixtures/users_mock/hubzilla-actor-alsoknownas-string.json b/test/fixtures/users_mock/hubzilla-actor-alsoknownas-string.json new file mode 100644 index 000000000..086db73b1 --- /dev/null +++ b/test/fixtures/users_mock/hubzilla-actor-alsoknownas-string.json @@ -0,0 +1 @@ +{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1","https://purl.archive.org/socialweb/webfinger",{"zot":"https://hub.netzgemeinde.eu/apschema#","contextHistory":"https://w3id.org/fep/171b/contextHistory","schema":"http://schema.org#","ostatus":"http://ostatus.org#","diaspora":"https://diasporafoundation.org/ns/","litepub":"http://litepub.social/ns#","toot":"http://joinmastodon.org/ns#","commentPolicy":"zot:commentPolicy","Bookmark":"zot:Bookmark","Category":"zot:Category","Emoji":"toot:Emoji","directMessage":"litepub:directMessage","PropertyValue":"schema:PropertyValue","value":"schema:value","uuid":"schema:identifier","conversation":"ostatus:conversation","manuallyApprovesFollowers":"as:manuallyApprovesFollowers","Hashtag":"as:Hashtag","quoteUrl":"as:quoteUrl","quoteUri":"http://fedibird.com/ns#quoteUri"}],"type":"Person","manuallyApprovesFollowers":true,"id":"https://hub.netzgemeinde.eu/channel/jupiter_rowland","preferredUsername":"jupiter_rowland","name":"Jupiter Rowland","updated":"0001-01-01T00:00:00Z","icon":{"type":"Image","mediaType":"image/png","updated":"2024-10-15T21:10:02Z","url":"https://hub.netzgemeinde.eu/photo/profile/l/1035?rev=1729019402","height":300,"width":300},"url":"https://hub.netzgemeinde.eu/channel/jupiter_rowland","publicKey":{"id":"https://hub.netzgemeinde.eu/channel/jupiter_rowland","owner":"https://hub.netzgemeinde.eu/channel/jupiter_rowland","signatureAlgorithm":"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAmDB9nkcdhjzcfSPQG5q3\nxRVAYaWa+pKC38NhhRMbmd7+P8+8be3HUuX97bwhLdSA3+IRMz9JmX8bqqtKPK8A\nKxWFdUxoZKefwAAVpT+R89hHvi6Ib56Tp5lNlUSTIg3QXfm2gyRc3ehYbI9i6+Wr\nuFgCzdYT9bIeLqhZdFabPP4NORnyPgBQtktXcQUoDOKSaaKuitxFP1dhM9Dco3uX\nLcpQOLdY6yct5J+1Y6+/GUZgtO2pjMtkoEo6Ro0+Wlo7xfvdPnk5moljDVzPFoQE\nhUao5amorPhm1/iEpQXI0eEUW8IXdObFS8gyQJLmS30AjMkaWfwM9HFGmUmn8CSw\nKGBaKDN2C93fvmLOLpaoIRqgVTHBxfv3bN/CtvTRP83/eWvmhnlYo0fmE49tj2ZH\neCHCvRPJ+XM44WntbrUmwJ61+6nO/Io7qe7zmLm+0ew1VD9xTWAd96isW8HEmhcu\nO2iP2GALb0PqE7mgQmV1x/WAYB40+29C03UINHAnZY+nvBW6xd0wAOFiYXqjJF/4\nxEOWPvwcwOtltX+dTerFBC2KzLQQVk/CK2JdKtD2ssiYrvdC/IqPQzVMz3EAtVoP\n8sq/lzffCD3T+zhhnLhgxu7p9JNjRq83jMOOB3DUSd5izO+u6TBn5lasfoa7Eh+X\nQezGvMXhRQR4mnzrSLwZZ1ECAwEAAQ==\n-----END PUBLIC KEY-----\n"},"tag":[{"type":"PropertyValue","name":"Protocol","value":"zot6"},{"type":"PropertyValue","name":"Protocol","value":"activitypub"}],"outbox":"https://hub.netzgemeinde.eu/outbox/jupiter_rowland","webfinger":"jupiter_rowland@hub.netzgemeinde.eu","inbox":"https://hub.netzgemeinde.eu/inbox/jupiter_rowland","followers":"https://hub.netzgemeinde.eu/followers/jupiter_rowland","following":"https://hub.netzgemeinde.eu/following/jupiter_rowland","endpoints":{"sharedInbox":"https://hub.netzgemeinde.eu/inbox"},"discoverable":true,"assertionMethod":[{"id":"https://hub.netzgemeinde.eu/channel/jupiter_rowland#z6Mkw5vE2YnuCwke9VY6Xn1jnaXqLgDCKoJsRE2PjDmAUw9w","type":"Multikey","controller":"https://hub.netzgemeinde.eu/channel/jupiter_rowland","publicKeyMultibase":"z6Mkw5vE2YnuCwke9VY6Xn1jnaXqLgDCKoJsRE2PjDmAUw9w"}],"copiedTo":"https://hub.hubzilla.de/channel/jupiter_rowland","alsoKnownAs":"https://hub.hubzilla.de/channel/jupiter_rowland","image":{"type":"Image","mediaType":"image/jpeg","url":"https://hub.netzgemeinde.eu/photo/5bd87e54-e328-47d3-a8ed-7a33547ad882-7"},"summary":"An avatar roaming the decentralised and federated 3-D virtual worlds based on OpenSimulator, a free and open-source server-side re-implementation of Second Life. Mostly talking about OpenSim, sometimes about other virtual worlds, occasionally about the Fediverse beyond Mastodon. No, the Fediverse is not only Mastodon.

If you're looking for real-life people posting about real-life topics, go look somewhere else. This channel is never about real life.

Even if you see me on Mastodon, I'm not on Mastodon myself. I'm on Hubzilla which is neither a Mastodon instance nor a Mastodon fork. In fact, it's older and much more powerful than Mastodon. And it has always been connected to Mastodon.

I regularly write posts with way more than 500 characters. If that disturbs you, block me now, but don't complain. I'm not on Mastodon, I don't have a character limit here.

I rather give too many content warnings than too few. But I have absolutely no means of blanking out pictures for Mastodon users.

I always describe my images, no matter how long it takes. My posts with image descriptions tend to be my longest. Don't go looking for my image descriptions in the alt-text; they're always in the post text which is always hidden behind a content warning due to being over 500 characters long.

If you follow me, and I "follow" you back, I don't actually follow you and receive your posts. Unless you've got something to say that's interesting to me within the scope of this channel, or I know you from OpenSim, I'll most likely deny you the permission to send me your posts. I only "follow" you back because Hubzilla requires me to do that to allow you to follow me. But I do let you send me your comments and direct messages. If you boost a lot of uninteresting stuff, I'll block you boosts.

My "birthday" isn't my actual birthday but my rezday. My first avatar has been around since that day.

If you happen to know German, maybe my "homepage" is something for you, a blog which, much like this channel, is about OpenSim and generally virtual worlds.

#OpenSim #OpenSimulator #VirtualWorlds #Metaverse #SocialVR  #fedi22","proof":{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","created":"2026-02-12T17:18:56Z","verificationMethod":"https://hub.netzgemeinde.eu/channel/jupiter_rowland#z6Mkw5vE2YnuCwke9VY6Xn1jnaXqLgDCKoJsRE2PjDmAUw9w","proofPurpose":"assertionMethod","proofValue":"z65hrf1X7c2kZgiyAvBnZ1pd16LGRaLYN4zt6kLdPQtYoYpxuVYgL7zZGFEh3h6gcsq8f6FL5XDjXVNivwg9eJL3u"},"signature":{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1"],"type":"RsaSignature2017","nonce":"87c52f614ebea65c8ea41d99487e8f8c8883bd5bc65b2538438df3870c532823","creator":"https://hub.netzgemeinde.eu/channel/jupiter_rowland","created":"2026-02-12T17:18:56Z","signatureValue":"FtiD/BaezB1SFiYj18hfAuyLgYQ0c+Mjc9CXiB+ikd8re/uXV5jr+kMgpYzY+nUB3vpU03XnWVi+lHkJ55vzcTjfbUi7puspF7e88yjIFJcHhs+fc9/+bzBbL2PSQ026bTgH3N1Hj2I7qQ3NcmvKmDF/OovWDks/HPGFiiS9uUezZHRF5be7KfUgEJjESk+5zKGkr7yG4M+f23xaScGxT+/uGK69/RT7QbgSiF2A1IiD2gtwVqxWe9eP//HeoXPCT+O+MdKLWtSZwwzfboO/iwNehMtSAAq+POBW7emHTcM1FEZD6abEQAQwpTq28qAnjuYNCU4TzBdKg1q4akhqnTWEyqpscS9xVUUv/iRiyXvWh9WtcGSCvcMb6avUsxGmdk0nO8hv2zigR/s/ZZJhjXUrID+/bwP2o9Uie6ibdwmt1bCHR8U6z7NCQXEJXw9qZuZYpiTFeuNSftICVRuE/NIYpSOPyqERlRnjU6AX6qEQXHQsOnvhB8BUKcRXpWv0xuzWN3Sr2MZyul8A98nvla9Uwqh9BYn70nRcpRGfiY3m41N4kn5WbPw5PezOB8RSFZjji963oum8tu0/NjzKQ/5UOh3aPO+h+AJsnjau2KOeRHLeChalOOLrt3KFalkyGxLzxt5o/OcRvyM37nnih14lqeTNo60PmR/l/QpiwBs="}} \ No newline at end of file diff --git a/test/mix/pleroma_test.exs b/test/mix/pleroma_test.exs index e362223b2..e8f801913 100644 --- a/test/mix/pleroma_test.exs +++ b/test/mix/pleroma_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.PleromaTest do - use ExUnit.Case, async: true + use ExUnit.Case, async: false import Mix.Pleroma setup_all do diff --git a/test/mix/tasks/pleroma/app_test.exs b/test/mix/tasks/pleroma/app_test.exs index 65245eadd..6156214c1 100644 --- a/test/mix/tasks/pleroma/app_test.exs +++ b/test/mix/tasks/pleroma/app_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Tasks.Pleroma.AppTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false setup_all do Mix.shell(Mix.Shell.Process) diff --git a/test/mix/tasks/pleroma/config_test.exs b/test/mix/tasks/pleroma/config_test.exs index 942cfa83d..ef1adc235 100644 --- a/test/mix/tasks/pleroma/config_test.exs +++ b/test/mix/tasks/pleroma/config_test.exs @@ -329,5 +329,39 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do assert config_records() == [] end + + test "filters non-whitelisted settings" do + clear_config(:database_config_whitelist, [ + {:pleroma}, + {:web_push_encryption, :vapid_details} + ]) + + insert_config_record(:web_push_encryption, :non_whitelisted_key, a: 1) + insert_config_record(:web_push_encryption, :vapid_details, b: 1) + + MixTask.run(["filter_whitelisted", "--force"]) + + assert [ + %ConfigDB{group: :pleroma, key: :instance}, + %ConfigDB{group: :pleroma, key: Pleroma.Captcha}, + %ConfigDB{group: :web_push_encryption, key: :vapid_details} + ] = config_records() + end + + test "filter_whitelisted doesn't crash when whitelist is unset" do + clear_config(:database_config_whitelist, nil) + + existing = config_records() + MixTask.run(["filter_whitelisted", "--force"]) + assert config_records() == existing + end + + test "filter_whitelisted doesn't crash when whitelist is disabled" do + clear_config(:database_config_whitelist, false) + + existing = config_records() + MixTask.run(["filter_whitelisted", "--force"]) + assert config_records() == existing + end end end diff --git a/test/mix/tasks/pleroma/database_test.exs b/test/mix/tasks/pleroma/database_test.exs index 38ed096ae..5b567325c 100644 --- a/test/mix/tasks/pleroma/database_test.exs +++ b/test/mix/tasks/pleroma/database_test.exs @@ -3,11 +3,12 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Tasks.Pleroma.DatabaseTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false use Oban.Testing, repo: Pleroma.Repo alias Pleroma.Activity alias Pleroma.Bookmark + alias Pleroma.Hashtag alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User @@ -550,6 +551,39 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do assert length(activities) == 3 end + + test "it prunes hashtags with no objects associated", %{old_insert_date: old_insert_date} do + user = insert(:user) + + {:ok, hashtag_post_activity} = + CommonAPI.post(user, %{status: "morning #cofe", local: true}) + + hashtag_post_object = Object.normalize(hashtag_post_activity) + + {:ok, hashtag_post2_activity} = + CommonAPI.post(user, %{status: "morning #cawfee", local: true}) + + hashtag_post2_object = Object.normalize(hashtag_post2_activity) + + hashtag_post_object + |> Ecto.Changeset.change(%{updated_at: old_insert_date}) + |> Repo.update!() + + hashtag_post2_object + |> Ecto.Changeset.change(%{updated_at: old_insert_date}) + |> Repo.update!() + + # Test whether hashtags with follow relationships are kept + User.follow_hashtag(user, Hashtag.get_by_name("cofe")) + + assert length(Repo.all(Hashtag)) == 2 + assert length(Repo.all(Object)) == 2 + + Mix.Tasks.Pleroma.Database.run(["prune_objects"]) + assert length(Repo.all(Hashtag)) == 1 + assert length(Repo.all(Object)) == 0 + assert Repo.one(Hashtag) |> Map.fetch!(:name) == "cofe" + end end describe "running update_users_following_followers_counts" do diff --git a/test/pleroma/config_db_test.exs b/test/pleroma/config_db_test.exs index d68e4e6fa..9d70e6d54 100644 --- a/test/pleroma/config_db_test.exs +++ b/test/pleroma/config_db_test.exs @@ -174,6 +174,22 @@ defmodule Pleroma.ConfigDBTest do assert updated1.value == [groups: [c: 3, d: 4], key: [a: 1, b: 2]] assert updated2.value == [mascots: [c: 3, d: 4], key: [a: 1, b: 2]] end + + test "rejects invalid :rate_limit values (e.g. empty-string scale from AdminFE)" do + assert {:error, _changeset} = + ConfigDB.update_or_create(%{ + group: ":pleroma", + key: ":rate_limit", + value: [ + %{ + "tuple" => [ + ":statuses_actions", + [%{"tuple" => ["", 0]}, %{"tuple" => ["", ""]}] + ] + } + ] + }) + end end describe "delete/1" do @@ -273,24 +289,28 @@ defmodule Pleroma.ConfigDBTest do end test "sigil" do - assert ConfigDB.to_elixir_types("~r[comp[lL][aA][iI][nN]er]") == ~r/comp[lL][aA][iI][nN]er/ + assert ConfigDB.to_elixir_types("~r[comp[lL][aA][iI][nN]er]").source == + ~r/comp[lL][aA][iI][nN]er/.source end test "link sigil" do - assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/") == ~r/https:\/\/example.com/ + assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/").source == + ~r/https:\/\/example.com/.source end test "link sigil with um modifiers" do - assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/um") == - ~r/https:\/\/example.com/um + assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/um").source == + ~r/https:\/\/example.com/um.source end test "link sigil with i modifier" do - assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/i") == ~r/https:\/\/example.com/i + assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/i").source == + ~r/https:\/\/example.com/i.source end test "link sigil with s modifier" do - assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/s") == ~r/https:\/\/example.com/s + assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/s").source == + ~r/https:\/\/example.com/s.source end test "raise if valid delimiter not found" do @@ -460,11 +480,11 @@ defmodule Pleroma.ConfigDBTest do test "complex keyword with sigil" do assert ConfigDB.to_elixir_types([ %{"tuple" => [":federated_timeline_removal", []]}, - %{"tuple" => [":reject", ["~r/comp[lL][aA][iI][nN]er/"]]}, + %{"tuple" => [":reject", [~r/comp[lL][aA][iI][nN]er/.source]]}, %{"tuple" => [":replace", []]} ]) == [ federated_timeline_removal: [], - reject: [~r/comp[lL][aA][iI][nN]er/], + reject: [~r/comp[lL][aA][iI][nN]er/.source], replace: [] ] end diff --git a/test/pleroma/http/adapter_helper/hackney_test.exs b/test/pleroma/http/adapter_helper/hackney_test.exs index 57ce4728c..343bdb800 100644 --- a/test/pleroma/http/adapter_helper/hackney_test.exs +++ b/test/pleroma/http/adapter_helper/hackney_test.exs @@ -16,6 +16,14 @@ defmodule Pleroma.HTTP.AdapterHelper.HackneyTest do describe "options/2" do setup do: clear_config([:http, :adapter], a: 1, b: 2) + test "uses redirect-safe defaults", %{uri: uri} do + opts = Hackney.options([], uri) + + assert opts[:follow_redirect] == false + assert opts[:force_redirect] == false + assert opts[:with_body] == true + end + test "add proxy and opts from config", %{uri: uri} do opts = Hackney.options([proxy: "localhost:8123"], uri) diff --git a/test/pleroma/http/hackney_follow_redirect_regression_test.exs b/test/pleroma/http/hackney_follow_redirect_regression_test.exs new file mode 100644 index 000000000..4fef26af6 --- /dev/null +++ b/test/pleroma/http/hackney_follow_redirect_regression_test.exs @@ -0,0 +1,404 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.HackneyFollowRedirectRegressionTest do + use ExUnit.Case, async: false + + setup do + {:ok, _} = Application.ensure_all_started(:hackney) + + {:ok, tls_server} = start_tls_redirect_server() + {:ok, proxy} = start_connect_proxy() + + on_exit(fn -> + stop_connect_proxy(proxy) + stop_tls_redirect_server(tls_server) + end) + + {:ok, tls_server: tls_server, proxy: proxy} + end + + test "hackney follow_redirect crashes behind CONNECT proxy on relative redirects", %{ + tls_server: tls_server, + proxy: proxy + } do + url = "#{tls_server.base_url}/redirect" + + opts = [ + pool: :media, + proxy: proxy.proxy_url, + insecure: true, + connect_timeout: 1_000, + recv_timeout: 1_000, + follow_redirect: true, + force_redirect: true + ] + + {pid, ref} = spawn_monitor(fn -> :hackney.request(:get, url, [], <<>>, opts) end) + + assert_receive {:DOWN, ^ref, :process, ^pid, reason}, 5_000 + + assert match?({%FunctionClauseError{}, _}, reason) or match?(%FunctionClauseError{}, reason) or + match?({:function_clause, _}, reason) + end + + test "redirects work via proxy when hackney follow_redirect is disabled", %{ + tls_server: tls_server, + proxy: proxy + } do + url = "#{tls_server.base_url}/redirect" + + adapter_opts = [ + pool: :media, + proxy: proxy.proxy_url, + insecure: true, + connect_timeout: 1_000, + recv_timeout: 1_000, + follow_redirect: false, + force_redirect: false, + with_body: true + ] + + client = Tesla.client([Tesla.Middleware.FollowRedirects], Tesla.Adapter.Hackney) + + assert {:ok, %Tesla.Env{status: 200, body: "ok"}} = + Tesla.request(client, method: :get, url: url, opts: [adapter: adapter_opts]) + end + + test "reverse proxy hackney client follows redirects via proxy without crashing", %{ + tls_server: tls_server, + proxy: proxy + } do + url = "#{tls_server.base_url}/redirect" + + opts = [ + pool: :media, + proxy: proxy.proxy_url, + insecure: true, + connect_timeout: 1_000, + recv_timeout: 1_000, + follow_redirect: true + ] + + assert {:ok, 200, _headers, ref} = + Pleroma.ReverseProxy.Client.Hackney.request(:get, url, [], "", opts) + + assert collect_body(ref) == "ok" + Pleroma.ReverseProxy.Client.Hackney.close(ref) + end + + test "hackney reverse proxy follows nested redirects via proxy", %{ + tls_server: tls_server, + proxy: proxy + } do + url = "#{tls_server.base_url}/nested_redirect" + + opts = [ + pool: :media, + proxy: proxy.proxy_url, + insecure: true, + connect_timeout: 1_000, + recv_timeout: 1_000, + follow_redirect: true + ] + + assert {:ok, 200, _headers, ref} = + Pleroma.ReverseProxy.Client.Hackney.request(:get, url, [], "", opts) + + assert collect_body(ref) == "ok" + Pleroma.ReverseProxy.Client.Hackney.close(ref) + end + + test "hackney reverse proxy stops following redirects after limit is reached", %{ + tls_server: tls_server, + proxy: proxy + } do + url = "#{tls_server.base_url}/infinite_redirect" + + opts = [ + pool: :media, + proxy: proxy.proxy_url, + insecure: true, + connect_timeout: 1_000, + recv_timeout: 1_000, + follow_redirect: true + ] + + assert {:ok, 302, _headers, ref} = + Pleroma.ReverseProxy.Client.Hackney.request(:get, url, [], "", opts) + + Pleroma.ReverseProxy.Client.Hackney.close(ref) + end + + defp collect_body(ref, acc \\ "") do + case Pleroma.ReverseProxy.Client.Hackney.stream_body(ref) do + :done -> acc + {:ok, data, _ref} -> collect_body(ref, acc <> data) + {:error, error} -> flunk("stream_body failed: #{inspect(error)}") + end + end + + defp start_tls_redirect_server do + certfile = Path.expand("../../fixtures/server.pem", __DIR__) + keyfile = Path.expand("../../fixtures/private_key.pem", __DIR__) + + {:ok, listener} = + :ssl.listen(0, [ + :binary, + certfile: certfile, + keyfile: keyfile, + reuseaddr: true, + active: false, + packet: :raw, + ip: {127, 0, 0, 1} + ]) + + {:ok, {{127, 0, 0, 1}, port}} = :ssl.sockname(listener) + + {:ok, acceptor} = + Task.start_link(fn -> + accept_tls_loop(listener) + end) + + {:ok, %{listener: listener, acceptor: acceptor, base_url: "https://127.0.0.1:#{port}"}} + end + + defp stop_tls_redirect_server(%{listener: listener, acceptor: acceptor}) do + :ok = :ssl.close(listener) + + if Process.alive?(acceptor) do + Process.exit(acceptor, :normal) + end + end + + defp accept_tls_loop(listener) do + case :ssl.transport_accept(listener) do + {:ok, socket} -> + _ = Task.start(fn -> serve_tls(socket) end) + accept_tls_loop(listener) + + {:error, :closed} -> + :ok + + {:error, _reason} -> + :ok + end + end + + defp serve_tls(tcp_socket) do + with {:ok, ssl_socket} <- :ssl.handshake(tcp_socket, 2_000), + {:ok, data} <- recv_ssl_headers(ssl_socket), + {:ok, path} <- parse_path(data) do + case path do + "/infinite_redirect" -> + send_ssl_response(ssl_socket, 302, "Found", [{"Location", "/infinite_redirect"}], "") + + "/nested_redirect" -> + send_ssl_response(ssl_socket, 302, "Found", [{"Location", "/redirect"}], "") + + "/redirect" -> + send_ssl_response(ssl_socket, 302, "Found", [{"Location", "/final"}], "") + + "/final" -> + send_ssl_response(ssl_socket, 200, "OK", [], "ok") + + _ -> + send_ssl_response(ssl_socket, 404, "Not Found", [], "not found") + end + + :ssl.close(ssl_socket) + else + _ -> + _ = :gen_tcp.close(tcp_socket) + :ok + end + end + + defp recv_ssl_headers(socket, acc \\ <<>>) do + case :ssl.recv(socket, 0, 1_000) do + {:ok, data} -> + acc = acc <> data + + if :binary.match(acc, "\r\n\r\n") != :nomatch do + {:ok, acc} + else + if byte_size(acc) > 8_192 do + {:error, :too_large} + else + recv_ssl_headers(socket, acc) + end + end + + {:error, _} = error -> + error + end + end + + defp send_ssl_response(socket, status, reason, headers, body) do + base_headers = + [ + {"Content-Length", Integer.to_string(byte_size(body))}, + {"Connection", "close"} + ] ++ headers + + iodata = + [ + "HTTP/1.1 ", + Integer.to_string(status), + " ", + reason, + "\r\n", + Enum.map(base_headers, fn {k, v} -> [k, ": ", v, "\r\n"] end), + "\r\n", + body + ] + + :ssl.send(socket, iodata) + end + + defp start_connect_proxy do + {:ok, listener} = + :gen_tcp.listen(0, [ + :binary, + active: false, + packet: :raw, + reuseaddr: true, + ip: {127, 0, 0, 1} + ]) + + {:ok, {{127, 0, 0, 1}, port}} = :inet.sockname(listener) + + {:ok, acceptor} = + Task.start_link(fn -> + accept_proxy_loop(listener) + end) + + {:ok, %{listener: listener, acceptor: acceptor, proxy_url: "127.0.0.1:#{port}"}} + end + + defp stop_connect_proxy(%{listener: listener, acceptor: acceptor}) do + :ok = :gen_tcp.close(listener) + + if Process.alive?(acceptor) do + Process.exit(acceptor, :normal) + end + end + + defp accept_proxy_loop(listener) do + case :gen_tcp.accept(listener) do + {:ok, socket} -> + _ = Task.start(fn -> serve_proxy(socket) end) + accept_proxy_loop(listener) + + {:error, :closed} -> + :ok + + {:error, _reason} -> + :ok + end + end + + defp serve_proxy(client_socket) do + with {:ok, {headers, rest}} <- recv_tcp_headers(client_socket), + {:ok, {host, port}} <- parse_connect(headers), + {:ok, upstream_socket} <- connect_upstream(host, port) do + :gen_tcp.send(client_socket, "HTTP/1.1 200 Connection established\r\n\r\n") + + if rest != <<>> do + :gen_tcp.send(upstream_socket, rest) + end + + tunnel(client_socket, upstream_socket) + else + _ -> + :gen_tcp.close(client_socket) + :ok + end + end + + defp tunnel(client_socket, upstream_socket) do + parent = self() + _ = spawn_link(fn -> forward(client_socket, upstream_socket, parent) end) + _ = spawn_link(fn -> forward(upstream_socket, client_socket, parent) end) + + receive do + :tunnel_closed -> :ok + after + 10_000 -> :ok + end + + :gen_tcp.close(client_socket) + :gen_tcp.close(upstream_socket) + end + + defp forward(from_socket, to_socket, parent) do + case :gen_tcp.recv(from_socket, 0, 10_000) do + {:ok, data} -> + _ = :gen_tcp.send(to_socket, data) + forward(from_socket, to_socket, parent) + + {:error, _reason} -> + send(parent, :tunnel_closed) + :ok + end + end + + defp recv_tcp_headers(socket, acc \\ <<>>) do + case :gen_tcp.recv(socket, 0, 1_000) do + {:ok, data} -> + acc = acc <> data + + case :binary.match(acc, "\r\n\r\n") do + :nomatch -> + if byte_size(acc) > 8_192 do + {:error, :too_large} + else + recv_tcp_headers(socket, acc) + end + + {idx, _len} -> + split_at = idx + 4 + <> = acc + {:ok, {headers, rest}} + end + + {:error, _} = error -> + error + end + end + + defp parse_connect(data) do + with [request_line | _] <- String.split(data, "\r\n", trim: true), + ["CONNECT", hostport | _] <- String.split(request_line, " ", parts: 3), + [host, port_str] <- String.split(hostport, ":", parts: 2), + {port, ""} <- Integer.parse(port_str) do + {:ok, {host, port}} + else + _ -> {:error, :invalid_connect} + end + end + + defp connect_upstream(host, port) do + address = + case :inet.parse_address(String.to_charlist(host)) do + {:ok, ip} -> ip + {:error, _} -> String.to_charlist(host) + end + + :gen_tcp.connect(address, port, [:binary, active: false, packet: :raw], 1_000) + end + + defp parse_path(data) do + case String.split(data, "\r\n", parts: 2) do + [request_line | _] -> + case String.split(request_line, " ") do + [_method, path, _protocol] -> {:ok, path} + _ -> {:error, :invalid_request} + end + + _ -> + {:error, :invalid_request} + end + end +end diff --git a/test/pleroma/http/hackney_redirect_regression_test.exs b/test/pleroma/http/hackney_redirect_regression_test.exs new file mode 100644 index 000000000..61389aa7e --- /dev/null +++ b/test/pleroma/http/hackney_redirect_regression_test.exs @@ -0,0 +1,151 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.HackneyRedirectRegressionTest do + use ExUnit.Case, async: false + + alias Pleroma.HTTP.AdapterHelper.Hackney, as: HackneyAdapterHelper + + setup do + {:ok, _} = Application.ensure_all_started(:hackney) + + {:ok, server} = start_server() + on_exit(fn -> stop_server(server) end) + + {:ok, server: server} + end + + test "pooled redirects work with follow_redirect disabled", %{server: server} do + url = "#{server.base_url}/redirect" + uri = URI.parse(url) + + adapter_opts = + HackneyAdapterHelper.options( + [pool: :media, follow_redirect: false, no_proxy_env: true], + uri + ) + + client = Tesla.client([Tesla.Middleware.FollowRedirects], Tesla.Adapter.Hackney) + + assert {:ok, %Tesla.Env{status: 200, body: "ok"}} = + Tesla.request(client, method: :get, url: url, opts: [adapter: adapter_opts]) + end + + defp start_server do + {:ok, listener} = + :gen_tcp.listen(0, [ + :binary, + active: false, + packet: :raw, + reuseaddr: true, + ip: {127, 0, 0, 1} + ]) + + {:ok, {{127, 0, 0, 1}, port}} = :inet.sockname(listener) + + {:ok, acceptor} = + Task.start_link(fn -> + accept_loop(listener) + end) + + {:ok, %{listener: listener, acceptor: acceptor, base_url: "http://127.0.0.1:#{port}"}} + end + + defp stop_server(%{listener: listener, acceptor: acceptor}) do + :ok = :gen_tcp.close(listener) + + if Process.alive?(acceptor) do + Process.exit(acceptor, :normal) + end + end + + defp accept_loop(listener) do + case :gen_tcp.accept(listener) do + {:ok, socket} -> + serve(socket) + accept_loop(listener) + + {:error, :closed} -> + :ok + + {:error, _reason} -> + :ok + end + end + + defp serve(socket) do + with {:ok, data} <- recv_headers(socket), + {:ok, path} <- parse_path(data) do + case path do + "/redirect" -> + send_response(socket, 302, "Found", [{"Location", "/final"}], "") + + "/final" -> + send_response(socket, 200, "OK", [], "ok") + + _ -> + send_response(socket, 404, "Not Found", [], "not found") + end + else + _ -> :ok + end + + :gen_tcp.close(socket) + end + + defp recv_headers(socket, acc \\ <<>>) do + case :gen_tcp.recv(socket, 0, 1_000) do + {:ok, data} -> + acc = acc <> data + + if :binary.match(acc, "\r\n\r\n") != :nomatch do + {:ok, acc} + else + if byte_size(acc) > 8_192 do + {:error, :too_large} + else + recv_headers(socket, acc) + end + end + + {:error, _} = error -> + error + end + end + + defp parse_path(data) do + case String.split(data, "\r\n", parts: 2) do + [request_line | _] -> + case String.split(request_line, " ") do + [_method, path, _protocol] -> {:ok, path} + _ -> {:error, :invalid_request} + end + + _ -> + {:error, :invalid_request} + end + end + + defp send_response(socket, status, reason, headers, body) do + base_headers = + [ + {"Content-Length", Integer.to_string(byte_size(body))}, + {"Connection", "close"} + ] ++ headers + + iodata = + [ + "HTTP/1.1 ", + Integer.to_string(status), + " ", + reason, + "\r\n", + Enum.map(base_headers, fn {k, v} -> [k, ": ", v, "\r\n"] end), + "\r\n", + body + ] + + :gen_tcp.send(socket, iodata) + end +end diff --git a/test/pleroma/http_test.exs b/test/pleroma/http_test.exs index 7b6847cf9..e673e6591 100644 --- a/test/pleroma/http_test.exs +++ b/test/pleroma/http_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.HTTPTest do - use ExUnit.Case, async: true + use ExUnit.Case, async: false use Pleroma.Tests.Helpers import Tesla.Mock diff --git a/test/pleroma/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs index 88f32762d..47f6f5f76 100644 --- a/test/pleroma/integration/mastodon_websocket_test.exs +++ b/test/pleroma/integration/mastodon_websocket_test.exs @@ -363,7 +363,7 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do test "accepts the 'list' stream", %{token: token, user: user} do posting_user = insert(:user) - {:ok, list} = Pleroma.List.create("test", user) + {:ok, list} = Pleroma.List.create(%{title: "test"}, user) Pleroma.List.follow(list, posting_user) assert {:ok, _} = start_socket("?stream=list&access_token=#{token.token}&list=#{list.id}") diff --git a/test/pleroma/list_test.exs b/test/pleroma/list_test.exs index a68146b0d..d44310689 100644 --- a/test/pleroma/list_test.exs +++ b/test/pleroma/list_test.exs @@ -10,22 +10,23 @@ defmodule Pleroma.ListTest do test "creating a list" do user = insert(:user) - {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", user) - %Pleroma.List{title: title} = Pleroma.List.get(list.id, user) + {:ok, %Pleroma.List{} = list} = Pleroma.List.create(%{title: "title"}, user) + %Pleroma.List{title: title, exclusive: exclusive} = Pleroma.List.get(list.id, user) assert title == "title" + assert exclusive == false end test "validates title" do user = insert(:user) - assert {:error, changeset} = Pleroma.List.create("", user) + assert {:error, changeset} = Pleroma.List.create(%{title: ""}, user) assert changeset.errors == [title: {"can't be blank", [validation: :required]}] end test "getting a list not belonging to the user" do user = insert(:user) other_user = insert(:user) - {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", user) + {:ok, %Pleroma.List{} = list} = Pleroma.List.create(%{title: "title"}, user) ret = Pleroma.List.get(list.id, other_user) assert is_nil(ret) end @@ -33,7 +34,7 @@ defmodule Pleroma.ListTest do test "adding an user to a list" do user = insert(:user) other_user = insert(:user) - {:ok, list} = Pleroma.List.create("title", user) + {:ok, list} = Pleroma.List.create(%{title: "title"}, user) {:ok, %{following: following}} = Pleroma.List.follow(list, other_user) assert [other_user.follower_address] == following end @@ -41,7 +42,7 @@ defmodule Pleroma.ListTest do test "removing an user from a list" do user = insert(:user) other_user = insert(:user) - {:ok, list} = Pleroma.List.create("title", user) + {:ok, list} = Pleroma.List.create(%{title: "title"}, user) {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user) {:ok, %{following: following}} = Pleroma.List.unfollow(list, other_user) assert [] == following @@ -49,14 +50,27 @@ defmodule Pleroma.ListTest do test "renaming a list" do user = insert(:user) - {:ok, list} = Pleroma.List.create("title", user) - {:ok, %{title: title}} = Pleroma.List.rename(list, "new") + {:ok, list} = Pleroma.List.create(%{title: "title"}, user) + {:ok, %{title: title}} = Pleroma.List.update(list, %{title: "new"}) assert "new" == title end + test "updating a list exclusivity" do + user = insert(:user) + + {:ok, %{exclusive: exclusive} = list} = + Pleroma.List.create(%{title: "title", exclusive: true}, user) + + assert exclusive == true + {:ok, %{exclusive: exclusive} = list} = Pleroma.List.update(list, %{exclusive: false}) + assert exclusive == false + {:ok, %{exclusive: exclusive}} = Pleroma.List.update(list, %{exclusive: true}) + assert exclusive == true + end + test "deleting a list" do user = insert(:user) - {:ok, list} = Pleroma.List.create("title", user) + {:ok, list} = Pleroma.List.create(%{title: "title"}, user) {:ok, list} = Pleroma.List.delete(list) assert is_nil(Repo.get(Pleroma.List, list.id)) end @@ -65,7 +79,7 @@ defmodule Pleroma.ListTest do user = insert(:user) other_user = insert(:user) third_user = insert(:user) - {:ok, list} = Pleroma.List.create("title", user) + {:ok, list} = Pleroma.List.create(%{title: "title"}, user) {:ok, list} = Pleroma.List.follow(list, other_user) {:ok, list} = Pleroma.List.follow(list, third_user) {:ok, following} = Pleroma.List.get_following(list) @@ -76,9 +90,9 @@ defmodule Pleroma.ListTest do test "getting all lists by an user" do user = insert(:user) other_user = insert(:user) - {:ok, list_one} = Pleroma.List.create("title", user) - {:ok, list_two} = Pleroma.List.create("other title", user) - {:ok, list_three} = Pleroma.List.create("third title", other_user) + {:ok, list_one} = Pleroma.List.create(%{title: "title"}, user) + {:ok, list_two} = Pleroma.List.create(%{title: "other title"}, user) + {:ok, list_three} = Pleroma.List.create(%{title: "third title"}, other_user) lists = Pleroma.List.for_user(user, %{}) assert list_one in lists assert list_two in lists @@ -88,9 +102,9 @@ defmodule Pleroma.ListTest do test "getting all lists the user is a member of" do user = insert(:user) other_user = insert(:user) - {:ok, list_one} = Pleroma.List.create("title", user) - {:ok, list_two} = Pleroma.List.create("other title", user) - {:ok, list_three} = Pleroma.List.create("third title", other_user) + {:ok, list_one} = Pleroma.List.create(%{title: "title"}, user) + {:ok, list_two} = Pleroma.List.create(%{title: "other title"}, user) + {:ok, list_three} = Pleroma.List.create(%{title: "third title"}, other_user) {:ok, list_one} = Pleroma.List.follow(list_one, other_user) {:ok, list_two} = Pleroma.List.follow(list_two, other_user) {:ok, list_three} = Pleroma.List.follow(list_three, user) @@ -106,8 +120,8 @@ defmodule Pleroma.ListTest do not_owner = insert(:user) member_1 = insert(:user) member_2 = insert(:user) - {:ok, owned_list} = Pleroma.List.create("owned", owner) - {:ok, not_owned_list} = Pleroma.List.create("not owned", not_owner) + {:ok, owned_list} = Pleroma.List.create(%{title: "owned"}, owner) + {:ok, not_owned_list} = Pleroma.List.create(%{title: "not owned"}, not_owner) {:ok, owned_list} = Pleroma.List.follow(owned_list, member_1) {:ok, owned_list} = Pleroma.List.follow(owned_list, member_2) {:ok, not_owned_list} = Pleroma.List.follow(not_owned_list, member_1) @@ -123,14 +137,14 @@ defmodule Pleroma.ListTest do test "get by ap_id" do user = insert(:user) - {:ok, list} = Pleroma.List.create("foo", user) + {:ok, list} = Pleroma.List.create(%{title: "foo"}, user) assert Pleroma.List.get_by_ap_id(list.ap_id) == list end test "memberships" do user = insert(:user) member = insert(:user) - {:ok, list} = Pleroma.List.create("foo", user) + {:ok, list} = Pleroma.List.create(%{title: "foo"}, user) {:ok, list} = Pleroma.List.follow(list, member) assert Pleroma.List.memberships(member) == [list.ap_id] @@ -140,7 +154,7 @@ defmodule Pleroma.ListTest do user = insert(:user) member = insert(:user) - {:ok, list} = Pleroma.List.create("foo", user) + {:ok, list} = Pleroma.List.create(%{title: "foo"}, user) {:ok, list} = Pleroma.List.follow(list, member) assert Pleroma.List.member?(list, member) diff --git a/test/pleroma/marker_test.exs b/test/pleroma/marker_test.exs index 819dde9be..7f573ac7a 100644 --- a/test/pleroma/marker_test.exs +++ b/test/pleroma/marker_test.exs @@ -36,11 +36,12 @@ defmodule Pleroma.MarkerTest do insert(:notification, user: user, activity: insert(:note_activity)) insert(:notification, user: user, activity: insert(:note_activity)) insert(:marker, timeline: "home", user: user) + %Marker{} = refreshed_marker = refresh_record(marker) assert Marker.get_markers( user, ["notifications"] - ) == [%Marker{refresh_record(marker) | unread_count: 2}] + ) == [%{refreshed_marker | unread_count: 2}] end end diff --git a/test/pleroma/pleroma_ctl_test.exs b/test/pleroma/pleroma_ctl_test.exs new file mode 100644 index 000000000..d96396399 --- /dev/null +++ b/test/pleroma/pleroma_ctl_test.exs @@ -0,0 +1,293 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# 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 diff --git a/test/pleroma/repo/migrations/autolinker_to_linkify_test.exs b/test/pleroma/repo/migrations/autolinker_to_linkify_test.exs index 99522994a..c15967eee 100644 --- a/test/pleroma/repo/migrations/autolinker_to_linkify_test.exs +++ b/test/pleroma/repo/migrations/autolinker_to_linkify_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Repo.Migrations.AutolinkerToLinkifyTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false import Pleroma.Factory import Pleroma.Tests.Helpers alias Pleroma.ConfigDB diff --git a/test/pleroma/repo_test.exs b/test/pleroma/repo_test.exs index 9c0f5d028..721175bda 100644 --- a/test/pleroma/repo_test.exs +++ b/test/pleroma/repo_test.exs @@ -24,7 +24,8 @@ defmodule Pleroma.RepoTest do describe "get_assoc/2" do test "get assoc from preloaded data" do user = %User{name: "Agent Smith"} - token = %Pleroma.Web.OAuth.Token{insert(:oauth_token) | user: user} + %Pleroma.Web.OAuth.Token{} = token = insert(:oauth_token) + token = %{token | user: user} assert Repo.get_assoc(token, :user) == {:ok, user} end diff --git a/test/pleroma/reverse_proxy_test.exs b/test/pleroma/reverse_proxy_test.exs index 8dbe9c6bf..ec4470379 100644 --- a/test/pleroma/reverse_proxy_test.exs +++ b/test/pleroma/reverse_proxy_test.exs @@ -294,7 +294,7 @@ defmodule Pleroma.ReverseProxyTest do |> expect(:stream_body, fn _ -> :done end) conn = ReverseProxy.call(conn, "/cache") - assert {"cache-control", "public, max-age=1209600"} in conn.resp_headers + assert {"cache-control", "public, max-age=1209600, immutable"} in conn.resp_headers end end diff --git a/test/pleroma/search/meilisearch_test.exs b/test/pleroma/search/meilisearch_test.exs index eea454323..ff32491c5 100644 --- a/test/pleroma/search/meilisearch_test.exs +++ b/test/pleroma/search/meilisearch_test.exs @@ -74,29 +74,6 @@ defmodule Pleroma.Search.MeilisearchTest do assert_received("posted_to_meilisearch") end - test "doesn't index posts that are not public" do - user = insert(:user) - - Enum.each(["private", "direct"], fn visibility -> - {:ok, activity} = - CommonAPI.post(user, %{ - status: "guys i just don't wanna leave the swamp", - visibility: visibility - }) - - args = %{"op" => "add_to_index", "activity" => activity.id} - - Config - |> expect(:get, fn - [Pleroma.Search, :module], nil -> - Meilisearch - end) - - assert_enqueued(worker: SearchIndexingWorker, args: args) - assert :ok = perform_job(SearchIndexingWorker, args) - end) - end - test "deletes posts from index when deleted locally" do user = insert(:user) diff --git a/test/pleroma/search_test.exs b/test/pleroma/search_test.exs new file mode 100644 index 000000000..d777bcda2 --- /dev/null +++ b/test/pleroma/search_test.exs @@ -0,0 +1,69 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.SearchTest do + use Pleroma.DataCase, async: true + use Oban.Testing, repo: Pleroma.Repo + + import Pleroma.Factory + + alias Pleroma.Web.CommonAPI + alias Pleroma.Workers.SearchIndexingWorker + + test "indexes posts that are public" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "Well this is a story all about how my life got flipped turned upside down", + visibility: "public" + }) + + args = %{"op" => "add_to_index", "activity" => activity.id} + + assert_enqueued(worker: SearchIndexingWorker, args: args) + end + + test "doesn't index posts that are not public" do + user = insert(:user) + + Enum.each(["private", "direct"], fn visibility -> + {:ok, activity} = + CommonAPI.post(user, %{ + status: "guys i just don't wanna leave the swamp", + visibility: visibility + }) + + args = %{"op" => "add_to_index", "activity" => activity.id} + + refute_enqueued(worker: SearchIndexingWorker, args: args) + end) + end + + test "Indexes appropriate activity types" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "I'm my own hype man", + visibility: "public" + }) + + args = %{"op" => "add_to_index", "activity" => activity.id} + + assert_enqueued(worker: SearchIndexingWorker, args: args) + + {:ok, fav_activity} = CommonAPI.favorite(activity.id, user) + + args = %{"op" => "add_to_index", "activity" => fav_activity.id} + + refute_enqueued(worker: SearchIndexingWorker, args: args) + + {:ok, repeat_activity} = CommonAPI.repeat(activity.id, user) + + args = %{"op" => "add_to_index", "activity" => repeat_activity.id} + + refute_enqueued(worker: SearchIndexingWorker, args: args) + end +end diff --git a/test/pleroma/upload/filter/exiftool/strip_location_test.exs b/test/pleroma/upload/filter/exiftool/strip_location_test.exs index 4dcd4dce3..485060215 100644 --- a/test/pleroma/upload/filter/exiftool/strip_location_test.exs +++ b/test/pleroma/upload/filter/exiftool/strip_location_test.exs @@ -25,8 +25,8 @@ defmodule Pleroma.Upload.Filter.Exiftool.StripLocationTest do assert Filter.Exiftool.StripLocation.filter(upload) == {:ok, :filtered} - {exif_original, 0} = System.cmd("exiftool", ["test/fixtures/DSCN0010.#{type}"]) - {exif_filtered, 0} = System.cmd("exiftool", ["test/fixtures/DSCN0010_tmp.#{type}"]) + {exif_original, 0} = System.cmd("exiftool", ["-m", "test/fixtures/DSCN0010.#{type}"]) + {exif_filtered, 0} = System.cmd("exiftool", ["-m", "test/fixtures/DSCN0010_tmp.#{type}"]) assert String.match?(exif_original, ~r/GPS/) refute String.match?(exif_filtered, ~r/GPS/) diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index 73f53db56..9aafc41a5 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -499,6 +499,35 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do "https://queef.in/storage/banner.gif" end + test "works with alsoKnownAs as string" do + user_id = "https://hub.netzgemeinde.eu/channel/jupiter_rowland" + + user_data = + "test/fixtures/users_mock/hubzilla-actor-alsoknownas-string.json" + |> File.read!() + + user_data_decoded = + user_data + |> Jason.decode!() + + Tesla.Mock.mock(fn + %{ + method: :get, + url: ^user_id + } -> + %Tesla.Env{ + status: 200, + body: user_data, + headers: [{"content-type", "application/activity+json"}] + } + end) + + {:ok, user} = ActivityPub.make_user_from_ap_id(user_id) + + assert is_list(user.also_known_as) + assert user.also_known_as == [user_data_decoded["alsoKnownAs"]] + end + test "it fetches the appropriate tag-restricted posts" do user = insert(:user) @@ -1495,8 +1524,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do %{test_file: test_file} end - test "strips / from filename", %{test_file: file} do - file = %Plug.Upload{file | filename: "../../../../../nested/bad.jpg"} + test "strips / from filename", %{test_file: %Plug.Upload{} = file} do + file = %{file | filename: "../../../../../nested/bad.jpg"} {:ok, %Object{} = object} = ActivityPub.upload(file) [%{"href" => href}] = object.data["url"] assert Regex.match?(~r"/bad.jpg$", href) @@ -1754,13 +1783,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do test "fetch_activities/2 returns activities addressed to a list " do user = insert(:user) member = insert(:user) - {:ok, list} = Pleroma.List.create("foo", user) + {:ok, list} = Pleroma.List.create(%{title: "foo"}, user) {:ok, list} = Pleroma.List.follow(list, member) - {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"}) + {:ok, %Activity{} = activity} = + CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"}) activity = Repo.preload(activity, :bookmark) - activity = %Activity{activity | thread_muted?: !!activity.thread_muted?} + activity = %{activity | thread_muted?: !!activity.thread_muted?} assert ActivityPub.fetch_activities([], %{user: user}) == [activity] end @@ -1960,7 +1990,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert User.following?(follower, old_user) assert User.following?(follower_move_opted_out, old_user) - assert {:ok, activity} = ActivityPub.move(old_user, new_user) + assert {:ok, %Activity{} = activity} = ActivityPub.move(old_user, new_user) assert %Activity{ actor: ^old_ap_id, @@ -1992,7 +2022,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert User.following?(follower_move_opted_out, old_user) refute User.following?(follower_move_opted_out, new_user) - activity = %Activity{activity | object: nil} + activity = %{activity | object: nil} assert [%Notification{activity: ^activity}] = Notification.for_user(follower) diff --git a/test/pleroma/web/activity_pub/mrf/hashtag_policy_test.exs b/test/pleroma/web/activity_pub/mrf/hashtag_policy_test.exs index 32991c966..e231f88a8 100644 --- a/test/pleroma/web/activity_pub/mrf/hashtag_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/hashtag_policy_test.exs @@ -15,7 +15,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicyTest do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "#nsfw hey"}) - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, modified} = Transmogrifier.prepare_activity(activity.data) assert modified["object"]["sensitive"] end @@ -94,7 +94,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicyTest do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "#cofe hey"}) - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, modified} = Transmogrifier.prepare_activity(activity.data) refute modified["object"]["sensitive"] end diff --git a/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs b/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs index 0da3afa3b..4b94b9ac7 100644 --- a/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs @@ -54,14 +54,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do setup do: clear_config([:media_proxy, :enabled], true) test "it prefetches media proxy URIs" do - Tesla.Mock.mock(fn %{method: :get, url: "http://example.com/image.jpg"} -> - {:ok, %Tesla.Env{status: 200, body: ""}} - end) - - with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do + with_mock HTTP, + get: fn _, _, opts -> + send(self(), {:prefetch_opts, opts}) + {:ok, []} + end do MediaProxyWarmingPolicy.filter(@message) assert called(HTTP.get(:_, :_, :_)) + assert_receive {:prefetch_opts, opts} + refute Keyword.has_key?(opts, :follow_redirect) + refute Keyword.has_key?(opts, :force_redirect) end end @@ -81,10 +84,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do end test "history-aware" do - Tesla.Mock.mock(fn %{method: :get, url: "http://example.com/image.jpg"} -> - {:ok, %Tesla.Env{status: 200, body: ""}} - end) - with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do MRF.filter_one(MediaProxyWarmingPolicy, @message_with_history) @@ -93,10 +92,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do end test "works with Updates" do - Tesla.Mock.mock(fn %{method: :get, url: "http://example.com/image.jpg"} -> - {:ok, %Tesla.Env{status: 200, body: ""}} - end) - with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do MRF.filter_one(MediaProxyWarmingPolicy, @message_with_history |> Map.put("type", "Update")) diff --git a/test/pleroma/web/activity_pub/mrf/remote_report_policy_test.exs b/test/pleroma/web/activity_pub/mrf/remote_report_policy_test.exs index 8d2a6b4fa..270c650e0 100644 --- a/test/pleroma/web/activity_pub/mrf/remote_report_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/remote_report_policy_test.exs @@ -1,5 +1,5 @@ defmodule Pleroma.Web.ActivityPub.MRF.RemoteReportPolicyTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false alias Pleroma.Web.ActivityPub.MRF.RemoteReportPolicy diff --git a/test/pleroma/web/activity_pub/mrf_test.exs b/test/pleroma/web/activity_pub/mrf_test.exs index 25548e3da..2d0f3b317 100644 --- a/test/pleroma/web/activity_pub/mrf_test.exs +++ b/test/pleroma/web/activity_pub/mrf_test.exs @@ -10,18 +10,25 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do alias Pleroma.Web.ActivityPub.MRF + defp regexes_match!([], []), do: true + + defp regexes_match!([authority | authority_rest], [checked | checked_rest]) do + authority.source == checked.source and regexes_match!(authority_rest, checked_rest) + end + + defp regexes_match!(_, _), do: false + test "subdomains_regex/1" do - assert MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"]) == [ - ~r/^unsafe.tld$/i, - ~r/^(.*\.)*unsafe.tld$/i - ] + regexes = MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"]) + + assert regexes_match!(regexes, [~r/^unsafe.tld$/i, ~r/^(.*\.)*unsafe.tld$/i]) end describe "subdomain_match/2" do test "common domains" do regexes = MRF.subdomains_regex(["unsafe.tld", "unsafe2.tld"]) - assert regexes == [~r/^unsafe.tld$/i, ~r/^unsafe2.tld$/i] + assert regexes_match!(regexes, [~r/^unsafe.tld$/i, ~r/^unsafe2.tld$/i]) assert MRF.subdomain_match?(regexes, "unsafe.tld") assert MRF.subdomain_match?(regexes, "unsafe2.tld") @@ -32,7 +39,7 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do test "wildcard domains with one subdomain" do regexes = MRF.subdomains_regex(["*.unsafe.tld"]) - assert regexes == [~r/^(.*\.)*unsafe.tld$/i] + assert regexes_match!(regexes, [~r/^(.*\.)*unsafe.tld$/i]) assert MRF.subdomain_match?(regexes, "unsafe.tld") assert MRF.subdomain_match?(regexes, "sub.unsafe.tld") @@ -43,7 +50,7 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do test "wildcard domains with two subdomains" do regexes = MRF.subdomains_regex(["*.unsafe.tld"]) - assert regexes == [~r/^(.*\.)*unsafe.tld$/i] + assert regexes_match!(regexes, [~r/^(.*\.)*unsafe.tld$/i]) assert MRF.subdomain_match?(regexes, "unsafe.tld") assert MRF.subdomain_match?(regexes, "sub.sub.unsafe.tld") @@ -54,7 +61,7 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do test "matches are case-insensitive" do regexes = MRF.subdomains_regex(["UnSafe.TLD", "UnSAFE2.Tld"]) - assert regexes == [~r/^UnSafe.TLD$/i, ~r/^UnSAFE2.Tld$/i] + assert regexes_match!(regexes, [~r/^UnSafe.TLD$/i, ~r/^UnSAFE2.Tld$/i]) assert MRF.subdomain_match?(regexes, "UNSAFE.TLD") assert MRF.subdomain_match?(regexes, "UNSAFE2.TLD") diff --git a/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs index 5b2fcb26d..1ca4f04f6 100644 --- a/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs @@ -86,23 +86,32 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidationTest do object = Object.normalize(post_activity, fetch: false) # Another user can't announce it - {:ok, announce, []} = Builder.announce(announcer, object, public: false) + {:ok, announce, []} = Builder.announce(announcer, object, visibility: "private") {:error, cng} = ObjectValidator.validate(announce, []) assert {:actor, {"can not announce this object", []}} in cng.errors - # The actor of the object can announce it - {:ok, announce, []} = Builder.announce(user, object, public: false) + # The actor of the object can announce it with a restrictive scope + {:ok, announce, []} = Builder.announce(user, object, visibility: "private") + assert {:ok, _, _} = ObjectValidator.validate(announce, []) + {:ok, announce, []} = Builder.announce(user, object, visibility: "direct") assert {:ok, _, _} = ObjectValidator.validate(announce, []) # The actor of the object can not announce it publicly - {:ok, announce, []} = Builder.announce(user, object, public: true) + {:ok, announce, []} = Builder.announce(user, object, visibility: "public") + {:error, cng1} = ObjectValidator.validate(announce, []) - {:error, cng} = ObjectValidator.validate(announce, []) + {:ok, announce, []} = Builder.announce(user, object, visibility: "unlisted") + {:error, cng2} = ObjectValidator.validate(announce, []) - assert {:actor, {"can not announce this object publicly", []}} in cng.errors + {:ok, announce, []} = Builder.announce(user, object, visibility: "local") + {:error, cng3} = ObjectValidator.validate(announce, []) + + for cng <- [cng1, cng2, cng3] do + assert {:actor, {"can not announce this object publicly", []}} in cng.errors + end end end end diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs index 3c7ff0eeb..c32811c5b 100644 --- a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs @@ -67,7 +67,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest {:ok, edit} = Pleroma.Web.CommonAPI.update(activity, user, %{status: "edited :blank:"}) {:ok, %{"object" => external_rep}} = - Pleroma.Web.ActivityPub.Transmogrifier.prepare_outgoing(edit.data) + Pleroma.Web.ActivityPub.Transmogrifier.prepare_activity(edit.data) %{external_rep: external_rep} end diff --git a/test/pleroma/web/activity_pub/object_validators/update_handling_test.exs b/test/pleroma/web/activity_pub/object_validators/update_handling_test.exs index c88339d14..347c5b578 100644 --- a/test/pleroma/web/activity_pub/object_validators/update_handling_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/update_handling_test.exs @@ -133,7 +133,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateHandlingTest do user = insert(:user) {:ok, activity} = Pleroma.Web.CommonAPI.post(user, %{status: "mew mew :dinosaur:"}) {:ok, edit} = Pleroma.Web.CommonAPI.update(activity, user, %{status: "edited :blank:"}) - {:ok, external_rep} = Pleroma.Web.ActivityPub.Transmogrifier.prepare_outgoing(edit.data) + {:ok, external_rep} = Pleroma.Web.ActivityPub.Transmogrifier.prepare_activity(edit.data) %{external_rep: external_rep} end diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs index 1f9e0bfe5..447ffda1b 100644 --- a/test/pleroma/web/activity_pub/publisher_test.exs +++ b/test/pleroma/web/activity_pub/publisher_test.exs @@ -259,7 +259,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do ) end - test "Publishes with the new actor if prepare_outgoing changes the actor." do + test "Publishes with the new actor if prepare_activity changes the actor." do mock(fn %{method: :post, url: "https://domain.com/users/nick1/inbox", body: body} -> {:ok, %Tesla.Env{status: 200, body: body}} @@ -281,7 +281,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do ) Pleroma.Web.ActivityPub.TransmogrifierMock - |> Mox.expect(:prepare_outgoing, fn data -> + |> Mox.expect(:prepare_activity, fn data -> {:ok, Map.put(data, "actor", replaced_actor.ap_id)} end) @@ -334,7 +334,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do test "activity with BCC is published to a list member." do actor = insert(:user) - {:ok, list} = Pleroma.List.create("list", actor) + {:ok, list} = Pleroma.List.create(%{title: "list"}, actor) list_member = insert(:user, %{local: false}) Pleroma.List.follow(list, list_member) diff --git a/test/pleroma/web/activity_pub/side_effects_test.exs b/test/pleroma/web/activity_pub/side_effects_test.exs index 4a18cab68..6d20c591c 100644 --- a/test/pleroma/web/activity_pub/side_effects_test.exs +++ b/test/pleroma/web/activity_pub/side_effects_test.exs @@ -784,13 +784,15 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do {:ok, post} = CommonAPI.post(poster, %{status: "hey"}) {:ok, private_post} = CommonAPI.post(poster, %{status: "hey", visibility: "private"}) - {:ok, announce_data, _meta} = Builder.announce(user, post.object, public: true) + {:ok, announce_data, _meta} = Builder.announce(user, post.object, visibility: "public") {:ok, private_announce_data, _meta} = - Builder.announce(user, private_post.object, public: false) + Builder.announce(user, private_post.object, visibility: "private") {:ok, relay_announce_data, _meta} = - Builder.announce(Pleroma.Web.ActivityPub.Relay.get_actor(), post.object, public: true) + Builder.announce(Pleroma.Web.ActivityPub.Relay.get_actor(), post.object, + visibility: "public" + ) {:ok, announce, _meta} = ActivityPub.persist(announce_data, local: true) {:ok, private_announce, _meta} = ActivityPub.persist(private_announce_data, local: true) diff --git a/test/pleroma/web/activity_pub/transmogrifier/answer_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/answer_handling_test.exs index 39a1598a8..913d143a5 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/answer_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/answer_handling_test.exs @@ -72,7 +72,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.AnswerHandlingTest do |> Kernel.put_in(["object", "to"], user.ap_id) {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) - {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, data} = Transmogrifier.prepare_activity(activity.data) assert data["object"]["type"] == "Note" end diff --git a/test/pleroma/web/activity_pub/transmogrifier/emoji_tag_building_test.exs b/test/pleroma/web/activity_pub/transmogrifier/emoji_tag_building_test.exs index c632c199c..b91658ce4 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/emoji_tag_building_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/emoji_tag_building_test.exs @@ -1,14 +1,46 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.EmojiTagBuildingTest do use Pleroma.DataCase, async: true - alias Pleroma.Web.ActivityPub.Transmogrifier - test "it encodes the id to be a valid url" do name = "hanapog" url = "https://misskey.local.live/emojis/hana pog.png" - tag = Transmogrifier.build_emoji_tag({name, url}) + tag = Pleroma.Emoji.build_emoji_tag({name, url}) assert tag["id"] == "https://misskey.local.live/emojis/hana%20pog.png" end + + test "it does not double-encode already encoded urls" do + name = "hanapog" + url = "https://misskey.local.live/emojis/hana%20pog.png" + + tag = Pleroma.Emoji.build_emoji_tag({name, url}) + + assert tag["id"] == url + end + + test "it encodes disallowed path characters" do + name = "hanapog" + url = "https://example.com/emojis/hana[pog].png" + + tag = Pleroma.Emoji.build_emoji_tag({name, url}) + + assert tag["id"] == "https://example.com/emojis/hana%5Bpog%5D.png" + end + + test "local_url does not decode percent in filenames" do + url = Pleroma.Emoji.local_url("/emoji/hana%20pog.png") + + assert url == Pleroma.Web.Endpoint.url() <> "/emoji/hana%2520pog.png" + + tag = Pleroma.Emoji.build_emoji_tag({"hanapog", url}) + + assert tag["id"] == url + end + + test "local_url encodes question marks in filenames" do + url = Pleroma.Emoji.local_url("/emoji/file?name.png") + + assert url == Pleroma.Web.Endpoint.url() <> "/emoji/file%3Fname.png" + end end diff --git a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs index 403c98a2d..31b9a699d 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs @@ -508,7 +508,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do {:ok, activity} = Transmogrifier.handle_incoming(message) - {:ok, _} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, _} = Transmogrifier.prepare_activity(activity.data) end test "successfully reserializes a message with AS2 objects in IR" do @@ -537,7 +537,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do {:ok, activity} = Transmogrifier.handle_incoming(message) - {:ok, _} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, _} = Transmogrifier.prepare_activity(activity.data) end end diff --git a/test/pleroma/web/activity_pub/transmogrifier/question_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/question_handling_test.exs index d31070546..2a4e78cf0 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/question_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/question_handling_test.exs @@ -170,4 +170,23 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.QuestionHandlingTest do assert {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data) end + + test "it displays voters count for a poll" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "???", + poll: %{expires_in: 10, options: ["yes", "no"]} + }) + + object = Object.normalize(activity, fetch: false) + {:ok, _, _} = CommonAPI.vote(object, other_user, [1]) + + {:ok, modified} = Transmogrifier.prepare_activity(activity.data) + + refute Map.has_key?(modified["object"], "voters") + assert modified["object"]["votersCount"] == 1 + end end diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs index 6dc4423fa..c1e01557d 100644 --- a/test/pleroma/web/activity_pub/transmogrifier_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -433,7 +433,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do {:ok, announce_activity} = CommonAPI.repeat(activity.id, user) - {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data) + {:ok, modified} = Transmogrifier.prepare_activity(announce_activity.data) assert modified["object"]["content"] == "hey" assert modified["object"]["actor"] == modified["object"]["attributedTo"] @@ -448,7 +448,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do with_mock Pleroma.Notification, get_notified_from_activity: fn _, _ -> [] end do - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, modified} = Transmogrifier.prepare_activity(activity.data) object = modified["object"] @@ -474,7 +474,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, modified} = Transmogrifier.prepare_activity(activity.data) assert modified["@context"] == Utils.make_json_ld_header()["@context"] @@ -485,7 +485,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, modified} = Transmogrifier.prepare_activity(activity.data) assert modified["object"]["actor"] == modified["object"]["attributedTo"] end @@ -501,7 +501,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do "name" => "#2hu" } - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, modified} = Transmogrifier.prepare_activity(activity.data) assert modified["object"]["tag"] == [expected_tag] end @@ -524,7 +524,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do url: "https://pleroma.social" } == activity.object.data["generator"] - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, modified} = Transmogrifier.prepare_activity(activity.data) assert length(modified["object"]["tag"]) == 2 @@ -541,7 +541,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do test "it strips internal fields of article" do activity = insert(:article_activity) - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, modified} = Transmogrifier.prepare_activity(activity.data) assert length(modified["object"]["tag"]) == 2 @@ -558,13 +558,13 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do {:ok, activity} = CommonAPI.post(user, %{status: "2hu :moominmamma:"}) - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, modified} = Transmogrifier.prepare_activity(activity.data) assert modified["directMessage"] == false {:ok, activity} = CommonAPI.post(user, %{status: "@#{other_user.nickname} :moominmamma:"}) - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, modified} = Transmogrifier.prepare_activity(activity.data) assert modified["directMessage"] == false @@ -574,18 +574,18 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do visibility: "direct" }) - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, modified} = Transmogrifier.prepare_activity(activity.data) assert modified["directMessage"] == true end test "it strips BCC field" do user = insert(:user) - {:ok, list} = Pleroma.List.create("foo", user) + {:ok, list} = Pleroma.List.create(%{title: "foo"}, user) {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"}) - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, modified} = Transmogrifier.prepare_activity(activity.data) assert is_nil(modified["bcc"]) end @@ -594,7 +594,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do listen_activity = insert(:listen) # This has an inlined object as in ObjectView - {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data) + {:ok, modified} = Transmogrifier.prepare_activity(listen_activity.data) assert modified["type"] == "Listen" @@ -610,7 +610,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do object_type = activity.object.data["type"] # This does not have an inlined object - {:ok, modified2} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, modified2} = Transmogrifier.prepare_activity(activity.data) assert match?( %{ @@ -640,7 +640,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do {:ok, activity} = CommonAPI.post(user, %{status: "everybody do the dinosaur :dinosaur:"}) - {:ok, prepared} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, prepared} = Transmogrifier.prepare_activity(activity.data) assert length(prepared["object"]["tag"]) == 1 @@ -655,7 +655,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do {:ok, activity} = CommonAPI.post(user, %{status: "everybody do the dinosaur :dinosaur:"}) {:ok, update} = CommonAPI.update(activity, user, %{status: "mew mew :blank:"}) - {:ok, prepared} = Transmogrifier.prepare_outgoing(update.data) + {:ok, prepared} = Transmogrifier.prepare_activity(update.data) assert %{ "content" => "mew mew :blank:", @@ -689,7 +689,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do user_update_changeset: changeset ) - assert {:ok, prepared} = Transmogrifier.prepare_outgoing(activity.data) + assert {:ok, prepared} = Transmogrifier.prepare_activity(activity.data) assert prepared["type"] == "Update" assert prepared["@context"] assert prepared["object"]["type"] == user.actor_type @@ -704,7 +704,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do {:ok, %Activity{} = block_activity} = CommonAPI.block(blocked, blocker) {:ok, %Activity{} = undo_activity} = CommonAPI.unblock(blocked, blocker) - {:ok, data} = Transmogrifier.prepare_outgoing(undo_activity.data) + {:ok, data} = Transmogrifier.prepare_activity(undo_activity.data) block_ap_id = block_activity.data["id"] assert is_binary(block_ap_id) @@ -738,7 +738,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert is_binary(note_ap_id) {:ok, react_activity} = CommonAPI.react_with_emoji(note_activity.id, user, "🐈") - {:ok, data} = Transmogrifier.prepare_outgoing(react_activity.data) + {:ok, data} = Transmogrifier.prepare_activity(react_activity.data) assert match?( %{ @@ -759,13 +759,29 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do ) end + test "EmojiReact custom emoji urls are URI encoded" do + user = insert(:user, local: true) + note_activity = insert(:note_activity) + + {:ok, react_activity} = CommonAPI.react_with_emoji(note_activity.id, user, ":dinosaur:") + {:ok, data} = Transmogrifier.prepare_activity(react_activity.data) + + assert length(data["tag"]) == 1 + + tag = List.first(data["tag"]) + url = tag["icon"]["url"] + + assert url == "http://localhost:4001/emoji/dino%20walking.gif" + assert tag["id"] == "http://localhost:4001/emoji/dino%20walking.gif" + end + test "it prepares a quote post" do user = insert(:user) {:ok, quoted_post} = CommonAPI.post(user, %{status: "hey"}) {:ok, quote_post} = CommonAPI.post(user, %{status: "hey", quoted_status_id: quoted_post.id}) - {:ok, modified} = Transmogrifier.prepare_outgoing(quote_post.data) + {:ok, modified} = Transmogrifier.prepare_activity(quote_post.data) %{data: %{"id" => quote_id}} = Object.normalize(quoted_post) @@ -777,7 +793,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "Cześć", language: "pl"}) - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.object.data) + {:ok, modified} = Transmogrifier.prepare_activity(activity.object.data) assert [_, _, %{"@language" => "pl"}] = modified["@context"] end @@ -786,7 +802,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "Cześć", language: "pl"}) - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, modified} = Transmogrifier.prepare_activity(activity.data) assert [_, _, %{"@language" => "pl"}] = modified["@context"] end @@ -809,7 +825,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do content: content }) - {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, data} = Transmogrifier.prepare_activity(activity.data) expected_data = activity.data @@ -843,7 +859,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do clear_config([:activitypub, :anonymize_reporter], true) clear_config([:activitypub, :anonymize_reporter_local_nickname], placeholder.nickname) - {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, data} = Transmogrifier.prepare_activity(activity.data) expected_data = activity.data diff --git a/test/pleroma/web/activity_pub/utils_test.exs b/test/pleroma/web/activity_pub/utils_test.exs index a48639c38..3b77f0867 100644 --- a/test/pleroma/web/activity_pub/utils_test.exs +++ b/test/pleroma/web/activity_pub/utils_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.UtilsTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false alias Pleroma.Activity alias Pleroma.Object alias Pleroma.Repo @@ -671,6 +671,19 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do end end + describe "assign_report_to_account/2" do + test "assigns report to an account" do + reporter = insert(:user) + target_account = insert(:user) + %{id: assigned_id} = insert(:user) + + {:ok, report} = CommonAPI.report(reporter, %{account_id: target_account.id}) + {:ok, report} = Utils.assign_report_to_account(report, assigned_id) + + assert %{data: %{"assigned_account" => ^assigned_id}} = report + end + end + describe "maybe_anonymize_reporter/1" do setup do reporter = insert(:user) diff --git a/test/pleroma/web/activity_pub/views/user_view_test.exs b/test/pleroma/web/activity_pub/views/user_view_test.exs index 7ac5f7c0f..a3f807ca9 100644 --- a/test/pleroma/web/activity_pub/views/user_view_test.exs +++ b/test/pleroma/web/activity_pub/views/user_view_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.UserViewTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false import Pleroma.Factory alias Pleroma.User diff --git a/test/pleroma/web/activity_pub/visibility_test.exs b/test/pleroma/web/activity_pub/visibility_test.exs index fd3dc83a1..f92f2df16 100644 --- a/test/pleroma/web/activity_pub/visibility_test.exs +++ b/test/pleroma/web/activity_pub/visibility_test.exs @@ -17,7 +17,7 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do following = insert(:user) unrelated = insert(:user) {:ok, following, user} = Pleroma.User.follow(following, user) - {:ok, list} = Pleroma.List.create("foo", user) + {:ok, list} = Pleroma.List.create(%{title: "foo"}, user) Pleroma.List.follow(list, unrelated) diff --git a/test/pleroma/web/admin_api/controllers/config_controller_test.exs b/test/pleroma/web/admin_api/controllers/config_controller_test.exs index e12115ea1..e62d95fad 100644 --- a/test/pleroma/web/admin_api/controllers/config_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/config_controller_test.exs @@ -194,6 +194,16 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do setup do: clear_config(:configurable_from_database, true) + setup do: + clear_config(:database_config_whitelist, [ + {:pleroma}, + {:http}, + {:idna}, + {:oban}, + {:tesla}, + {:ueberauth} + ]) + test "create new config setting in db", %{conn: conn} do ueberauth = Application.get_env(:ueberauth, Ueberauth) on_exit(fn -> Application.put_env(:ueberauth, Ueberauth, ueberauth) end) @@ -807,7 +817,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do %{ "tuple" => [ "/websocket", - "Phoenix.Endpoint.CowboyWebSocket", + ":sth", %{ "tuple" => [ "Phoenix.Transports.WebSocket", @@ -871,7 +881,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do %{ "tuple" => [ "/websocket", - "Phoenix.Endpoint.CowboyWebSocket", + ":sth", %{ "tuple" => [ "Phoenix.Transports.WebSocket", @@ -1210,6 +1220,31 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do assert Application.get_env(:not_real, :anything) == "value6" end + test "doesn't allow updating the database_config_whitelist itself", %{conn: conn} do + original_whitelist = Pleroma.Config.get(:database_config_whitelist) + + refute ConfigDB.get_by_group_and_key(:pleroma, :database_config_whitelist) + + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/admin/config", %{ + configs: [ + %{ + group: ":pleroma", + key: ":database_config_whitelist", + value: [%{"tuple" => [":pleroma", ":key1"]}] + } + ] + }) + + %{"configs" => configs} = json_response_and_validate_schema(conn, 200) + + assert configs == [] + assert Pleroma.Config.get(:database_config_whitelist) == original_whitelist + refute ConfigDB.get_by_group_and_key(:pleroma, :database_config_whitelist) + end + test "args for Pleroma.Upload.Filter.Mogrify with custom tuples", %{conn: conn} do assert conn |> put_req_header("content-type", "application/json") @@ -1472,5 +1507,13 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do web_endpoint = Enum.find(children, fn c -> c["key"] == "Pleroma.Upload" end) assert web_endpoint["children"] end + + test "all keys from description are whitelisted", %{conn: conn} do + conn = get(conn, "/api/pleroma/admin/config/descriptions") + + assert response = json_response_and_validate_schema(conn, 200) + + assert length(response) == length(Pleroma.Docs.JSON.compiled_descriptions()) + end end end diff --git a/test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs b/test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs index 10eefbeca..2c2d13bb7 100644 --- a/test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs @@ -57,6 +57,28 @@ defmodule Pleroma.Web.AdminAPI.OAuthAppControllerTest do } = response end + test "success with redirect_uris array", %{conn: conn} do + base_url = Endpoint.url() + app_name = "Trusted app" + + response = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/admin/oauth_app", %{ + name: app_name, + redirect_uris: [base_url] + }) + |> json_response_and_validate_schema(200) + + assert %{ + "client_id" => _, + "client_secret" => _, + "name" => ^app_name, + "redirect_uri" => ^base_url, + "trusted" => false + } = response + end + test "with trusted", %{conn: conn} do base_url = Endpoint.url() app_name = "Trusted app" diff --git a/test/pleroma/web/admin_api/controllers/report_controller_test.exs b/test/pleroma/web/admin_api/controllers/report_controller_test.exs index b626ddf55..9fbb608c4 100644 --- a/test/pleroma/web/admin_api/controllers/report_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/report_controller_test.exs @@ -388,6 +388,38 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do |> json_response_and_validate_schema(:ok) end + test "returns reports with specified assigned user", %{conn: conn, admin: admin} do + [reporter, target_user] = insert_pair(:user) + activity = insert(:note_activity, user: target_user) + + {:ok, _report} = + CommonAPI.report(reporter, %{ + account_id: target_user.id, + comment: "I feel offended", + status_ids: [activity.id] + }) + + {:ok, %{id: second_report_id}} = + CommonAPI.report(reporter, %{ + account_id: target_user.id, + comment: "I don't like this user" + }) + + CommonAPI.assign_report_to_account(second_report_id, admin.id) + + response = + conn + |> get(report_path(conn, :index, %{assigned_account: admin.id})) + |> json_response_and_validate_schema(:ok) + + assert [open_report] = response["reports"] + + assert length(response["reports"]) == 1 + assert open_report["id"] == second_report_id + + assert response["total"] == 1 + end + test "renders content correctly", %{conn: conn} do [reporter, target_user] = insert_pair(:user) note = insert(:note, user: target_user, data: %{"content" => "mew 1"}) @@ -467,6 +499,66 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do end end + describe "POST /api/pleroma/admin/reports/assign_account" do + test "assigns account to report", %{conn: conn, admin: admin} do + [reporter, target_user] = insert_pair(:user) + activity = insert(:note_activity, user: target_user) + + {:ok, %{id: report_id}} = + CommonAPI.report(reporter, %{ + account_id: target_user.id, + status_ids: [activity.id] + }) + + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/admin/reports/assign_account", %{ + "reports" => [ + %{"assigned_account" => admin.nickname, "id" => report_id} + ] + }) + |> json_response_and_validate_schema(:no_content) + + activity = Activity.get_by_id_with_user_actor(report_id) + assert activity.data["assigned_account"] == admin.id + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} assigned report ##{report_id} (on user @#{activity.user_actor.nickname}) to user #{admin.nickname}" + end + + test "unassigns account from report", %{conn: conn, admin: admin} do + [reporter, target_user] = insert_pair(:user) + activity = insert(:note_activity, user: target_user) + + {:ok, %{id: report_id}} = + CommonAPI.report(reporter, %{ + account_id: target_user.id, + status_ids: [activity.id] + }) + + CommonAPI.assign_report_to_account(report_id, admin.id) + + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/admin/reports/assign_account", %{ + "reports" => [ + %{"assigned_account" => nil, "id" => report_id} + ] + }) + |> json_response_and_validate_schema(:no_content) + + activity = Activity.get_by_id_with_user_actor(report_id) + assert activity.data["assigned_account"] == nil + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} unassigned report ##{report_id} (on user @#{activity.user_actor.nickname}) from a user" + end + end + describe "POST /api/pleroma/admin/reports/:id/notes" do setup %{conn: conn, admin: admin} do clear_config([:instance, :admin_privileges], [:reports_manage_reports]) diff --git a/test/pleroma/web/admin_api/views/report_view_test.exs b/test/pleroma/web/admin_api/views/report_view_test.exs index 1b16aca6a..6e155ef58 100644 --- a/test/pleroma/web/admin_api/views/report_view_test.exs +++ b/test/pleroma/web/admin_api/views/report_view_test.exs @@ -36,6 +36,7 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do }), AdminAPI.AccountView.render("show.json", %{user: other_user}) ), + assigned_account: nil, statuses: [], notes: [], state: "open", @@ -75,6 +76,7 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do }), AdminAPI.AccountView.render("show.json", %{user: other_user}) ), + assigned_account: nil, statuses: [StatusView.render("show.json", %{activity: activity})], state: "open", notes: [], diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs index 27b1da1e3..d0cbc3111 100644 --- a/test/pleroma/web/common_api/utils_test.exs +++ b/test/pleroma/web/common_api/utils_test.exs @@ -647,7 +647,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do describe "maybe_add_list_data/3" do test "adds list params when found user list" do user = insert(:user) - {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", user) + {:ok, %Pleroma.List{} = list} = Pleroma.List.create(%{title: "title"}, user) assert Utils.maybe_add_list_data(%{additional: %{}, object: %{}}, user, {:list, list.id}) == %{ @@ -658,7 +658,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do test "returns original params when list not found" do user = insert(:user) - {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", insert(:user)) + {:ok, %Pleroma.List{} = list} = Pleroma.List.create(%{title: "title"}, insert(:user)) assert Utils.maybe_add_list_data(%{additional: %{}, object: %{}}, user, {:list, list.id}) == %{additional: %{}, object: %{}} diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index 52829b734..017fac696 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -759,7 +759,7 @@ defmodule Pleroma.Web.CommonAPITest do test "it allows to address a list" do user = insert(:user) - {:ok, list} = Pleroma.List.create("foo", user) + {:ok, list} = Pleroma.List.create(%{title: "foo"}, user) {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"}) @@ -1458,6 +1458,29 @@ defmodule Pleroma.Web.CommonAPITest do } } = flag_activity end + + test "assigns report to an account" do + [reporter, target_user] = insert_pair(:user) + %{id: assigned} = insert(:user) + + {:ok, %Activity{id: report_id}} = CommonAPI.report(reporter, %{account_id: target_user.id}) + + {:ok, activity} = CommonAPI.assign_report_to_account(report_id, assigned) + + assert %{data: %{"assigned_account" => ^assigned}} = activity + end + + test "unassigns report from account" do + [reporter, target_user] = insert_pair(:user) + %{id: assigned} = insert(:user) + + {:ok, %Activity{id: report_id}} = CommonAPI.report(reporter, %{account_id: target_user.id}) + + CommonAPI.assign_report_to_account(report_id, assigned) + {:ok, activity} = CommonAPI.assign_report_to_account(report_id, nil) + + refute Map.has_key?(activity.data, "assigned_account") + end end describe "reblog muting" do diff --git a/test/pleroma/web/fallback_test.exs b/test/pleroma/web/fallback_test.exs index 9184cf8f1..bc3052959 100644 --- a/test/pleroma/web/fallback_test.exs +++ b/test/pleroma/web/fallback_test.exs @@ -77,6 +77,16 @@ defmodule Pleroma.Web.FallbackTest do assert redirected_to(get(conn, "/pleroma/admin")) =~ "/pleroma/admin/" end + test "GET /phoenix/live_dashboard -> /pleroma/live_dashboard", %{conn: conn} do + assert redirected_to(get(conn, "/phoenix/live_dashboard")) =~ "/pleroma/live_dashboard" + assert redirected_to(get(conn, "/phoenix/live_dashboard/")) =~ "/pleroma/live_dashboard/" + end + + test "GET /phoenix/live_dashboard/* -> /pleroma/live_dashboard/*", %{conn: conn} do + assert redirected_to(get(conn, "/phoenix/live_dashboard/ecto_stats?nav=diagnose")) =~ + "/pleroma/live_dashboard/ecto_stats?nav=diagnose" + end + test "OPTIONS /*path", %{conn: conn} do assert conn |> options("/foo") diff --git a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs index 02da781dd..01e195842 100644 --- a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs @@ -1815,7 +1815,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do test "returns lists to which the account belongs" do %{user: user, conn: conn} = oauth_access(["read:lists"]) other_user = insert(:user) - assert {:ok, %Pleroma.List{id: _list_id} = list} = Pleroma.List.create("Test List", user) + + assert {:ok, %Pleroma.List{id: _list_id} = list} = + Pleroma.List.create(%{title: "Test List"}, user) + {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user) assert [%{"id" => _list_id, "title" => "Test List"}] = @@ -1901,7 +1904,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do {:ok, _user_relationships} = User.mute(user, other_user1) {:ok, _user_relationships} = User.mute(user, other_user2) - {:ok, _user_relationships} = User.mute(user, other_user3) + {:ok, _user_relationships} = User.mute(user, other_user3, %{duration: 24 * 60 * 60}) + + date = + DateTime.utc_now() + |> DateTime.add(24 * 60 * 60) + |> DateTime.truncate(:second) + |> DateTime.to_iso8601() result = conn @@ -1937,6 +1946,17 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do |> json_response_and_validate_schema(200) assert [%{"id" => ^id3}] = result + + result = + conn + |> get("/api/v1/mutes") + |> json_response_and_validate_schema(200) + + assert [ + %{"id" => ^id3, "mute_expires_at" => ^date}, + %{"id" => ^id2, "mute_expires_at" => nil}, + %{"id" => ^id1, "mute_expires_at" => nil} + ] = result end test "list of mutes with with_relationships parameter" do @@ -1951,20 +1971,44 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do {:ok, _} = User.mute(user, other_user1) {:ok, _} = User.mute(user, other_user2) - {:ok, _} = User.mute(user, other_user3) + {:ok, _} = User.mute(user, other_user3, %{duration: 24 * 60 * 60}) + + date = + DateTime.utc_now() + |> DateTime.add(24 * 60 * 60) + |> DateTime.truncate(:second) + |> DateTime.to_iso8601() assert [ %{ "id" => ^id3, - "pleroma" => %{"relationship" => %{"muting" => true, "followed_by" => true}} + "pleroma" => %{ + "relationship" => %{ + "muting" => true, + "mute_expires_at" => ^date, + "followed_by" => true + } + } }, %{ "id" => ^id2, - "pleroma" => %{"relationship" => %{"muting" => true, "followed_by" => true}} + "pleroma" => %{ + "relationship" => %{ + "muting" => true, + "mute_expires_at" => nil, + "followed_by" => true + } + } }, %{ "id" => ^id1, - "pleroma" => %{"relationship" => %{"muting" => true, "followed_by" => true}} + "pleroma" => %{ + "relationship" => %{ + "muting" => true, + "mute_expires_at" => nil, + "followed_by" => true + } + } } ] = conn @@ -1980,7 +2024,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do {:ok, _user_relationship} = User.block(user, other_user1) {:ok, _user_relationship} = User.block(user, other_user3) - {:ok, _user_relationship} = User.block(user, other_user2) + {:ok, _user_relationship} = User.block(user, other_user2, %{duration: 24 * 60 * 60}) + + date = + DateTime.utc_now() + |> DateTime.add(24 * 60 * 60) + |> DateTime.truncate(:second) + |> DateTime.to_iso8601() result = conn @@ -2045,6 +2095,18 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do |> json_response_and_validate_schema(200) assert [%{"id" => ^id1}] = result + + result = + conn + |> assign(:user, user) + |> get("/api/v1/blocks") + |> json_response_and_validate_schema(200) + + assert [ + %{"id" => ^id3, "block_expires_at" => nil}, + %{"id" => ^id2, "block_expires_at" => ^date}, + %{"id" => ^id1, "block_expires_at" => nil} + ] = result end test "list of blocks with with_relationships parameter" do @@ -2059,20 +2121,44 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do {:ok, _} = User.block(user, other_user1) {:ok, _} = User.block(user, other_user2) - {:ok, _} = User.block(user, other_user3) + {:ok, _} = User.block(user, other_user3, %{duration: 24 * 60 * 60}) + + date = + DateTime.utc_now() + |> DateTime.add(24 * 60 * 60) + |> DateTime.truncate(:second) + |> DateTime.to_iso8601() assert [ %{ "id" => ^id3, - "pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}} + "pleroma" => %{ + "relationship" => %{ + "blocking" => true, + "block_expires_at" => ^date, + "followed_by" => false + } + } }, %{ "id" => ^id2, - "pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}} + "pleroma" => %{ + "relationship" => %{ + "blocking" => true, + "block_expires_at" => nil, + "followed_by" => false + } + } }, %{ "id" => ^id1, - "pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}} + "pleroma" => %{ + "relationship" => %{ + "blocking" => true, + "block_expires_at" => nil, + "followed_by" => false + } + } } ] = conn diff --git a/test/pleroma/web/mastodon_api/controllers/app_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/app_controller_test.exs index bc9d4048c..45902d7d9 100644 --- a/test/pleroma/web/mastodon_api/controllers/app_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/app_controller_test.exs @@ -61,6 +61,33 @@ defmodule Pleroma.Web.MastodonAPI.AppControllerTest do assert app.user_id == nil end + test "creates an oauth app with redirect_uris array", %{conn: conn} do + app_attrs = build(:oauth_app) + + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/apps", %{ + client_name: app_attrs.client_name, + redirect_uris: [app_attrs.redirect_uris] + }) + + [app] = Repo.all(App) + + expected = %{ + "name" => app.client_name, + "website" => app.website, + "client_id" => app.client_id, + "client_secret" => app.client_secret, + "id" => app.id |> to_string(), + "redirect_uri" => app.redirect_uris, + "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key) + } + + assert expected == json_response_and_validate_schema(conn, 200) + assert app.user_id == nil + end + test "creates an oauth app with a user", %{conn: conn} do user = insert(:user) app_attrs = build(:oauth_app) diff --git a/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs index b7c7ccae0..276866d75 100644 --- a/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs @@ -10,6 +10,11 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do import Pleroma.Factory + defp extract_next_link_header(header) do + [_, next_link] = Regex.run(~r{<(?.*)>; rel="next"}, header) + next_link + end + describe "locked accounts" do setup do user = insert(:user, is_locked: true) @@ -31,6 +36,23 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do assert to_string(other_user.id) == relationship["id"] end + test "/api/v1/follow_requests paginates", %{user: user, conn: conn} do + for _ <- 1..21 do + other_user = insert(:user) + {:ok, _, _, _activity} = CommonAPI.follow(other_user, user) + {:ok, _, _} = User.follow(other_user, user, :follow_pending) + end + + conn = get(conn, "/api/v1/follow_requests") + assert length(json_response_and_validate_schema(conn, 200)) == 20 + assert [link_header] = get_resp_header(conn, "link") + assert link_header =~ "rel=\"next\"" + next_link = extract_next_link_header(link_header) + assert next_link =~ "/api/v1/follow_requests" + conn = get(conn, next_link) + assert length(json_response_and_validate_schema(conn, 200)) == 1 + end + test "/api/v1/follow_requests/:id/authorize works", %{user: user, conn: conn} do other_user = insert(:user) diff --git a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs index 10c0b6ea7..461b46066 100644 --- a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs @@ -153,6 +153,49 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do ] = result["rules"] end + describe "instance domain blocks" do + setup do + clear_config([:mrf_simple, :reject], [{"fediverse.pl", "uses pl-fe"}]) + end + + test "get instance domain blocks", %{conn: conn} do + conn = get(conn, "/api/v1/instance/domain_blocks") + + assert [ + %{ + "comment" => "uses pl-fe", + "digest" => "55e3f44aefe7eb022d3b1daaf7396cabf7f181bf6093c8ea841e30c9fc7d8226", + "domain" => "fediverse.pl", + "severity" => "suspend" + } + ] == json_response_and_validate_schema(conn, 200) + end + + test "omits comment field if comment is empty", %{conn: conn} do + clear_config([:mrf_simple, :reject], ["fediverse.pl"]) + + conn = get(conn, "/api/v1/instance/domain_blocks") + + assert [ + %{ + "digest" => "55e3f44aefe7eb022d3b1daaf7396cabf7f181bf6093c8ea841e30c9fc7d8226", + "domain" => "fediverse.pl", + "severity" => "suspend" + } = domain_block + ] = json_response_and_validate_schema(conn, 200) + + refute Map.has_key?(domain_block, "comment") + end + + test "returns empty array if mrf transparency is disabled", %{conn: conn} do + clear_config([:mrf, :transparency], false) + + conn = get(conn, "/api/v1/instance/domain_blocks") + + assert [] == json_response_and_validate_schema(conn, 200) + end + end + test "translation languages matrix", %{conn: conn} do clear_config([Pleroma.Language.Translation, :provider], TranslationMock) diff --git a/test/pleroma/web/mastodon_api/controllers/list_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/list_controller_test.exs index 430b8b89d..bfda48be4 100644 --- a/test/pleroma/web/mastodon_api/controllers/list_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/list_controller_test.exs @@ -56,7 +56,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do %{user: user, conn: conn} = oauth_access(["write:lists"]) other_user = insert(:user) third_user = insert(:user) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) assert %{} == conn @@ -77,7 +77,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do other_user = insert(:user) third_user = insert(:user) fourth_user = insert(:user) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) {:ok, list} = Pleroma.List.follow(list, other_user) {:ok, list} = Pleroma.List.follow(list, third_user) {:ok, list} = Pleroma.List.follow(list, fourth_user) @@ -98,7 +98,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do %{user: user, conn: conn} = oauth_access(["write:lists"]) other_user = insert(:user) third_user = insert(:user) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) {:ok, list} = Pleroma.List.follow(list, other_user) {:ok, list} = Pleroma.List.follow(list, third_user) @@ -115,7 +115,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do test "listing users in a list" do %{user: user, conn: conn} = oauth_access(["read:lists"]) other_user = insert(:user) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) {:ok, list} = Pleroma.List.follow(list, other_user) conn = @@ -129,7 +129,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do test "retrieving a list" do %{user: user, conn: conn} = oauth_access(["read:lists"]) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) conn = conn @@ -150,7 +150,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do test "renaming a list" do %{user: user, conn: conn} = oauth_access(["write:lists"]) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) assert %{"title" => "newname"} = conn @@ -161,7 +161,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do test "validates title when renaming a list" do %{user: user, conn: conn} = oauth_access(["write:lists"]) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) conn = conn @@ -175,7 +175,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do test "deleting a list" do %{user: user, conn: conn} = oauth_access(["write:lists"]) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) conn = delete(conn, "/api/v1/lists/#{list.id}") diff --git a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs index ae86078d7..fc083fd0e 100644 --- a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs @@ -129,8 +129,8 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do assert :ok == File.rm(Path.absname("test/tmp/large_binary.data")) end - test "Do not allow nested filename", %{conn: conn, image: image} do - image = %Plug.Upload{ + test "Do not allow nested filename", %{conn: conn, image: %Plug.Upload{} = image} do + image = %{ image | filename: "../../../../../nested/file.jpg" } diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index 298e92366..11e96a6ac 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -3068,7 +3068,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do |> json_response_and_validate_schema(:ok) {:ok, a_expires_at, 0} = DateTime.from_iso8601(a_expires_at) - assert DateTime.diff(expires_at, a_expires_at) == 0 + + assert DateTime.diff( + DateTime.truncate(expires_at, :second), + DateTime.truncate(a_expires_at, :second) + ) == 0 %{conn: conn} = oauth_access(["read:statuses"]) diff --git a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs index 4d646509c..b685f4ff8 100644 --- a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs @@ -149,6 +149,31 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do |> get("/api/v1/timelines/home?remote=true&local=true") |> json_response_and_validate_schema(200) == [] end + + test "the home timeline excludes posts from users in exclusive lists", %{ + user: user, + conn: conn + } do + other_user1 = insert(:user) + other_user2 = insert(:user) + + {:ok, user, other_user1} = User.follow(user, other_user1) + {:ok, user, other_user2} = User.follow(user, other_user2) + + {:ok, list} = Pleroma.List.create(%{title: "foo", exclusive: true}, user) + {:ok, _list} = Pleroma.List.follow(list, other_user1) + + {:ok, _activity} = CommonAPI.post(other_user1, %{status: "hi"}) + {:ok, %{id: activity2_id}} = CommonAPI.post(other_user2, %{status: "hi too"}) + + response = + conn + |> assign(:user, user) + |> get("/api/v1/timelines/home") + |> json_response_and_validate_schema(200) + + assert [%{"id" => ^activity2_id}] = response + end end describe "public" do @@ -606,7 +631,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do {:ok, activity_two} = CommonAPI.post(other_user, %{status: "Marisa is stupid."}) {:ok, _} = CommonAPI.repeat(activity_one.id, other_user) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) {:ok, list} = Pleroma.List.follow(list, other_user) conn = get(conn, "/api/v1/timelines/list/#{list.id}") @@ -618,7 +643,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do test "works with pagination", %{user: user, conn: conn} do other_user = insert(:user) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) {:ok, list} = Pleroma.List.follow(list, other_user) Enum.each(1..30, fn i -> @@ -644,7 +669,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do other_user = insert(:user) {:ok, _activity_one} = CommonAPI.post(user, %{status: "Marisa is cute."}) {:ok, activity_two} = CommonAPI.post(other_user, %{status: "Marisa is cute."}) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) {:ok, list} = Pleroma.List.follow(list, other_user) conn = get(conn, "/api/v1/timelines/list/#{list.id}") @@ -667,7 +692,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do visibility: "private" }) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) {:ok, list} = Pleroma.List.follow(list, other_user) conn = get(conn, "/api/v1/timelines/list/#{list.id}") @@ -685,7 +710,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do {:ok, _} = CommonAPI.react_with_emoji(activity.id, user3, "🎅") User.mute(user, user3) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) {:ok, list} = Pleroma.List.follow(list, user2) result = @@ -716,7 +741,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do end test "filtering", %{user: user, conn: conn} do - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) local_user = insert(:user) {:ok, local_activity} = CommonAPI.post(local_user, %{status: "Marisa is stupid."}) diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs index 5d24c0e9f..f34a801c1 100644 --- a/test/pleroma/web/mastodon_api/views/account_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs @@ -54,8 +54,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do note: "valid html. a
b
c
d
f '&<>"", url: user.ap_id, avatar: "http://localhost:4001/images/avi.png", + avatar_description: "", avatar_static: "http://localhost:4001/images/avi.png", header: "http://localhost:4001/images/banner.png", + header_description: "", header_static: "http://localhost:4001/images/banner.png", emojis: [ %{ @@ -105,6 +107,25 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do assert expected == AccountView.render("show.json", %{user: user, skip_visibility_check: true}) end + test "encodes emoji urls in the emojis field" do + user = + insert(:user, + name: ":brackets: :percent:", + emoji: %{ + "brackets" => "/emoji/hana[pog].png", + "percent" => "/emoji/hana%20pog.png" + } + ) + + %{emojis: emojis} = + AccountView.render("show.json", %{user: user, skip_visibility_check: true}) + + emoji_urls = Map.new(emojis, &{&1.shortcode, &1.url}) + + assert emoji_urls["brackets"] == "/emoji/hana%5Bpog%5D.png" + assert emoji_urls["percent"] == "/emoji/hana%2520pog.png" + end + describe "roles and privileges" do setup do clear_config([:instance, :moderator_privileges], [:cofe, :only_moderator]) @@ -307,8 +328,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do note: user.bio, url: user.ap_id, avatar: "http://localhost:4001/images/avi.png", + avatar_description: "", avatar_static: "http://localhost:4001/images/avi.png", header: "http://localhost:4001/images/banner.png", + header_description: "", header_static: "http://localhost:4001/images/banner.png", emojis: [], fields: [], @@ -420,8 +443,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do following: false, followed_by: false, blocking: false, + block_expires_at: nil, blocked_by: false, muting: false, + mute_expires_at: nil, muting_notifications: false, subscribing: false, notifying: false, @@ -517,6 +542,53 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do test_relationship_rendering(user, other_user, expected) end + test "represent a relationship for the blocking and blocked user with expiry" do + user = insert(:user) + other_user = insert(:user) + date = DateTime.utc_now() |> DateTime.add(24 * 60 * 60) |> DateTime.truncate(:second) + + {:ok, user, other_user} = User.follow(user, other_user) + {:ok, _subscription} = User.subscribe(user, other_user) + {:ok, _user_relationship} = User.block(user, other_user, %{duration: 24 * 60 * 60}) + {:ok, _user_relationship} = User.block(other_user, user) + + expected = + Map.merge( + @blank_response, + %{ + following: false, + blocking: true, + block_expires_at: date, + blocked_by: true, + id: to_string(other_user.id) + } + ) + + test_relationship_rendering(user, other_user, expected) + end + + test "represent a relationship for the muting user with expiry" do + user = insert(:user) + other_user = insert(:user) + date = DateTime.utc_now() |> DateTime.add(24 * 60 * 60) |> DateTime.truncate(:second) + + {:ok, _user_relationship} = + User.mute(user, other_user, %{notifications: true, duration: 24 * 60 * 60}) + + expected = + Map.merge( + @blank_response, + %{ + muting: true, + mute_expires_at: date, + muting_notifications: true, + id: to_string(other_user.id) + } + ) + + test_relationship_rendering(user, other_user, expected) + end + test "represent a relationship for the user blocking a domain" do user = insert(:user) other_user = insert(:user, ap_id: "https://bad.site/users/other_user") @@ -837,12 +909,37 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do User.mute(user, other_user, %{notifications: true, duration: 24 * 60 * 60}) %{ - mute_expires_at: mute_expires_at - } = AccountView.render("show.json", %{user: other_user, for: user, mutes: true}) + pleroma: %{ + relationship: %{ + mute_expires_at: mute_expires_at + } + } + } = AccountView.render("show.json", %{user: other_user, for: user, embed_relationships: true}) assert DateTime.diff( mute_expires_at, DateTime.utc_now() |> DateTime.add(24 * 60 * 60) ) in -3..3 end + + test "renders block expiration date" do + user = insert(:user) + other_user = insert(:user) + + {:ok, _user_relationships} = + User.block(user, other_user, %{duration: 24 * 60 * 60}) + + %{ + pleroma: %{ + relationship: %{ + block_expires_at: block_expires_at + } + } + } = AccountView.render("show.json", %{user: other_user, for: user, embed_relationships: true}) + + assert DateTime.diff( + block_expires_at, + DateTime.utc_now() |> DateTime.add(24 * 60 * 60) + ) in -3..3 + end end diff --git a/test/pleroma/web/mastodon_api/views/list_view_test.exs b/test/pleroma/web/mastodon_api/views/list_view_test.exs index bbf87bab2..ae0593b6b 100644 --- a/test/pleroma/web/mastodon_api/views/list_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/list_view_test.exs @@ -10,11 +10,12 @@ defmodule Pleroma.Web.MastodonAPI.ListViewTest do test "show" do user = insert(:user) title = "mortal enemies" - {:ok, list} = Pleroma.List.create(title, user) + {:ok, list} = Pleroma.List.create(%{title: title}, user) expected = %{ id: to_string(list.id), - title: title + title: title, + exclusive: false } assert expected == ListView.render("show.json", %{list: list}) @@ -23,10 +24,13 @@ defmodule Pleroma.Web.MastodonAPI.ListViewTest do test "index" do user = insert(:user) - {:ok, list} = Pleroma.List.create("my list", user) - {:ok, list2} = Pleroma.List.create("cofe", user) + {:ok, list} = Pleroma.List.create(%{title: "my list", exclusive: false}, user) + {:ok, list2} = Pleroma.List.create(%{title: "cofe", exclusive: true}, user) - assert [%{id: _, title: "my list"}, %{id: _, title: "cofe"}] = + assert [ + %{id: _, title: "my list", exclusive: false}, + %{id: _, title: "cofe", exclusive: true} + ] = ListView.render("index.json", lists: [list, list2]) end end diff --git a/test/pleroma/web/mastodon_api/views/notification_view_test.exs b/test/pleroma/web/mastodon_api/views/notification_view_test.exs index ce5ddd0fc..c8287bb79 100644 --- a/test/pleroma/web/mastodon_api/views/notification_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/notification_view_test.exs @@ -219,7 +219,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do data: %{ "reactions" => [ ["👍", [user.ap_id], nil], - ["dinosaur", [user.ap_id], "http://localhost:4001/emoji/dino walking.gif"] + ["dinosaur", [user.ap_id], "http://localhost:4001/emoji/dino%20walking.gif"] ] } ) @@ -243,7 +243,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do account: AccountView.render("show.json", %{user: other_user, for: user}), status: StatusView.render("show.json", %{activity: activity, for: user}), created_at: Utils.to_masto_date(notification.inserted_at), - emoji_url: "http://localhost:4001/emoji/dino walking.gif" + emoji_url: "http://localhost:4001/emoji/dino%20walking.gif" } test_notifications_rendering([notification], user, [expected]) diff --git a/test/pleroma/web/mastodon_api/views/poll_view_test.exs b/test/pleroma/web/mastodon_api/views/poll_view_test.exs index 6de001421..16281393d 100644 --- a/test/pleroma/web/mastodon_api/views/poll_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/poll_view_test.exs @@ -167,7 +167,14 @@ defmodule Pleroma.Web.MastodonAPI.PollViewTest do } = PollView.render("show.json", %{object: object}) end - test "that poll is non anonymous" do + test "displays correct voters count" do + object = Object.normalize("https://friends.grishka.me/posts/54642", fetch: true) + result = PollView.render("show.json", %{object: object}) + + assert result[:voters_count] == 14 + end + + test "detects that poll is non anonymous" do object = Object.normalize("https://friends.grishka.me/posts/54642", fetch: true) result = PollView.render("show.json", %{object: object}) diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index d7908886b..76123cd0f 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -54,7 +54,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do count: 2, me: false, name: "dinosaur", - url: "http://localhost:4001/emoji/dino walking.gif", + url: "http://localhost:4001/emoji/dino%20walking.gif", account_ids: [other_user.id, user.id] }, %{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]} @@ -70,7 +70,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do count: 2, me: true, name: "dinosaur", - url: "http://localhost:4001/emoji/dino walking.gif", + url: "http://localhost:4001/emoji/dino%20walking.gif", account_ids: [other_user.id, user.id] }, %{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]} @@ -909,7 +909,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do test "visibility/list" do user = insert(:user) - {:ok, list} = Pleroma.List.create("foo", user) + {:ok, list} = Pleroma.List.create(%{title: "foo"}, user) {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"}) diff --git a/test/pleroma/web/twitter_api/password_controller_test.exs b/test/pleroma/web/o_auth/password_controller_test.exs similarity index 99% rename from test/pleroma/web/twitter_api/password_controller_test.exs rename to test/pleroma/web/o_auth/password_controller_test.exs index 26cca1345..bb61b24fd 100644 --- a/test/pleroma/web/twitter_api/password_controller_test.exs +++ b/test/pleroma/web/o_auth/password_controller_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.PasswordControllerTest do +defmodule Pleroma.Web.OAuth.PasswordControllerTest do use Pleroma.Web.ConnCase alias Pleroma.Config diff --git a/test/pleroma/web/twitter_api/controller_test.exs b/test/pleroma/web/o_auth/token_controller_test.exs similarity index 97% rename from test/pleroma/web/twitter_api/controller_test.exs rename to test/pleroma/web/o_auth/token_controller_test.exs index 494be9ec7..5c64cb394 100644 --- a/test/pleroma/web/twitter_api/controller_test.exs +++ b/test/pleroma/web/o_auth/token_controller_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.ControllerTest do +defmodule Pleroma.Web.OAuth.TokenControllerTest do use Pleroma.Web.ConnCase, async: true alias Pleroma.Repo diff --git a/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs index 79b805aca..8fcdb233b 100644 --- a/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs @@ -100,7 +100,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do "name" => "dinosaur", "count" => 1, "me" => true, - "url" => "http://localhost:4001/emoji/dino walking.gif", + "url" => "http://localhost:4001/emoji/dino%20walking.gif", "account_ids" => [other_user.id] } ] diff --git a/test/pleroma/web/twitter_api/util_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/util_controller_test.exs similarity index 85% rename from test/pleroma/web/twitter_api/util_controller_test.exs rename to test/pleroma/web/pleroma_api/controllers/util_controller_test.exs index d06ae71aa..6e817df56 100644 --- a/test/pleroma/web/twitter_api/util_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/util_controller_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do +defmodule Pleroma.Web.PleromaAPI.UtilControllerTest do use Pleroma.Web.ConnCase use Oban.Testing, repo: Pleroma.Repo @@ -182,153 +182,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do end end - describe "POST /main/ostatus - remote_subscribe/2" do - setup do: clear_config([:instance, :federating], true) - - test "renders subscribe form", %{conn: conn} do - user = insert(:user) - - response = - conn - |> post("/main/ostatus", %{"nickname" => user.nickname, "profile" => ""}) - |> response(:ok) - - refute response =~ "Could not find user" - assert response =~ "Remotely follow #{user.nickname}" - end - - test "renders subscribe form with error when user not found", %{conn: conn} do - response = - conn - |> post("/main/ostatus", %{"nickname" => "nickname", "profile" => ""}) - |> response(:ok) - - assert response =~ "Could not find user" - refute response =~ "Remotely follow" - end - - test "it redirect to webfinger url", %{conn: conn} do - user = insert(:user) - user2 = insert(:user, ap_id: "shp@social.heldscal.la") - - conn = - conn - |> post("/main/ostatus", %{ - "user" => %{"nickname" => user.nickname, "profile" => user2.ap_id} - }) - - assert redirected_to(conn) == - "https://social.heldscal.la/main/ostatussub?profile=#{user.ap_id}" - end - - test "it renders form with error when user not found", %{conn: conn} do - user2 = insert(:user, ap_id: "shp@social.heldscal.la") - - response = - conn - |> post("/main/ostatus", %{"user" => %{"nickname" => "jimm", "profile" => user2.ap_id}}) - |> response(:ok) - - assert response =~ "Something went wrong." - end - end - - describe "POST /main/ostatus - remote_subscribe/2 - with statuses" do - setup do: clear_config([:instance, :federating], true) - - test "renders subscribe form", %{conn: conn} do - user = insert(:user) - status = insert(:note_activity, %{user: user}) - status_id = status.id - - assert is_binary(status_id) - - response = - conn - |> post("/main/ostatus", %{"status_id" => status_id, "profile" => ""}) - |> response(:ok) - - refute response =~ "Could not find status" - assert response =~ "Interacting with" - end - - test "renders subscribe form with error when status not found", %{conn: conn} do - response = - conn - |> post("/main/ostatus", %{"status_id" => "somerandomid", "profile" => ""}) - |> response(:ok) - - assert response =~ "Could not find status" - refute response =~ "Interacting with" - end - - test "it redirect to webfinger url", %{conn: conn} do - user = insert(:user) - status = insert(:note_activity, %{user: user}) - status_id = status.id - status_ap_id = status.data["object"] - - assert is_binary(status_id) - assert is_binary(status_ap_id) - - user2 = insert(:user, ap_id: "shp@social.heldscal.la") - - conn = - conn - |> post("/main/ostatus", %{ - "status" => %{"status_id" => status_id, "profile" => user2.ap_id} - }) - - assert redirected_to(conn) == - "https://social.heldscal.la/main/ostatussub?profile=#{status_ap_id}" - end - - test "it renders form with error when status not found", %{conn: conn} do - user2 = insert(:user, ap_id: "shp@social.heldscal.la") - - response = - conn - |> post("/main/ostatus", %{ - "status" => %{"status_id" => "somerandomid", "profile" => user2.ap_id} - }) - |> response(:ok) - - assert response =~ "Something went wrong." - end - end - - describe "GET /main/ostatus - show_subscribe_form/2" do - setup do: clear_config([:instance, :federating], true) - - test "it works with users", %{conn: conn} do - user = insert(:user) - - response = - conn - |> get("/main/ostatus", %{"nickname" => user.nickname}) - |> response(:ok) - - refute response =~ "Could not find user" - assert response =~ "Remotely follow #{user.nickname}" - end - - test "it works with statuses", %{conn: conn} do - user = insert(:user) - status = insert(:note_activity, %{user: user}) - status_id = status.id - - assert is_binary(status_id) - - response = - conn - |> get("/main/ostatus", %{"status_id" => status_id}) - |> response(:ok) - - refute response =~ "Could not find status" - assert response =~ "Interacting with" - end - end - test "it returns new captcha", %{conn: conn} do with_mock Pleroma.Captcha, new: fn -> "test_captcha" end do diff --git a/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs b/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs index c78c03aba..8c3682798 100644 --- a/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs +++ b/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatMessageReferenceViewTest do alias Pleroma.NullCache - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false alias Pleroma.Chat alias Pleroma.Chat.MessageReference @@ -18,6 +18,11 @@ defmodule Pleroma.Web.PleromaAPI.ChatMessageReferenceViewTest do import Mox import Pleroma.Factory + setup do + Mox.stub_with(Pleroma.CachexMock, Pleroma.NullCache) + :ok + end + setup do: clear_config([:rich_media, :enabled], true) test "it displays a chat message" do diff --git a/test/pleroma/web/plugs/favicon_plug_test.exs b/test/pleroma/web/plugs/favicon_plug_test.exs new file mode 100644 index 000000000..520501250 --- /dev/null +++ b/test/pleroma/web/plugs/favicon_plug_test.exs @@ -0,0 +1,63 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2026 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.FaviconPlugTest do + use Pleroma.Web.ConnCase + + @dir "test/tmp/favicon_static" + + setup do + Pleroma.Backports.mkdir_p!(@dir) + + on_exit(fn -> File.rm_rf!(@dir) end) + end + + describe "default favicon" do + test "returns favicon", %{conn: conn} do + conn = get(conn, "/favicon.png") + body_size = byte_size(conn.resp_body) + + assert conn.status == 200 + assert body_size == 1583 + assert response_content_type(conn, :png) + end + + test "returns correct cache-control", %{conn: conn} do + conn = get(conn, "/favicon.png") + cache = get_resp_header(conn, "cache-control") + + assert conn.status == 200 + assert cache == ["public, max=age=86400, immutable"] + end + end + + describe "custom favicon" do + setup do + favicon_path = Path.join(@dir, "favicon.png") + donor_image = "test/fixtures/image.png" + + File.cp!(donor_image, favicon_path) + clear_config([:instance, :static_dir], @dir) + + on_exit(fn -> File.rm!(favicon_path) end) + end + + test "returns favicon", %{conn: conn} do + conn = get(conn, "/favicon.png") + body_size = byte_size(conn.resp_body) + + assert conn.status == 200 + assert body_size == 104_426 + assert response_content_type(conn, :png) + end + + test "returns correct cache-control", %{conn: conn} do + conn = get(conn, "/favicon.png") + cache = get_resp_header(conn, "cache-control") + + assert conn.status == 200 + assert cache == ["public, max=age=86400, immutable"] + end + end +end diff --git a/test/pleroma/web/plugs/frontend_static_plug_test.exs b/test/pleroma/web/plugs/frontend_static_plug_test.exs index cbe200738..b7c06eacd 100644 --- a/test/pleroma/web/plugs/frontend_static_plug_test.exs +++ b/test/pleroma/web/plugs/frontend_static_plug_test.exs @@ -105,8 +105,8 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do "nodeinfo", "manifest.json", "auth", + "embed", "proxy", - "phoenix", "test", "user_exists", "check_password" diff --git a/test/pleroma/web/plugs/instance_static_test.exs b/test/pleroma/web/plugs/instance_static_test.exs index b5a5a3334..017c49d1e 100644 --- a/test/pleroma/web/plugs/instance_static_test.exs +++ b/test/pleroma/web/plugs/instance_static_test.exs @@ -137,4 +137,47 @@ defmodule Pleroma.Web.Plugs.InstanceStaticTest do # It should be preserved because "image" is in the allowed_mime_types list assert content_type == "image/jpeg" end + + describe "404s for missing files in static-only paths" do + test "returns 404 for non-existent static-only JSON files" do + conn = get(build_conn(), "/static/non-existent.json") + + assert conn.status == 404 + assert ["application/json"] = get_resp_header(conn, "content-type") + assert Jason.decode!(conn.resp_body) == %{"error" => "not found"} + end + + test "returns 404 for non-existent static-only non-JSON files" do + conn = get(build_conn(), "/static/non-existent.txt") + + assert conn.status == 404 + assert conn.resp_body == "Not found" + assert ["text/plain"] = get_resp_header(conn, "content-type") + end + + test "returns 404 for non-existent .css files" do + conn = get(build_conn(), "/static/non-existent.css") + + assert conn.status == 404 + assert conn.resp_body == "Not found" + # Verifies that we forced text/plain for the error body, even though the path was .css + assert ["text/plain"] = get_resp_header(conn, "content-type") + end + + test "returns 404 for non-existent files without an extension" do + conn = get(build_conn(), "/static/non-existent") + + assert conn.status == 404 + assert conn.resp_body == "Not found" + assert ["text/plain"] = get_resp_header(conn, "content-type") + end + + test "returns 200 (falls through to SPA) for non-static-only paths" do + # /some-route is NOT in static_only_files, so it should still fall through to the SPA. + conn = get(build_conn(), "/some-route") + + assert conn.status == 200 + assert ["text/html; charset=utf-8"] = get_resp_header(conn, "content-type") + end + end end diff --git a/test/pleroma/web/plugs/rate_limiter_test.exs b/test/pleroma/web/plugs/rate_limiter_test.exs index 19cee8aee..10c93fa73 100644 --- a/test/pleroma/web/plugs/rate_limiter_test.exs +++ b/test/pleroma/web/plugs/rate_limiter_test.exs @@ -268,6 +268,23 @@ defmodule Pleroma.Web.Plugs.RateLimiterTest do refute {:err, :not_found} == RateLimiter.inspect_bucket(conn, limiter_name, opts) end + test "doesn't crash if rate limit scale is invalid (e.g. broken DB config)" do + limiter_name = :test_invalid_rate_limit_config + + clear_config([:rate_limit, limiter_name], [{"", 0}, {"", ""}]) + clear_config([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) + + opts = RateLimiter.init(name: limiter_name) + + conn = %{build_conn(:get, "/") | remote_ip: {127, 0, 0, 1}} + + conn_limited = RateLimiter.call(conn, opts) + + refute conn_limited.status == Conn.Status.code(:too_many_requests) + refute conn_limited.resp_body + refute conn_limited.halted + end + def expire_ttl(%{remote_ip: remote_ip} = _conn, bucket_name_root) do bucket_name = "anon:#{bucket_name_root}" |> String.to_atom() key_name = "ip::#{remote_ip |> Tuple.to_list() |> Enum.join(".")}" diff --git a/test/pleroma/web/twitter_api/twitter_api_test.exs b/test/pleroma/web/registration_test.exs similarity index 90% rename from test/pleroma/web/twitter_api/twitter_api_test.exs rename to test/pleroma/web/registration_test.exs index b3cd80146..896e1a600 100644 --- a/test/pleroma/web/twitter_api/twitter_api_test.exs +++ b/test/pleroma/web/registration_test.exs @@ -2,14 +2,14 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do +defmodule Pleroma.Web.RegistrationTest do use Pleroma.DataCase import Pleroma.Factory alias Pleroma.Repo alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.UserInviteToken - alias Pleroma.Web.TwitterAPI.TwitterAPI + alias Pleroma.Web.Registration setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -25,7 +25,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :confirm => "bear" } - {:ok, user} = TwitterAPI.register_user(data) + {:ok, user} = Registration.register_user(data) assert user == User.get_cached_by_nickname("lain") end @@ -40,7 +40,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :confirm => "bear" } - {:ok, user} = TwitterAPI.register_user(data) + {:ok, user} = Registration.register_user(data) assert user == User.get_cached_by_nickname("lain") end @@ -57,7 +57,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :confirm => "bear" } - {:ok, user} = TwitterAPI.register_user(data) + {:ok, user} = Registration.register_user(data) ObanHelpers.perform_all() refute user.is_confirmed @@ -89,7 +89,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :reason => "I love anime" } - {:ok, user} = TwitterAPI.register_user(data) + {:ok, user} = Registration.register_user(data) ObanHelpers.perform_all() refute user.is_approved @@ -125,7 +125,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :confirm => "bear" } - {:ok, user1} = TwitterAPI.register_user(data1) + {:ok, user1} = Registration.register_user(data1) data2 = %{ :username => "lain", @@ -136,7 +136,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :confirm => "bear" } - {:ok, user2} = TwitterAPI.register_user(data2) + {:ok, user2} = Registration.register_user(data2) expected_text = ~s(@john test) @@ -160,7 +160,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => invite.token } - {:ok, user} = TwitterAPI.register_user(data) + {:ok, user} = Registration.register_user(data) assert user == User.get_cached_by_nickname("vinny") @@ -179,7 +179,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => "DudeLetMeInImAFairy" } - {:error, msg} = TwitterAPI.register_user(data) + {:error, msg} = Registration.register_user(data) assert msg == "Invalid token" refute User.get_cached_by_nickname("GrimReaper") @@ -199,7 +199,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => invite.token } - {:error, msg} = TwitterAPI.register_user(data) + {:error, msg} = Registration.register_user(data) assert msg == "Expired token" refute User.get_cached_by_nickname("GrimReaper") @@ -221,7 +221,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do check_fn = fn invite -> data = Map.put(data, :token, invite.token) - {:ok, user} = TwitterAPI.register_user(data) + {:ok, user} = Registration.register_user(data) assert user == User.get_cached_by_nickname("vinny") end @@ -254,7 +254,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do data = Map.put(data, "token", invite.token) - {:error, msg} = TwitterAPI.register_user(data) + {:error, msg} = Registration.register_user(data) assert msg == "Expired token" refute User.get_cached_by_nickname("vinny") @@ -282,7 +282,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => invite.token } - {:ok, user} = TwitterAPI.register_user(data) + {:ok, user} = Registration.register_user(data) assert user == User.get_cached_by_nickname("vinny") invite = Repo.get_by(UserInviteToken, token: invite.token) @@ -298,7 +298,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => invite.token } - {:error, msg} = TwitterAPI.register_user(data) + {:error, msg} = Registration.register_user(data) assert msg == "Expired token" refute User.get_cached_by_nickname("GrimReaper") @@ -321,7 +321,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => invite.token } - {:ok, user} = TwitterAPI.register_user(data) + {:ok, user} = Registration.register_user(data) assert user == User.get_cached_by_nickname("vinny") invite = Repo.get_by(UserInviteToken, token: invite.token) @@ -343,7 +343,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => invite.token } - {:ok, user} = TwitterAPI.register_user(data) + {:ok, user} = Registration.register_user(data) assert user == User.get_cached_by_nickname("vinny") invite = Repo.get_by(UserInviteToken, token: invite.token) @@ -359,7 +359,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => invite.token } - {:error, msg} = TwitterAPI.register_user(data) + {:error, msg} = Registration.register_user(data) assert msg == "Expired token" refute User.get_cached_by_nickname("GrimReaper") @@ -379,7 +379,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => invite.token } - {:error, msg} = TwitterAPI.register_user(data) + {:error, msg} = Registration.register_user(data) assert msg == "Expired token" refute User.get_cached_by_nickname("GrimReaper") @@ -401,7 +401,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => invite.token } - {:error, msg} = TwitterAPI.register_user(data) + {:error, msg} = Registration.register_user(data) assert msg == "Expired token" refute User.get_cached_by_nickname("GrimReaper") @@ -416,7 +416,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :bio => "close the world." } - {:error, error} = TwitterAPI.register_user(data) + {:error, error} = Registration.register_user(data) assert is_binary(error) refute User.get_cached_by_nickname("lain") diff --git a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs b/test/pleroma/web/remote_interaction/remote_interaction_controller_test.exs similarity index 68% rename from test/pleroma/web/twitter_api/remote_follow_controller_test.exs rename to test/pleroma/web/remote_interaction/remote_interaction_controller_test.exs index f762b1356..9236d1bf8 100644 --- a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs +++ b/test/pleroma/web/remote_interaction/remote_interaction_controller_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do +defmodule Pleroma.Web.RemoteInteraction.RemoteInteractionControllerTest do use Pleroma.Web.ConnCase alias Pleroma.MFA @@ -16,6 +16,11 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do import Mox import Pleroma.Factory + setup do + Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + setup_all do: clear_config([:instance, :federating], true) setup do: clear_config([:user, :deny_follow_blocked]) @@ -49,7 +54,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do assert conn |> get( - remote_follow_path(conn, :follow, %{ + remote_interaction_path(conn, :follow, %{ acct: "https://mastodon.social/users/emelie/statuses/101849165031453009" }) ) @@ -78,7 +83,9 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn - |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"})) + |> get( + remote_interaction_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"}) + ) |> html_response(200) assert response =~ "Log in to follow" @@ -109,7 +116,9 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn |> assign(:user, user) - |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"})) + |> get( + remote_interaction_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"}) + ) |> html_response(200) assert response =~ "Remote follow" @@ -130,7 +139,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do conn |> assign(:user, user) |> get( - remote_follow_path(conn, :follow, %{ + remote_interaction_path(conn, :follow, %{ acct: "https://mastodon.social/users/not_found" }) ) @@ -152,7 +161,9 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do conn |> assign(:user, user) |> assign(:token, read_token) - |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> post(remote_interaction_path(conn, :do_follow), %{ + "user" => %{"id" => user2.id} + }) |> response(200) assert response =~ "Error following account" @@ -167,7 +178,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do conn |> assign(:user, user) |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:follows"])) - |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> post(remote_interaction_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) assert redirected_to(conn) == "/users/#{user2.id}" end @@ -179,7 +190,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn |> assign(:user, user) - |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> post(remote_interaction_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) |> response(200) assert response =~ "Error following account" @@ -195,7 +206,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn |> assign(:user, user) - |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> post(remote_interaction_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) |> response(200) assert response =~ "Error following account" @@ -207,7 +218,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn |> assign(:user, user) - |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => "jimm"}}) + |> post(remote_interaction_path(conn, :do_follow), %{"user" => %{"id" => "jimm"}}) |> response(200) assert response =~ "Error following account" @@ -222,7 +233,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do conn |> assign(:user, refresh_record(user)) |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:follows"])) - |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> post(remote_interaction_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) assert redirected_to(conn) == "/users/#{user2.id}" end @@ -244,7 +255,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(remote_interaction_path(conn, :do_follow), %{ "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} }) |> response(200) @@ -272,7 +283,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(remote_interaction_path(conn, :do_follow), %{ "authorization" => %{"name" => user.nickname, "password" => "test1", "id" => user2.id} }) |> response(200) @@ -300,7 +311,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do conn = conn |> post( - remote_follow_path(conn, :do_follow), + remote_interaction_path(conn, :do_follow), %{ "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id} } @@ -329,7 +340,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn |> post( - remote_follow_path(conn, :do_follow), + remote_interaction_path(conn, :do_follow), %{ "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id} } @@ -348,7 +359,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do conn = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(remote_interaction_path(conn, :do_follow), %{ "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} }) @@ -361,7 +372,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(remote_interaction_path(conn, :do_follow), %{ "authorization" => %{"name" => user.nickname, "password" => "test", "id" => "jimm"} }) |> response(200) @@ -374,7 +385,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(remote_interaction_path(conn, :do_follow), %{ "authorization" => %{"name" => "jimm", "password" => "test", "id" => user.id} }) |> response(200) @@ -388,7 +399,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(remote_interaction_path(conn, :do_follow), %{ "authorization" => %{"name" => user.nickname, "password" => "42", "id" => user2.id} }) |> response(200) @@ -404,7 +415,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(remote_interaction_path(conn, :do_follow), %{ "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} }) |> response(200) @@ -423,7 +434,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do avatar: %{"url" => [%{"href" => "https://remote.org/avatar.png"}]} }) - avatar_url = Pleroma.Web.TwitterAPI.RemoteFollowView.avatar_url(user) + avatar_url = Pleroma.Web.RemoteInteraction.RemoteInteractionView.avatar_url(user) assert avatar_url == "https://remote.org/avatar.png" end @@ -440,7 +451,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do avatar: %{"url" => [%{"href" => "https://remote.org/avatar.png"}]} }) - avatar_url = Pleroma.Web.TwitterAPI.RemoteFollowView.avatar_url(user) + avatar_url = Pleroma.Web.RemoteInteraction.RemoteInteractionView.avatar_url(user) url = Pleroma.Web.Endpoint.url() assert String.starts_with?(avatar_url, url) @@ -455,7 +466,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do avatar: %{"url" => [%{"href" => "#{Pleroma.Web.Endpoint.url()}/localuser/avatar.png"}]} }) - avatar_url = Pleroma.Web.TwitterAPI.RemoteFollowView.avatar_url(user) + avatar_url = Pleroma.Web.RemoteInteraction.RemoteInteractionView.avatar_url(user) assert avatar_url == "#{Pleroma.Web.Endpoint.url()}/localuser/avatar.png" end @@ -485,13 +496,162 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do conn = conn |> get( - remote_follow_path(conn, :authorize_interaction, %{ + remote_interaction_path(conn, :authorize_interaction, %{ uri: "https://mastodon.social/users/emelie" }) ) assert redirected_to(conn) == - remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"}) + remote_interaction_path(conn, :follow, %{ + acct: "https://mastodon.social/users/emelie" + }) + end + end + + describe "POST /main/ostatus - remote_subscribe/2" do + setup do: clear_config([:instance, :federating], true) + + test "renders subscribe form", %{conn: conn} do + user = insert(:user) + + response = + conn + |> post("/main/ostatus", %{"nickname" => user.nickname, "profile" => ""}) + |> response(:ok) + + refute response =~ "Could not find user" + assert response =~ "Remotely follow #{user.nickname}" + end + + test "renders subscribe form with error when user not found", %{conn: conn} do + response = + conn + |> post("/main/ostatus", %{"nickname" => "nickname", "profile" => ""}) + |> response(:ok) + + assert response =~ "Could not find user" + refute response =~ "Remotely follow" + end + + test "it redirect to webfinger url", %{conn: conn} do + user = insert(:user) + user2 = insert(:user, ap_id: "shp@social.heldscal.la") + + conn = + conn + |> post("/main/ostatus", %{ + "user" => %{"nickname" => user.nickname, "profile" => user2.ap_id} + }) + + assert redirected_to(conn) == + "https://social.heldscal.la/main/ostatussub?profile=#{user.ap_id}" + end + + test "it renders form with error when user not found", %{conn: conn} do + user2 = insert(:user, ap_id: "shp@social.heldscal.la") + + response = + conn + |> post("/main/ostatus", %{"user" => %{"nickname" => "jimm", "profile" => user2.ap_id}}) + |> response(:ok) + + assert response =~ "Something went wrong." + end + end + + describe "POST /main/ostatus - remote_subscribe/2 - with statuses" do + setup do: clear_config([:instance, :federating], true) + + test "renders subscribe form", %{conn: conn} do + user = insert(:user) + status = insert(:note_activity, %{user: user}) + status_id = status.id + + assert is_binary(status_id) + + response = + conn + |> post("/main/ostatus", %{"status_id" => status_id, "profile" => ""}) + |> response(:ok) + + refute response =~ "Could not find status" + assert response =~ "Interacting with" + end + + test "renders subscribe form with error when status not found", %{conn: conn} do + response = + conn + |> post("/main/ostatus", %{"status_id" => "somerandomid", "profile" => ""}) + |> response(:ok) + + assert response =~ "Could not find status" + refute response =~ "Interacting with" + end + + test "it redirect to webfinger url", %{conn: conn} do + user = insert(:user) + status = insert(:note_activity, %{user: user}) + status_id = status.id + status_ap_id = status.data["object"] + + assert is_binary(status_id) + assert is_binary(status_ap_id) + + user2 = insert(:user, ap_id: "shp@social.heldscal.la") + + conn = + conn + |> post("/main/ostatus", %{ + "status" => %{"status_id" => status_id, "profile" => user2.ap_id} + }) + + assert redirected_to(conn) == + "https://social.heldscal.la/main/ostatussub?profile=#{status_ap_id}" + end + + test "it renders form with error when status not found", %{conn: conn} do + user2 = insert(:user, ap_id: "shp@social.heldscal.la") + + response = + conn + |> post("/main/ostatus", %{ + "status" => %{"status_id" => "somerandomid", "profile" => user2.ap_id} + }) + |> response(:ok) + + assert response =~ "Something went wrong." + end + end + + describe "GET /main/ostatus - show_subscribe_form/2" do + setup do: clear_config([:instance, :federating], true) + + test "it works with users", %{conn: conn} do + user = insert(:user) + + response = + conn + |> get("/main/ostatus", %{"nickname" => user.nickname}) + |> response(:ok) + + refute response =~ "Could not find user" + assert response =~ "Remotely follow #{user.nickname}" + end + + test "it works with statuses", %{conn: conn} do + user = insert(:user) + status = insert(:note_activity, %{user: user}) + status_id = status.id + + assert is_binary(status_id) + + response = + conn + |> get("/main/ostatus", %{"status_id" => status_id}) + |> response(:ok) + + refute response =~ "Could not find status" + assert response =~ "Interacting with" end end end diff --git a/test/pleroma/web/rich_media/card_test.exs b/test/pleroma/web/rich_media/card_test.exs index c69f85323..723446c86 100644 --- a/test/pleroma/web/rich_media/card_test.exs +++ b/test/pleroma/web/rich_media/card_test.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Web.RichMedia.CardTest do use Oban.Testing, repo: Pleroma.Repo - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false alias Pleroma.Tests.ObanHelpers alias Pleroma.UnstubbedConfigMock, as: ConfigMock @@ -19,6 +19,8 @@ defmodule Pleroma.Web.RichMedia.CardTest do setup do mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + Mox.stub_with(Pleroma.CachexMock, Pleroma.NullCache) + ConfigMock |> stub_with(Pleroma.Test.StaticConfig) diff --git a/test/pleroma/web/streamer_test.exs b/test/pleroma/web/streamer_test.exs index 096ca2d2a..b26bd1847 100644 --- a/test/pleroma/web/streamer_test.exs +++ b/test/pleroma/web/streamer_test.exs @@ -19,7 +19,12 @@ defmodule Pleroma.Web.StreamerTest do @moduletag needs_streamer: true, capture_log: true - setup do: clear_config([:instance, :skip_thread_containment]) + setup do + clear_config([:instance, :skip_thread_containment]) + Mox.stub_with(Pleroma.CachexMock, Pleroma.NullCache) + + :ok + end describe "get_topic/_ (unauthenticated)" do test "allows no stream" do @@ -215,7 +220,7 @@ defmodule Pleroma.Web.StreamerTest do } do %{token: read_lists_token} = oauth_access(["read:lists"], user: user) %{token: invalid_token} = oauth_access(["irrelevant:scope"], user: user) - {:ok, list} = List.create("Test", user) + {:ok, list} = List.create(%{title: "Test"}, user) assert {:error, _} = Streamer.get_topic("list:#{list.id}", user, read_oauth_token) @@ -228,7 +233,7 @@ defmodule Pleroma.Web.StreamerTest do test "disallows list stream that are not owned by the user", %{user: user, token: oauth_token} do another_user = insert(:user) - {:ok, list} = List.create("Test", another_user) + {:ok, list} = List.create(%{title: "Test"}, another_user) assert {:error, _} = Streamer.get_topic("list:#{list.id}", user, oauth_token) assert {:error, _} = Streamer.get_topic("list", user, oauth_token, %{"list" => list.id}) @@ -798,7 +803,7 @@ defmodule Pleroma.Web.StreamerTest do {:ok, user_a, user_b} = User.follow(user_a, user_b) - {:ok, list} = List.create("Test", user_a) + {:ok, list} = List.create(%{title: "Test"}, user_a) {:ok, list} = List.follow(list, user_b) Streamer.get_topic_and_add_socket("list", user_a, user_a_token, %{"list" => list.id}) @@ -815,7 +820,7 @@ defmodule Pleroma.Web.StreamerTest do test "it doesn't send unwanted private posts to list", %{user: user_a, token: user_a_token} do user_b = insert(:user) - {:ok, list} = List.create("Test", user_a) + {:ok, list} = List.create(%{title: "Test"}, user_a) {:ok, list} = List.follow(list, user_b) Streamer.get_topic_and_add_socket("list", user_a, user_a_token, %{"list" => list.id}) @@ -834,7 +839,7 @@ defmodule Pleroma.Web.StreamerTest do {:ok, user_a, user_b} = User.follow(user_a, user_b) - {:ok, list} = List.create("Test", user_a) + {:ok, list} = List.create(%{title: "Test"}, user_a) {:ok, list} = List.follow(list, user_b) Streamer.get_topic_and_add_socket("list", user_a, user_a_token, %{"list" => list.id}) diff --git a/test/pleroma/web/web_finger_test.exs b/test/pleroma/web/web_finger_test.exs index eb03c736e..da10abdd2 100644 --- a/test/pleroma/web/web_finger_test.exs +++ b/test/pleroma/web/web_finger_test.exs @@ -3,12 +3,13 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.WebFingerTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false alias Pleroma.Web.WebFinger import Pleroma.Factory import Tesla.Mock setup do + Mox.stub_with(Pleroma.CachexMock, Pleroma.NullCache) mock(fn env -> apply(HttpRequestMock, :request, [env]) end) :ok end diff --git a/test/pleroma/workers/publisher_worker_test.exs b/test/pleroma/workers/publisher_worker_test.exs index ca432d9bf..4519864a5 100644 --- a/test/pleroma/workers/publisher_worker_test.exs +++ b/test/pleroma/workers/publisher_worker_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.PublisherWorkerTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false use Oban.Testing, repo: Pleroma.Repo import Pleroma.Factory diff --git a/test/pleroma/workers/reachability_worker_test.exs b/test/pleroma/workers/reachability_worker_test.exs index 4854aff77..c641ccf65 100644 --- a/test/pleroma/workers/reachability_worker_test.exs +++ b/test/pleroma/workers/reachability_worker_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.ReachabilityWorkerTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false use Oban.Testing, repo: Pleroma.Repo import Mock diff --git a/test/pleroma/workers/receiver_worker_test.exs b/test/pleroma/workers/receiver_worker_test.exs index 7f4789f91..12abc1a27 100644 --- a/test/pleroma/workers/receiver_worker_test.exs +++ b/test/pleroma/workers/receiver_worker_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.ReceiverWorkerTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false use Oban.Testing, repo: Pleroma.Repo import Mock diff --git a/test/pleroma/workers/remote_fetcher_worker_test.exs b/test/pleroma/workers/remote_fetcher_worker_test.exs index 6eb6932cb..a3b900a9b 100644 --- a/test/pleroma/workers/remote_fetcher_worker_test.exs +++ b/test/pleroma/workers/remote_fetcher_worker_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.RemoteFetcherWorkerTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false use Oban.Testing, repo: Pleroma.Repo alias Pleroma.Workers.RemoteFetcherWorker diff --git a/tools/check-changelog b/tools/check-changelog index 5952aefcc..d09a68895 100644 --- a/tools/check-changelog +++ b/tools/check-changelog @@ -1,14 +1,14 @@ #!/bin/sh echo "adding ownership exception" -git config --global --add safe.directory $(pwd) +git config --global --add safe.directory "$(pwd)" echo "looking for change log" git remote add upstream https://git.pleroma.social/pleroma/pleroma.git -git fetch upstream ${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}:refs/remotes/upstream/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME +git fetch upstream ${CI_COMMIT_TARGET_BRANCH}:refs/remotes/upstream/${CI_COMMIT_TARGET_BRANCH} -git diff --raw --no-renames upstream/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME HEAD -- changelog.d | \ +git diff --raw --no-renames upstream/${CI_COMMIT_TARGET_BRANCH} HEAD -- changelog.d | \ grep ' A\t' | grep '\.\(skip\|add\|remove\|fix\|security\|change\)$' ret=$?