diff --git a/.woodpecker/build-amd64.yml b/.woodpecker/build-amd64.yml
index 32bbd2456..0a41fafef 100644
--- a/.woodpecker/build-amd64.yml
+++ b/.woodpecker/build-amd64.yml
@@ -6,9 +6,12 @@ depends_on:
variables:
- &scw-secrets
- - SCW_ACCESS_KEY
- - SCW_SECRET_KEY
- - SCW_DEFAULT_ORGANIZATION_ID
+ SCW_ACCESS_KEY:
+ from_secret: SCW_ACCESS_KEY
+ SCW_SECRET_KEY:
+ from_secret: SCW_SECRET_KEY
+ SCW_DEFAULT_ORGANIZATION_ID:
+ from_secret: SCW_DEFAULT_ORGANIZATION_ID
- &setup-hex "mix local.hex --force && mix local.rebar --force"
- &on-release
when:
@@ -56,7 +59,7 @@ steps:
release-debian-bookworm:
image: akkoma/releaser
<<: *on-release
- secrets: *scw-secrets
+ environment: *scw-secrets
commands:
- export SOURCE=akkoma-amd64.zip
# AMD64
@@ -85,7 +88,7 @@ steps:
release-debian-bullseye:
image: akkoma/releaser
<<: *on-release
- secrets: *scw-secrets
+ environment: *scw-secrets
commands:
- export SOURCE=akkoma-amd64-debian-bullseye.zip
# AMD64
@@ -111,7 +114,7 @@ steps:
release-musl:
image: akkoma/releaser
<<: *on-stable
- secrets: *scw-secrets
+ environment: *scw-secrets
commands:
- export SOURCE=akkoma-amd64-musl.zip
- export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-amd64-musl.zip
diff --git a/.woodpecker/build-arm64.yml b/.woodpecker/build-arm64.yml
index 7bcdc1435..b1e495153 100644
--- a/.woodpecker/build-arm64.yml
+++ b/.woodpecker/build-arm64.yml
@@ -1,14 +1,17 @@
labels:
- platform: linux/aarch64
+ platform: linux/arm64
depends_on:
- test
variables:
- &scw-secrets
- - SCW_ACCESS_KEY
- - SCW_SECRET_KEY
- - SCW_DEFAULT_ORGANIZATION_ID
+ SCW_ACCESS_KEY:
+ from_secret: SCW_ACCESS_KEY
+ SCW_SECRET_KEY:
+ from_secret: SCW_SECRET_KEY
+ SCW_DEFAULT_ORGANIZATION_ID:
+ from_secret: SCW_DEFAULT_ORGANIZATION_ID
- &setup-hex "mix local.hex --force && mix local.rebar --force"
- &on-release
when:
@@ -56,7 +59,7 @@ steps:
release-debian-bookworm:
image: akkoma/releaser:arm64
<<: *on-release
- secrets: *scw-secrets
+ environment: *scw-secrets
commands:
- export SOURCE=akkoma-arm64.zip
- export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-arm64-ubuntu-jammy.zip
@@ -83,7 +86,7 @@ steps:
release-musl:
image: akkoma/releaser:arm64
<<: *on-stable
- secrets: *scw-secrets
+ environment: *scw-secrets
commands:
- export SOURCE=akkoma-arm64-musl.zip
- export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-arm64-musl.zip
diff --git a/.woodpecker/docs.yml b/.woodpecker/docs.yml
index be7444e28..f8cd12145 100644
--- a/.woodpecker/docs.yml
+++ b/.woodpecker/docs.yml
@@ -6,10 +6,6 @@ depends_on:
- build-amd64
variables:
- - &scw-secrets
- - SCW_ACCESS_KEY
- - SCW_SECRET_KEY
- - SCW_DEFAULT_ORGANIZATION_ID
- &setup-hex "mix local.hex --force && mix local.rebar --force"
- &on-release
when:
@@ -49,12 +45,14 @@ variables:
steps:
docs:
<<: *on-point-release
- secrets:
- - SCW_ACCESS_KEY
- - SCW_SECRET_KEY
- - SCW_DEFAULT_ORGANIZATION_ID
environment:
CI: "true"
+ SCW_ACCESS_KEY:
+ from_secret: SCW_ACCESS_KEY
+ SCW_SECRET_KEY:
+ from_secret: SCW_SECRET_KEY
+ SCW_DEFAULT_ORGANIZATION_ID:
+ from_secret: SCW_DEFAULT_ORGANIZATION_ID
image: python:3.10-slim
commands:
- apt-get update && apt-get install -y rclone wget git zip
diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 623494301..70e8fc95d 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -2,10 +2,6 @@ labels:
platform: linux/amd64
variables:
- - &scw-secrets
- - SCW_ACCESS_KEY
- - SCW_SECRET_KEY
- - SCW_DEFAULT_ORGANIZATION_ID
- &setup-hex "mix local.hex --force && mix local.rebar --force"
- &on-release
when:
diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml
index 81c779b50..bef503752 100644
--- a/.woodpecker/test.yml
+++ b/.woodpecker/test.yml
@@ -17,10 +17,6 @@ matrix:
OTP_VERSION: 26
variables:
- - &scw-secrets
- - SCW_ACCESS_KEY
- - SCW_SECRET_KEY
- - SCW_DEFAULT_ORGANIZATION_ID
- &setup-hex "mix local.hex --force && mix local.rebar --force"
- &on-release
when:
@@ -87,5 +83,5 @@ steps:
- mix ecto.create
- mix ecto.migrate
- mkdir -p test/tmp
- - mix test --preload-modules --exclude erratic --exclude federated --exclude mocked
- - mix test --preload-modules --only mocked
+ - mix test --preload-modules --exclude erratic --exclude federated --exclude mocked || mix test --failed
+ - mix test --preload-modules --only mocked || mix test --failed
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3c743e5bd..42ed630fb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,10 +4,38 @@ 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/).
-## UNRELEASED
+## Unreleased
+
+## 2025.01.01
+
+Hotfix: Federation could break if a null value found its way into `should_federate?\1`
+
+## 2025.01
+
+## Added
+- New config option `:instance, :cleanup_attachments_delay`
+- It is now possible to display custom source URLs in akkoma-fe;
+ the settings are part of the frontend configuration
+
+## Fixed
+- Media proxy no longer attempts to proxy embedded images
+- Fix significant uneccessary overhead of attachment cleanup;
+ it no longer attempts to cleanup attachments of deleted remote posts
+- Fix “Delete & Redraft” often losing attachments if attachment cleanup was enabled
+- ObjectAge policy no longer lets unlisted posts slip through
+- ObjectAge policy no longer leaks belated DMs and follower-only posts
+- the NodeINfo endpoint now uses the correct content type
+
+## Changed
+- Anonymous objects now federate completely without an id
+ adopting a proposed AP spec errata and restoring federation
+ with e.g. IceShrimp.NET and fedify-based implementations
+
+## 3.13.3
## BREAKING
- Minimum PostgreSQL version is raised to 12
+- Swagger UI moved from `/akkoma/swaggerui/` to `/pleroma/swaggerui/`
## Added
- Implement [FEP-67ff](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md) (federation documentation)
@@ -17,6 +45,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Fixed
- Meilisearch: order of results returned from our REST API now actually matches how Meilisearch ranks results
+- Emoji are now federated as anonymous objects, fixing issues with
+ some strict servers e.g. rejecting e.g. remote emoji reactions
+- AP objects with additional JSON-LD profiles beyond ActivityStreams can now be fetched
+- Single-selection polls no longer expose the voter_count; MastoAPI demands it be null
+ and this confused some clients leading to vote distributions >100%
## Changed
- Refactored Rich Media to cache the content in the database. Fetching operations that could block status rendering have been eliminated.
diff --git a/config/config.exs b/config/config.exs
index d2dc778f4..296d8d071 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -256,6 +256,7 @@
external_user_synchronization: true,
extended_nickname_format: true,
cleanup_attachments: false,
+ cleanup_attachments_delay: 1800,
multi_factor_authentication: [
totp: [
# digits 6 or 8
@@ -303,6 +304,7 @@
allow_headings: false,
allow_tables: false,
allow_fonts: false,
+ allow_math: true,
scrub_policy: [
Pleroma.HTML.Scrubber.Default,
Pleroma.HTML.Transform.MediaProxy
diff --git a/config/description.exs b/config/description.exs
index b69478fdb..2a7f3344d 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -1184,7 +1184,7 @@
logoMask: true,
minimalScopesMode: false,
noAttachmentLinks: false,
- nsfwCensorImage: "/static/img/nsfw.74818f9.png",
+ nsfwCensorImage: "",
postContentType: "text/plain",
redirectRootLogin: "/main/friends",
redirectRootNoLogin: "/main/all",
@@ -1194,7 +1194,9 @@
showInstanceSpecificPanel: false,
subjectLineBehavior: "email",
theme: "pleroma-dark",
- webPushNotifications: false
+ webPushNotifications: false,
+ backendCommitUrl: "",
+ frontendCommitUrl: ""
}
],
children: [
@@ -1285,7 +1287,7 @@
type: {:string, :image},
description:
"URL of the image to use for hiding NSFW media attachments in the timeline",
- suggestions: ["/static/img/nsfw.74818f9.png"]
+ suggestions: [""]
},
%{
key: :postContentType,
@@ -1398,6 +1400,18 @@
label: "Stop Gifs",
type: :boolean,
description: "Whether to pause animated images until they're hovered on"
+ },
+ %{
+ key: :backendCommitUrl,
+ label: "Backend Commit URL",
+ type: :string,
+ description: "URL prefix for backend commit hashes"
+ },
+ %{
+ key: :frontendCommitUrl,
+ label: "Frontend Commit URL",
+ type: :string,
+ description: "URL prefix for frontend commit hashes"
}
]
},
diff --git a/config/test.exs b/config/test.exs
index 4c2580cf2..e8dbe2b0a 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -51,7 +51,8 @@
hostname: System.get_env("DB_HOST") || "localhost",
pool: Ecto.Adapters.SQL.Sandbox,
pool_size: 50,
- queue_target: 5000
+ queue_target: 5000,
+ log: false
config :pleroma, :dangerzone, override_repo_pool_size: true
diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md
index a34d68198..f0e21d61b 100644
--- a/docs/docs/configuration/cheatsheet.md
+++ b/docs/docs/configuration/cheatsheet.md
@@ -58,6 +58,7 @@ To add configuration to your config file, you can copy it from the base config.
* `registration_reason_length`: Maximum registration reason length (default: `500`).
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
* `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances.
+* `cleanup_attachments_delay`: How many seconds to wait after post deletion before attempting to deletion; useful for “delete & redraft” functionality (default: `1800`)
* `show_reactions`: Let favourites and emoji reactions be viewed through the API (default: `true`).
* `password_reset_token_validity`: The time after which reset tokens aren't accepted anymore, in seconds (default: one day).
* `local_bubble`: Array of domains representing instances closely related to yours. Used to populate the `bubble` timeline. e.g `["example.com"]`, (default: `[]`)
@@ -338,7 +339,7 @@ config :pleroma, :frontends,
* `:primary` - The frontend that will be served at `/`
* `:admin` - The frontend that will be served at `/pleroma/admin`
-* `:swagger` - Config for developers to act as an API reference to be served at `/akkoma/swaggerui/` (trailing slash _needed_). Disabled by default.
+* `:swagger` - Config for developers to act as an API reference to be served at `/pleroma/swaggerui/` (trailing slash _needed_). Disabled by default.
* `:mastodon` - The mastodon-fe configuration. This shouldn't need to be changed. This is served at `/web` when installed.
### :static\_fe
diff --git a/docs/docs/configuration/frontend_management.md b/docs/docs/configuration/frontend_management.md
index bc5344826..8875ee279 100644
--- a/docs/docs/configuration/frontend_management.md
+++ b/docs/docs/configuration/frontend_management.md
@@ -60,4 +60,4 @@ config :pleroma, :frontends,
Then run the [pleroma.frontend cli task](../../administration/CLI_tasks/frontend) with the name of `swagger-ui` to install the distribution files.
-You will now be able to view documentation at `/akkoma/swaggerui`
+You will now be able to view documentation at `/pleroma/swaggerui`
diff --git a/docs/docs/installation/debian_based_en.md b/docs/docs/installation/debian_based_en.md
index 5dddabe7f..442849e69 100644
--- a/docs/docs/installation/debian_based_en.md
+++ b/docs/docs/installation/debian_based_en.md
@@ -35,32 +35,24 @@ sudo useradd -r -s /bin/false -m -d /var/lib/akkoma -U akkoma
### Install Elixir and Erlang
+#### Using `apt`
If your distribution packages a recent enough version of Elixir, you can install it directly from the distro repositories and skip to the next section of the guide:
```shell
sudo apt install elixir erlang-dev erlang-nox
```
-Otherwise use [asdf](https://github.com/asdf-vm/asdf) to install the latest versions of Elixir and Erlang.
+#### Using `asdf`
+If your distribution does not have a recent version of Elxir in their repositories, you can use [asdf](https://asdf-vm.com/) to install a newer version of Elixir and Erlang.
First, install some dependencies needed to build Elixir and Erlang:
```shell
sudo apt install curl unzip build-essential autoconf m4 libncurses5-dev libssh-dev unixodbc-dev xsltproc libxml2-utils libncurses-dev
```
-Then login to the `akkoma` user and install asdf:
-```shell
-git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.11.3
-```
+Then login to the `akkoma` user.
-Add the following lines to `~/.bashrc`:
-```shell
-. "$HOME/.asdf/asdf.sh"
-# asdf completions
-. "$HOME/.asdf/completions/asdf.bash"
-```
-
-Restart the shell:
+Install asdf by following steps 1 to 3 on [their website](https://asdf-vm.com/guide/getting-started.html), then restart the shell to load asdf:
```shell
exec $SHELL
```
@@ -69,15 +61,15 @@ Next install Erlang:
```shell
asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git
export KERL_CONFIGURE_OPTIONS="--disable-debug --without-javac"
-asdf install erlang 25.3.2.5
-asdf global erlang 25.3.2.5
+asdf install erlang 26.2.5.4
+asdf global erlang 26.2.5.4
```
Now install Elixir:
```shell
asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git
-asdf install elixir 1.15.4-otp-25
-asdf global elixir 1.15.4-otp-25
+asdf install elixir 1.17.3-otp-26
+asdf global elixir 1.17.3-otp-26
```
Confirm that Elixir is installed correctly by checking the version:
diff --git a/docs/docs/installation/frontends.include b/docs/docs/installation/frontends.include
index 6da4018a9..094cfe4bd 100644
--- a/docs/docs/installation/frontends.include
+++ b/docs/docs/installation/frontends.include
@@ -6,7 +6,9 @@ probably install frontends.
These are no longer bundled with the distribution and need an extra
command to install.
-For most installations, the following will suffice:
+You **must** run frontend management tasks as the akkoma user,
+the same way you downloaded the build or cloned the git repo before.
+But otherwise, for most installations, the following will suffice:
=== "OTP"
```sh
@@ -28,4 +30,3 @@ For most installations, the following will suffice:
```
For more customised installations, refer to [Frontend Management](../../configuration/frontend_management)
-
diff --git a/docs/docs/installation/generic_dependencies.include b/docs/docs/installation/generic_dependencies.include
index 87669bd23..f4d439b83 100644
--- a/docs/docs/installation/generic_dependencies.include
+++ b/docs/docs/installation/generic_dependencies.include
@@ -1,8 +1,8 @@
## Required dependencies
* PostgreSQL 12+
-* Elixir 1.14+ (currently tested up to 1.16)
-* Erlang OTP 25+ (currently tested up to OTP26)
+* Elixir 1.14+ (currently tested up to 1.17)
+* Erlang OTP 25+ (currently tested up to OTP27)
* git
* file / libmagic
* gcc (clang might also work)
diff --git a/installation/openbsd/rc.d/akkomad b/installation/openbsd/rc.d/akkomad
index 68be46c9a..fa3c19b2b 100755
--- a/installation/openbsd/rc.d/akkomad
+++ b/installation/openbsd/rc.d/akkomad
@@ -11,11 +11,13 @@
#
daemon="/usr/local/bin/elixir"
-daemon_flags="--detached -S /usr/local/bin/mix phx.server"
+daemon_flags="-S /usr/local/bin/mix phx.server"
daemon_user="_akkoma"
+daemon_execdir="/home/_akkoma/akkoma"
. /etc/rc.d/rc.subr
+rc_bg="YES"
rc_reload=NO
pexp="phx.server"
@@ -24,7 +26,7 @@ rc_check() {
}
rc_start() {
- ${rcexec} "cd akkoma; ${daemon} ${daemon_flags}"
+ rc_exec "${daemon} ${daemon_flags}"
}
rc_stop() {
diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index 468c1ef6b..5c9e8d792 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -343,10 +343,16 @@ def run(["prune_objects" | args]) do
%{:num_rows => del_hashtags} =
"""
- DELETE FROM hashtags AS ht
- WHERE NOT EXISTS (
- SELECT 1 FROM hashtags_objects hto
- WHERE ht.id = hto.hashtag_id)
+ DELETE FROM hashtags
+ USING hashtags AS ht
+ LEFT JOIN hashtags_objects hto
+ ON ht.id = hto.hashtag_id
+ LEFT JOIN user_follows_hashtag ufht
+ ON ht.id = ufht.hashtag_id
+ WHERE
+ hashtags.id = ht.id
+ AND hto.hashtag_id is NULL
+ AND ufht.hashtag_id is NULL
"""
|> Repo.query!()
diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex
index 12918dfff..aa8131254 100644
--- a/lib/mix/tasks/pleroma/emoji.ex
+++ b/lib/mix/tasks/pleroma/emoji.ex
@@ -111,7 +111,7 @@ def run(["get-packs" | args]) do
{:ok, _} =
:zip.unzip(binary_archive,
- cwd: pack_path,
+ cwd: to_charlist(pack_path),
file_list: files_to_unzip
)
diff --git a/lib/pleroma/emails/mailer.ex b/lib/pleroma/emails/mailer.ex
index 6a79a7694..af513f1f1 100644
--- a/lib/pleroma/emails/mailer.ex
+++ b/lib/pleroma/emails/mailer.ex
@@ -84,8 +84,14 @@ defp default_config(Swoosh.Adapters.SMTP, conf, _) do
cacerts: os_cacerts,
versions: [:"tlsv1.2", :"tlsv1.3"],
verify: :verify_peer,
- # some versions have supposedly issues verifying wildcard certs without this
server_name_indication: relay,
+ # This allows wildcard ceritifcates to be verified properly.
+ # The :https parameter simply means to use the HTTPS wildcard format
+ # (as opposed to say LDAP). SMTP servers tend to use the same type of
+ # certs as HTTPS ones so this should work for most.
+ customize_hostname_check: [
+ match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
+ ],
# the default of 10 is too restrictive
depth: 32
]
diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex
index 142208854..e95320a07 100644
--- a/lib/pleroma/emoji/pack.ex
+++ b/lib/pleroma/emoji/pack.ex
@@ -125,7 +125,7 @@ def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"}
{:ok, _emoji_files} =
:zip.unzip(
to_charlist(file.path),
- [{:file_list, Enum.map(emojies, & &1[:path])}, {:cwd, tmp_dir}]
+ [{:file_list, Enum.map(emojies, & &1[:path])}, {:cwd, to_charlist(tmp_dir)}]
)
{_, updated_pack} =
diff --git a/lib/pleroma/frontend.ex b/lib/pleroma/frontend.ex
index dc9d55646..a309d8467 100644
--- a/lib/pleroma/frontend.ex
+++ b/lib/pleroma/frontend.ex
@@ -79,6 +79,10 @@ def unzip(zip, dest) do
new_file_path = Path.join(dest, path)
+ new_file_path
+ |> Path.dirname()
+ |> File.rm()
+
new_file_path
|> Path.dirname()
|> File.mkdir_p!()
diff --git a/lib/pleroma/keys.ex b/lib/pleroma/keys.ex
deleted file mode 100644
index 413861b15..000000000
--- a/lib/pleroma/keys.ex
+++ /dev/null
@@ -1,46 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Keys do
- # Native generation of RSA keys is only available since OTP 20+ and in default build conditions
- # We try at compile time to generate natively an RSA key otherwise we fallback on the old way.
- try do
- _ = :public_key.generate_key({:rsa, 2048, 65_537})
-
- def generate_rsa_pem do
- key = :public_key.generate_key({:rsa, 2048, 65_537})
- entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
- pem = :public_key.pem_encode([entry]) |> String.trim_trailing()
- {:ok, pem}
- end
- rescue
- _ ->
- def generate_rsa_pem do
- port = Port.open({:spawn, "openssl genrsa"}, [:binary])
-
- {:ok, pem} =
- receive do
- {^port, {:data, pem}} -> {:ok, pem}
- end
-
- Port.close(port)
-
- if Regex.match?(~r/RSA PRIVATE KEY/, pem) do
- {:ok, pem}
- else
- :error
- end
- end
- end
-
- def keys_from_pem(pem) do
- with [private_key_code] <- :public_key.pem_decode(pem),
- private_key <- :public_key.pem_entry_decode(private_key_code),
- {:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} <- private_key do
- {:ok, private_key, {:RSAPublicKey, modulus, exponent}}
- else
- error -> {:error, error}
- end
- end
-end
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 379b361f8..5d84bb286 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -9,7 +9,6 @@ defmodule Pleroma.Object do
import Ecto.Changeset
alias Pleroma.Activity
- alias Pleroma.Config
alias Pleroma.Hashtag
alias Pleroma.Object
alias Pleroma.Object.Fetcher
@@ -241,23 +240,11 @@ def delete(%Object{data: %{"id" => id}} = object) do
with {:ok, _obj} = swap_object_with_tombstone(object),
deleted_activity = Activity.delete_all_by_object_ap_id(id),
{:ok, _} <- invalid_object_cache(object) do
- cleanup_attachments(
- Config.get([:instance, :cleanup_attachments]),
- %{object: object}
- )
-
+ AttachmentsCleanupWorker.enqueue_if_needed(object.data)
{:ok, object, deleted_activity}
end
end
- @spec cleanup_attachments(boolean(), %{required(:object) => map()}) ::
- {:ok, Oban.Job.t() | nil}
- def cleanup_attachments(true, %{object: _} = params) do
- AttachmentsCleanupWorker.enqueue("cleanup_attachments", params)
- end
-
- def cleanup_attachments(_, _), do: {:ok, nil}
-
def prune(%Object{data: %{"id" => _id}} = object) do
with {:ok, object} <- Repo.delete(object),
{:ok, _} <- invalid_object_cache(object) do
diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex
index 37bc20e4d..7b1cc37bd 100644
--- a/lib/pleroma/object/containment.ex
+++ b/lib/pleroma/object/containment.ex
@@ -12,8 +12,6 @@ defmodule Pleroma.Object.Containment do
spoofing, therefore removal of object containment functions is NOT recommended.
"""
- alias Pleroma.Web.ActivityPub.Transmogrifier
-
def get_actor(%{"actor" => actor}) when is_binary(actor) do
actor
end
@@ -50,16 +48,39 @@ def get_object(_) do
defp compare_uris(%URI{host: host} = _id_uri, %URI{host: host} = _other_uri), do: :ok
defp compare_uris(_id_uri, _other_uri), do: :error
- defp compare_uris_exact(uri, uri), do: :ok
+ defp uri_strip_slash(%URI{path: path} = uri) when is_binary(path),
+ do: %{uri | path: String.replace_suffix(path, "/", "")}
- defp compare_uris_exact(%URI{} = id, %URI{} = other),
- do: compare_uris_exact(URI.to_string(id), URI.to_string(other))
+ defp uri_strip_slash(uri), do: uri
- defp compare_uris_exact(id_uri, other_uri)
- when is_binary(id_uri) and is_binary(other_uri) do
- norm_id = String.replace_suffix(id_uri, "/", "")
- norm_other = String.replace_suffix(other_uri, "/", "")
- if norm_id == norm_other, do: :ok, else: :error
+ # domain names are case-insensitive per spec (other parts of URIs aren’t necessarily)
+ defp uri_normalise_host(%URI{host: host} = uri) when is_binary(host),
+ do: %{uri | host: String.downcase(host, :ascii)}
+
+ defp uri_normalise_host(uri), do: uri
+
+ defp compare_uri_identities(uri, uri), do: :ok
+
+ defp compare_uri_identities(id_uri, other_uri) when is_binary(id_uri) and is_binary(other_uri),
+ do: compare_uri_identities(URI.parse(id_uri), URI.parse(other_uri))
+
+ defp compare_uri_identities(%URI{} = id, %URI{} = other) do
+ normid =
+ %{id | fragment: nil}
+ |> uri_strip_slash()
+ |> uri_normalise_host()
+
+ normother =
+ %{other | fragment: nil}
+ |> uri_strip_slash()
+ |> uri_normalise_host()
+
+ # Conversion back to binary avoids issues from non-normalised deprecated authority field
+ if URI.to_string(normid) == URI.to_string(normother) do
+ :ok
+ else
+ :error
+ end
end
@doc """
@@ -93,21 +114,13 @@ def contain_origin(id, %{"attributedTo" => actor} = params),
def contain_origin(_id, _data), do: :ok
@doc """
- Check whether the fetch URL (after redirects) exactly (sans tralining slash) matches either
- the canonical ActivityPub id or the objects url field (for display URLs from *key and Mastodon)
+ Check whether the fetch URL (after redirects) is the
+ same location the canonical ActivityPub id points to.
Since this is meant to be used for fetches, anonymous or transient objects are not accepted here.
"""
- def contain_id_to_fetch(url, %{"id" => id} = data) when is_binary(id) do
- with {:id, :error} <- {:id, compare_uris_exact(id, url)},
- # "url" can be a "Link" object and this is checked before full normalisation
- display_url <- Transmogrifier.fix_url(data)["url"],
- true <- display_url != nil do
- compare_uris_exact(display_url, url)
- else
- {:id, :ok} -> :ok
- _ -> :error
- end
+ def contain_id_to_fetch(url, %{"id" => id}) when is_binary(id) do
+ compare_uri_identities(url, id)
end
def contain_id_to_fetch(_url, _data), do: :error
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index 937026e04..11ed57ed5 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -116,7 +116,7 @@ defp reinject_object(%Object{} = object, new_data) do
@doc "Assumes object already is in our database and refetches from remote to update (e.g. for polls)"
def refetch_object(%Object{data: %{"id" => id}} = object) do
with {:local, false} <- {:local, Object.local?(object)},
- {:ok, new_data} <- fetch_and_contain_remote_object_from_id(id),
+ {:ok, new_data} <- fetch_and_contain_remote_object_from_id(id, true),
{:id, true} <- {:id, new_data["id"] == id},
{:ok, object} <- reinject_object(object, new_data) do
{:ok, object}
@@ -253,14 +253,17 @@ defp maybe_date_fetch(headers, date) do
end
end
- @doc "Fetches arbitrary remote object and performs basic safety and authenticity checks"
- def fetch_and_contain_remote_object_from_id(id)
+ @doc """
+ Fetches arbitrary remote object and performs basic safety and authenticity checks.
+ When the fetch URL is known to already be a canonical AP id, checks are stricter.
+ """
+ def fetch_and_contain_remote_object_from_id(id, is_ap_id \\ false)
- def fetch_and_contain_remote_object_from_id(%{"id" => id}),
- do: fetch_and_contain_remote_object_from_id(id)
+ def fetch_and_contain_remote_object_from_id(%{"id" => id}, is_ap_id),
+ do: fetch_and_contain_remote_object_from_id(id, is_ap_id)
- def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
- Logger.debug("Fetching object #{id} via AP")
+ def fetch_and_contain_remote_object_from_id(id, is_ap_id) when is_binary(id) do
+ Logger.debug("Fetching object #{id} via AP [ap_id=#{is_ap_id}]")
with {:valid_uri_scheme, true} <- {:valid_uri_scheme, String.starts_with?(id, "http")},
%URI{} = uri <- URI.parse(id),
@@ -270,18 +273,31 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
{:mrf_accept_check, Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_accept(uri)},
{:local_fetch, :ok} <- {:local_fetch, Containment.contain_local_fetch(id)},
{:ok, final_id, body} <- get_object(id),
+ # a canonical ID shouldn't be a redirect
+ true <- !is_ap_id || final_id == id,
{:ok, data} <- safe_json_decode(body),
- {_, :ok} <- {:strict_id, Containment.contain_id_to_fetch(final_id, data)},
- {_, :ok} <- {:containment, Containment.contain_origin(final_id, data)} do
+ {_, :ok} <- {:containment, Containment.contain_origin(final_id, data)},
+ {_, _, :ok} <- {:strict_id, data["id"], Containment.contain_id_to_fetch(final_id, data)} do
unless Instances.reachable?(final_id) do
Instances.set_reachable(final_id)
end
{:ok, data}
else
- {:strict_id, _} = e ->
- log_fetch_error(id, e)
- {:error, :id_mismatch}
+ # E.g. Mastodon and *key serve the AP object directly under their display URLs without
+ # redirecting to their canonical location first, thus ids will expectedly differ.
+ # Similarly keys, either use a fragment ID and are a subobjects or a distinct ID
+ # but for compatibility are still a subobject presenting their owning actors ID at the toplevel.
+ # Refetching _once_ from the listed id, should yield a strict match afterwards.
+ {:strict_id, ap_id, _} = e ->
+ case is_ap_id do
+ false ->
+ fetch_and_contain_remote_object_from_id(ap_id, true)
+
+ true ->
+ log_fetch_error(id, e)
+ {:error, :id_mismatch}
+ end
{:mrf_reject_check, _} = e ->
log_fetch_error(id, e)
@@ -301,7 +317,7 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
{:containment, reason} ->
log_fetch_error(id, reason)
- {:error, reason}
+ {:error, {:containment, reason}}
{:error, e} ->
{:error, e}
@@ -311,25 +327,13 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
end
end
- def fetch_and_contain_remote_object_from_id(_id),
+ def fetch_and_contain_remote_object_from_id(_id, _is_ap_id),
do: {:error, :invalid_id}
- defp check_crossdomain_redirect(final_host, original_url)
-
# HOPEFULLY TEMPORARY
# Basically none of our Tesla mocks in tests set the (supposed to
# exist for Tesla proper) url parameter for their responses
# causing almost every fetch in test to fail otherwise
- if @mix_env == :test do
- defp check_crossdomain_redirect(nil, _) do
- {:cross_domain_redirect, false}
- end
- end
-
- defp check_crossdomain_redirect(final_host, original_url) do
- {:cross_domain_redirect, final_host != URI.parse(original_url).host}
- end
-
if @mix_env == :test do
defp get_final_id(nil, initial_url), do: initial_url
defp get_final_id("", initial_url), do: initial_url
@@ -355,10 +359,6 @@ def get_object(id) do
with {:ok, %{body: body, status: code, headers: headers, url: final_url}}
when code in 200..299 <-
HTTP.Backoff.get(id, headers),
- remote_host <-
- URI.parse(final_url).host,
- {:cross_domain_redirect, false} <-
- check_crossdomain_redirect(remote_host, id),
{:has_content_type, {_, content_type}} <-
{:has_content_type, List.keyfind(headers, "content-type", 0)},
{:parse_content_type, {:ok, "application", subtype, type_params}} <-
@@ -369,8 +369,12 @@ def get_object(id) do
{"activity+json", _} ->
{:ok, final_id, body}
- {"ld+json", %{"profile" => "https://www.w3.org/ns/activitystreams"}} ->
- {:ok, final_id, body}
+ {"ld+json", %{"profile" => profiles}} ->
+ if "https://www.w3.org/ns/activitystreams" in String.split(profiles) do
+ {:ok, final_id, body}
+ else
+ {:error, {:content_type, content_type}}
+ end
_ ->
{:error, {:content_type, content_type}}
diff --git a/lib/pleroma/search/database_search.ex b/lib/pleroma/search/database_search.ex
index c36b2eb40..c5d5ca633 100644
--- a/lib/pleroma/search/database_search.ex
+++ b/lib/pleroma/search/database_search.ex
@@ -126,22 +126,31 @@ def query_with(q, :pgroonga, search_query) do
)
end
- def maybe_restrict_local(q, user, only_local) do
+ def should_restrict_local(user) do
limit = Pleroma.Config.get([:instance, :limit_to_local_content], :unauthenticated)
- case {only_local, limit, user} do
- {true, _, _} -> restrict_local(q)
- {_, :all, _} -> restrict_local(q)
- {_, :unauthenticated, %User{}} -> q
- {_, :unauthenticated, _} -> restrict_local(q)
- {_, false, _} -> q
+ case {limit, user} do
+ {:all, _} -> true
+ {:unauthenticated, %User{}} -> false
+ {:unauthenticated, _} -> true
+ {false, _} -> false
+ end
+ end
+
+ def maybe_restrict_local(q, user, only_local) do
+ with false <- should_restrict_local(user),
+ false <- only_local do
+ q
+ else
+ _ -> restrict_local(q)
end
end
defp restrict_local(q), do: where(q, local: true)
def maybe_fetch(activities, user, search_query) do
- with true <- Regex.match?(~r/https?:/, search_query),
+ with false <- should_restrict_local(user),
+ true <- Regex.match?(~r/https?:/, search_query),
{:ok, object} <- Fetcher.fetch_object_from_id(search_query),
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
true <- Visibility.visible_for_user?(activity, user) do
diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex
index c4ac2c87e..bc3baf433 100644
--- a/lib/pleroma/signature.ex
+++ b/lib/pleroma/signature.ex
@@ -5,47 +5,25 @@
defmodule Pleroma.Signature do
@behaviour HTTPSignatures.Adapter
- alias Pleroma.EctoType.ActivityPub.ObjectValidators
- alias Pleroma.Keys
alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPub
-
- @known_suffixes ["/publickey", "/main-key", "#key"]
+ alias Pleroma.User.SigningKey
+ require Logger
def key_id_to_actor_id(key_id) do
- uri =
- key_id
- |> URI.parse()
- |> Map.put(:fragment, nil)
- |> Map.put(:query, nil)
- |> remove_suffix(@known_suffixes)
+ case SigningKey.key_id_to_ap_id(key_id) do
+ nil ->
+ # hm, we SHOULD have gotten this in the pipeline before we hit here!
+ Logger.error("Could not figure out who owns the key #{key_id}")
+ {:error, :key_owner_not_found}
- maybe_ap_id = URI.to_string(uri)
-
- case ObjectValidators.ObjectID.cast(maybe_ap_id) do
- {:ok, ap_id} ->
- {:ok, ap_id}
-
- _ ->
- case Pleroma.Web.WebFinger.finger(maybe_ap_id) do
- {:ok, %{"ap_id" => ap_id}} -> {:ok, ap_id}
- _ -> {:error, maybe_ap_id}
- end
+ key ->
+ {:ok, key}
end
end
- defp remove_suffix(uri, [test | rest]) do
- if not is_nil(uri.path) and String.ends_with?(uri.path, test) do
- Map.put(uri, :path, String.replace(uri.path, test, ""))
- else
- remove_suffix(uri, rest)
- end
- end
-
- defp remove_suffix(uri, []), do: uri
-
def fetch_public_key(conn) do
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
+ {:ok, %SigningKey{}} <- SigningKey.get_or_fetch_by_key_id(kid),
{:ok, actor_id} <- key_id_to_actor_id(kid),
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
{:ok, public_key}
@@ -57,8 +35,8 @@ def fetch_public_key(conn) do
def refetch_public_key(conn) do
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
+ {:ok, %SigningKey{}} <- SigningKey.get_or_fetch_by_key_id(kid),
{:ok, actor_id} <- key_id_to_actor_id(kid),
- {:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
{:ok, public_key}
else
@@ -67,9 +45,9 @@ def refetch_public_key(conn) do
end
end
- def sign(%User{keys: keys} = user, headers) do
- with {:ok, private_key, _} <- Keys.keys_from_pem(keys) do
- HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers)
+ def sign(%User{} = user, headers) do
+ with {:ok, private_key} <- SigningKey.private_key(user) do
+ HTTPSignatures.sign(private_key, SigningKey.local_key_id(user.ap_id), headers)
end
end
diff --git a/lib/pleroma/upload/filter/exiftool/strip_metadata.ex b/lib/pleroma/upload/filter/exiftool/strip_metadata.ex
index 912ff6a92..a2604a682 100644
--- a/lib/pleroma/upload/filter/exiftool/strip_metadata.ex
+++ b/lib/pleroma/upload/filter/exiftool/strip_metadata.ex
@@ -18,6 +18,7 @@ defmodule Pleroma.Upload.Filter.Exiftool.StripMetadata do
# Formats not compatible with exiftool at this time
def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop}
+ def filter(%Pleroma.Upload{content_type: "image/bmp"}), do: {:ok, :noop}
def filter(%Pleroma.Upload{content_type: "image/svg+xml"}), do: {:ok, :noop}
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 2bc3e9ace..dfeab0410 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -25,7 +25,6 @@ defmodule Pleroma.User do
alias Pleroma.Hashtag
alias Pleroma.User.HashtagFollow
alias Pleroma.HTML
- alias Pleroma.Keys
alias Pleroma.MFA
alias Pleroma.Notification
alias Pleroma.Object
@@ -43,6 +42,7 @@ defmodule Pleroma.User do
alias Pleroma.Web.OAuth
alias Pleroma.Web.RelMe
alias Pleroma.Workers.BackgroundWorker
+ alias Pleroma.User.SigningKey
use Pleroma.Web, :verified_routes
@@ -101,7 +101,6 @@ defmodule Pleroma.User do
field(:password, :string, virtual: true)
field(:password_confirmation, :string, virtual: true)
field(:keys, :string)
- field(:public_key, :string)
field(:ap_id, :string)
field(:avatar, :map, default: %{})
field(:local, :boolean, default: true)
@@ -222,6 +221,10 @@ defmodule Pleroma.User do
on_replace: :delete
)
+ # FOR THE FUTURE: We might want to make this a one-to-many relationship
+ # it's entirely possible right now, but we don't have a use case for it
+ has_one(:signing_key, SigningKey, foreign_key: :user_id)
+
timestamps()
end
@@ -440,6 +443,7 @@ defp fix_follower_address(params), do: params
def remote_user_changeset(struct \\ %User{local: false}, params) do
bio_limit = Config.get([:instance, :user_bio_length], 5000)
name_limit = Config.get([:instance, :user_name_length], 100)
+ fields_limit = Config.get([:instance, :max_remote_account_fields], 0)
name =
case params[:name] do
@@ -453,10 +457,12 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
|> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
|> truncate_if_exists(:name, name_limit)
|> truncate_if_exists(:bio, bio_limit)
+ |> Map.update(:fields, [], &Enum.take(&1, fields_limit))
|> truncate_fields_param()
|> fix_follower_address()
struct
+ |> Repo.preload(:signing_key)
|> cast(
params,
[
@@ -466,7 +472,6 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
:inbox,
:shared_inbox,
:nickname,
- :public_key,
:avatar,
:ap_enabled,
:banner,
@@ -495,6 +500,7 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
|> validate_required([:ap_id])
|> validate_required([:name], trim: false)
|> unique_constraint(:nickname)
+ |> cast_assoc(:signing_key, with: &SigningKey.remote_changeset/2, required: false)
|> validate_format(:nickname, @email_regex)
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, max: name_limit)
@@ -526,7 +532,6 @@ def update_changeset(struct, params \\ %{}) do
:name,
:emoji,
:avatar,
- :public_key,
:inbox,
:shared_inbox,
:is_locked,
@@ -570,6 +575,7 @@ def update_changeset(struct, params \\ %{}) do
:pleroma_settings_store,
&{:ok, Map.merge(struct.pleroma_settings_store, &1)}
)
+ |> cast_assoc(:signing_key, with: &SigningKey.remote_changeset/2, requred: false)
|> validate_fields(false, struct)
end
@@ -828,8 +834,10 @@ def put_following_and_follower_and_featured_address(changeset) do
end
defp put_private_key(changeset) do
- {:ok, pem} = Keys.generate_rsa_pem()
- put_change(changeset, :keys, pem)
+ ap_id = get_field(changeset, :ap_id)
+
+ changeset
+ |> put_assoc(:signing_key, SigningKey.generate_local_keys(ap_id))
end
defp autofollow_users(user) do
@@ -1146,7 +1154,8 @@ def update_and_set_cache(%{data: %Pleroma.User{} = user} = changeset) do
was_superuser_before_update = User.superuser?(user)
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
- set_cache(user)
+ user
+ |> set_cache()
end
|> maybe_remove_report_notifications(was_superuser_before_update)
end
@@ -2051,24 +2060,16 @@ defp create_service_actor(uri, nickname) do
|> set_cache()
end
- def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
- key =
- public_key_pem
- |> :public_key.pem_decode()
- |> hd()
- |> :public_key.pem_entry_decode()
-
- {:ok, key}
- end
-
- def public_key(_), do: {:error, "key not found"}
+ defdelegate public_key(user), to: SigningKey
def get_public_key_for_ap_id(ap_id) do
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
- {:ok, public_key} <- public_key(user) do
+ {:ok, public_key} <- SigningKey.public_key(user) do
{:ok, public_key}
else
- _ -> :error
+ e ->
+ Logger.error("Could not get public key for #{ap_id}.\n#{inspect(e)}")
+ {:error, e}
end
end
diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex
new file mode 100644
index 000000000..87149aa58
--- /dev/null
+++ b/lib/pleroma/user/signing_key.ex
@@ -0,0 +1,255 @@
+defmodule Pleroma.User.SigningKey do
+ use Ecto.Schema
+ import Ecto.Query
+ import Ecto.Changeset
+ require Pleroma.Constants
+ alias Pleroma.User
+ alias Pleroma.Repo
+
+ require Logger
+
+ @primary_key false
+ schema "signing_keys" do
+ belongs_to(:user, Pleroma.User, type: FlakeId.Ecto.CompatType)
+ field :public_key, :string
+ field :private_key, :string
+ # This is an arbitrary field given by the remote instance
+ field :key_id, :string, primary_key: true
+ timestamps()
+ end
+
+ def load_key(%User{} = user) do
+ user
+ |> Repo.preload(:signing_key)
+ end
+
+ def key_id_of_local_user(%User{local: true} = user) do
+ case Repo.preload(user, :signing_key) do
+ %User{signing_key: %__MODULE__{key_id: key_id}} -> key_id
+ _ -> nil
+ end
+ end
+
+ @spec remote_changeset(__MODULE__, map) :: Changeset.t()
+ def remote_changeset(%__MODULE__{} = signing_key, attrs) do
+ signing_key
+ |> cast(attrs, [:public_key, :key_id])
+ |> validate_required([:public_key, :key_id])
+ end
+
+ @spec key_id_to_user_id(String.t()) :: String.t() | nil
+ @doc """
+ Given a key ID, return the user ID associated with that key.
+ Returns nil if the key ID is not found.
+ """
+ def key_id_to_user_id(key_id) do
+ from(sk in __MODULE__, where: sk.key_id == ^key_id)
+ |> select([sk], sk.user_id)
+ |> Repo.one()
+ end
+
+ @spec key_id_to_ap_id(String.t()) :: String.t() | nil
+ @doc """
+ Given a key ID, return the AP ID associated with that key.
+ Returns nil if the key ID is not found.
+ """
+ def key_id_to_ap_id(key_id) do
+ Logger.debug("Looking up key ID: #{key_id}")
+
+ result =
+ from(sk in __MODULE__, where: sk.key_id == ^key_id)
+ |> join(:inner, [sk], u in User, on: sk.user_id == u.id)
+ |> select([sk, u], %{user: u})
+ |> Repo.one()
+
+ case result do
+ %{user: %User{ap_id: ap_id}} -> ap_id
+ _ -> nil
+ end
+ end
+
+ @spec generate_rsa_pem() :: {:ok, binary()}
+ @doc """
+ Generate a new RSA private key and return it as a PEM-encoded string.
+ """
+ def generate_rsa_pem do
+ key = :public_key.generate_key({:rsa, 2048, 65_537})
+ entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
+ pem = :public_key.pem_encode([entry]) |> String.trim_trailing()
+ {:ok, pem}
+ end
+
+ @spec generate_local_keys(String.t()) :: {:ok, Changeset.t()} | {:error, String.t()}
+ @doc """
+ Generate a new RSA key pair and create a changeset for it
+ """
+ def generate_local_keys(ap_id) do
+ {:ok, private_pem} = generate_rsa_pem()
+ {:ok, local_pem} = private_pem_to_public_pem(private_pem)
+
+ %__MODULE__{}
+ |> change()
+ |> put_change(:public_key, local_pem)
+ |> put_change(:private_key, private_pem)
+ |> put_change(:key_id, local_key_id(ap_id))
+ end
+
+ @spec local_key_id(String.t()) :: String.t()
+ @doc """
+ Given an AP ID, return the key ID for the local user.
+ """
+ def local_key_id(ap_id) do
+ ap_id <> "#main-key"
+ end
+
+ @spec private_pem_to_public_pem(binary) :: {:ok, binary()} | {:error, String.t()}
+ @doc """
+ Given a private key in PEM format, return the corresponding public key in PEM format.
+ """
+ def private_pem_to_public_pem(private_pem) do
+ [private_key_code] = :public_key.pem_decode(private_pem)
+ private_key = :public_key.pem_entry_decode(private_key_code)
+ {:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} = private_key
+ public_key = {:RSAPublicKey, modulus, exponent}
+ public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
+ {:ok, :public_key.pem_encode([public_key])}
+ end
+
+ @spec public_key(User.t()) :: {:ok, binary()} | {:error, String.t()}
+ @doc """
+ Given a user, return the public key for that user in binary format.
+ """
+ def public_key(%User{} = user) do
+ case Repo.preload(user, :signing_key) do
+ %User{signing_key: %__MODULE__{public_key: public_key_pem}} ->
+ key =
+ public_key_pem
+ |> :public_key.pem_decode()
+ |> hd()
+ |> :public_key.pem_entry_decode()
+
+ {:ok, key}
+
+ _ ->
+ {:error, "key not found"}
+ end
+ end
+
+ def public_key(_), do: {:error, "key not found"}
+
+ def public_key_pem(%User{} = user) do
+ case Repo.preload(user, :signing_key) do
+ %User{signing_key: %__MODULE__{public_key: public_key_pem}} -> {:ok, public_key_pem}
+ _ -> {:error, "key not found"}
+ end
+ end
+
+ def public_key_pem(_e) do
+ {:error, "key not found"}
+ end
+
+ @spec private_key(User.t()) :: {:ok, binary()} | {:error, String.t()}
+ @doc """
+ Given a user, return the private key for that user in binary format.
+ """
+ def private_key(%User{} = user) do
+ case Repo.preload(user, :signing_key) do
+ %{signing_key: %__MODULE__{private_key: private_key_pem}} ->
+ key =
+ private_key_pem
+ |> :public_key.pem_decode()
+ |> hd()
+ |> :public_key.pem_entry_decode()
+
+ {:ok, key}
+
+ _ ->
+ {:error, "key not found"}
+ end
+ end
+
+ @spec get_or_fetch_by_key_id(String.t()) :: {:ok, __MODULE__} | {:error, String.t()}
+ @doc """
+ Given a key ID, return the signing key associated with that key.
+ Will either return the key if it exists locally, or fetch it from the remote instance.
+ """
+ def get_or_fetch_by_key_id(key_id) do
+ case key_id_to_user_id(key_id) do
+ nil ->
+ fetch_remote_key(key_id)
+
+ user_id ->
+ {:ok, Repo.get_by(__MODULE__, user_id: user_id)}
+ end
+ end
+
+ @spec fetch_remote_key(String.t()) :: {:ok, __MODULE__} | {:error, String.t()}
+ @doc """
+ Fetch a remote key by key ID.
+ Will send a request to the remote instance to get the key ID.
+ This request should, at the very least, return a user ID and a public key object.
+ Though bear in mind that some implementations (looking at you, pleroma) may require a signature for this request.
+ This has the potential to create an infinite loop if the remote instance requires a signature to fetch the key...
+ So if we're rejected, we should probably just give up.
+ """
+ def fetch_remote_key(key_id) do
+ Logger.debug("Fetching remote key: #{key_id}")
+
+ with {:ok, _body} = resp <-
+ Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(key_id),
+ {:ok, ap_id, public_key_pem} <- handle_signature_response(resp) do
+ Logger.debug("Fetched remote key: #{ap_id}")
+ # fetch the user
+ {:ok, user} = User.get_or_fetch_by_ap_id(ap_id)
+ # store the key
+ key = %__MODULE__{
+ user_id: user.id,
+ public_key: public_key_pem,
+ key_id: key_id
+ }
+
+ Repo.insert(key, on_conflict: :replace_all, conflict_target: :key_id)
+ else
+ e ->
+ Logger.debug("Failed to fetch remote key: #{inspect(e)}")
+ {:error, "Could not fetch key"}
+ end
+ end
+
+ # Take the response from the remote instance and extract the key details
+ # will check if the key ID matches the owner of the key, if not, error
+ defp extract_key_details(%{"id" => ap_id, "publicKey" => public_key}) do
+ if ap_id !== public_key["owner"] do
+ {:error, "Key ID does not match owner"}
+ else
+ %{"publicKeyPem" => public_key_pem} = public_key
+ {:ok, ap_id, public_key_pem}
+ end
+ end
+
+ defp handle_signature_response({:ok, body}) do
+ case body do
+ %{
+ "type" => "CryptographicKey",
+ "publicKeyPem" => public_key_pem,
+ "owner" => ap_id
+ } ->
+ {:ok, ap_id, public_key_pem}
+
+ # for when we get a subset of the user object
+ %{
+ "id" => _user_id,
+ "publicKey" => _public_key,
+ "type" => actor_type
+ }
+ when actor_type in Pleroma.Constants.actor_types() ->
+ extract_key_details(body)
+
+ %{"error" => error} ->
+ {:error, error}
+ end
+ end
+
+ defp handle_signature_response({:error, e}), do: {:error, e}
+ defp handle_signature_response(other), do: {:error, "Could not fetch key: #{inspect(other)}"}
+end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index c87072300..9b28e64d9 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1547,6 +1547,17 @@ defp normalize_attachment(%{} = attachment), do: [attachment]
defp normalize_attachment(attachment) when is_list(attachment), do: attachment
defp normalize_attachment(_), do: []
+ defp maybe_make_public_key_object(data) do
+ if is_map(data["publicKey"]) && is_binary(data["publicKey"]["publicKeyPem"]) do
+ %{
+ public_key: data["publicKey"]["publicKeyPem"],
+ key_id: data["publicKey"]["id"]
+ }
+ else
+ nil
+ end
+ end
+
defp object_to_user_data(data, additional) do
fields =
data
@@ -1578,9 +1589,16 @@ defp object_to_user_data(data, additional) do
featured_address = data["featured"]
{:ok, pinned_objects} = fetch_and_prepare_featured_from_ap_id(featured_address)
- public_key =
- if is_map(data["publicKey"]) && is_binary(data["publicKey"]["publicKeyPem"]) do
- data["publicKey"]["publicKeyPem"]
+ # first, check that the owner is correct
+ signing_key =
+ if data["id"] !== data["publicKey"]["owner"] do
+ Logger.error(
+ "Owner of the public key is not the same as the actor - not saving the public key."
+ )
+
+ nil
+ else
+ maybe_make_public_key_object(data)
end
shared_inbox =
@@ -1624,7 +1642,7 @@ defp object_to_user_data(data, additional) do
bio: data["summary"] || "",
actor_type: actor_type,
also_known_as: also_known_as,
- public_key: public_key,
+ signing_key: signing_key,
inbox: data["inbox"],
shared_inbox: shared_inbox,
pinned_objects: pinned_objects,
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 6642f7771..9c6b3655d 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -60,7 +60,26 @@ defp relay_active?(conn, _) do
end
end
- def user(conn, %{"nickname" => nickname}) do
+ @doc """
+ Render the user's AP data
+ WARNING: we cannot actually check if the request has a fragment! so let's play defensively
+ - IF we have a valid signature, serve full user
+ - IF we do not, and authorized_fetch_mode is enabled, serve the key only
+ - OTHERWISE, serve the full actor (since we don't need to worry about the signature)
+ """
+ def user(%{assigns: %{valid_signature: true}} = conn, params) do
+ render_full_user(conn, params)
+ end
+
+ def user(conn, params) do
+ if Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) do
+ render_key_only_user(conn, params)
+ else
+ render_full_user(conn, params)
+ end
+ end
+
+ defp render_full_user(conn, %{"nickname" => nickname}) do
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
conn
|> put_resp_content_type("application/activity+json")
@@ -72,6 +91,18 @@ def user(conn, %{"nickname" => nickname}) do
end
end
+ def render_key_only_user(conn, %{"nickname" => nickname}) do
+ with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
+ conn
+ |> put_resp_content_type("application/activity+json")
+ |> put_view(UserView)
+ |> render("keys.json", %{user: user})
+ else
+ nil -> {:error, :not_found}
+ %{local: false} -> {:error, :not_found}
+ end
+ end
+
def object(%{assigns: assigns} = conn, _) do
with ap_id <- Endpoint.url() <> conn.request_path,
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
diff --git a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
index 02c9b18ed..b0c940339 100644
--- a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
@@ -34,16 +34,34 @@ defp check_reject(message, actions) do
end
end
+ @spec delete_and_count(list(), term()) :: {integer(), list()}
+ defp delete_and_count(list, element), do: delete_and_count(list, element, {0, [], list})
+
+ defp delete_and_count([], _element, {0, _nlist, olist}), do: {0, olist}
+ defp delete_and_count([], _element, {count, nlist, _olist}), do: {count, Enum.reverse(nlist)}
+
+ defp delete_and_count([h | r], h, {count, nlist, olist}),
+ do: delete_and_count(r, h, {count + 1, nlist, olist})
+
+ defp delete_and_count([h | r], element, {count, nlist, olist}),
+ do: delete_and_count(r, element, {count, [h | nlist], olist})
+
+ defp insert_if_needed(list, oldcount, element) do
+ if oldcount <= 0 || Enum.member?(list, element) do
+ list
+ else
+ [element | list]
+ end
+ end
+
defp check_delist(message, actions) do
if :delist in actions do
with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do
- to =
- List.delete(message["to"] || [], Pleroma.Constants.as_public()) ++
- [user.follower_address]
+ {pubcnt, to} = delete_and_count(message["to"] || [], Pleroma.Constants.as_public())
+ {flwcnt, cc} = delete_and_count(message["cc"] || [], user.follower_address)
- cc =
- List.delete(message["cc"] || [], user.follower_address) ++
- [Pleroma.Constants.as_public()]
+ cc = insert_if_needed(cc, pubcnt, Pleroma.Constants.as_public())
+ to = insert_if_needed(to, flwcnt, user.follower_address)
message =
message
@@ -65,8 +83,8 @@ defp check_delist(message, actions) do
defp check_strip_followers(message, actions) do
if :strip_followers in actions do
with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do
- to = List.delete(message["to"] || [], user.follower_address)
- cc = List.delete(message["cc"] || [], user.follower_address)
+ {_, to} = delete_and_count(message["to"] || [], user.follower_address)
+ {_, cc} = delete_and_count(message["cc"] || [], user.follower_address)
message =
message
diff --git a/lib/pleroma/web/activity_pub/object_validators/user_validator.ex b/lib/pleroma/web/activity_pub/object_validators/user_validator.ex
index adb291a55..b80068e37 100644
--- a/lib/pleroma/web/activity_pub/object_validators/user_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/user_validator.ex
@@ -14,7 +14,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UserValidator do
@behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating
alias Pleroma.Object.Containment
- alias Pleroma.Signature
require Pleroma.Constants
@@ -23,8 +22,7 @@ def validate(object, meta)
def validate(%{"type" => type, "id" => _id} = data, meta)
when type in Pleroma.Constants.actor_types() do
- with :ok <- validate_pubkey(data),
- :ok <- validate_inbox(data),
+ with :ok <- validate_inbox(data),
:ok <- contain_collection_origin(data) do
{:ok, data, meta}
else
@@ -35,33 +33,6 @@ def validate(%{"type" => type, "id" => _id} = data, meta)
def validate(_, _), do: {:error, "Not a user object"}
- defp mabye_validate_owner(nil, _actor), do: :ok
- defp mabye_validate_owner(actor, actor), do: :ok
- defp mabye_validate_owner(_owner, _actor), do: :error
-
- defp validate_pubkey(
- %{"id" => id, "publicKey" => %{"id" => pk_id, "publicKeyPem" => _key}} = data
- )
- when id != nil do
- with {_, {:ok, kactor}} <- {:key, Signature.key_id_to_actor_id(pk_id)},
- true <- id == kactor,
- :ok <- mabye_validate_owner(Map.get(data, "owner"), id) do
- :ok
- else
- {:key, _} ->
- {:error, "Unable to determine actor id from key id"}
-
- false ->
- {:error, "Key id does not relate to user id"}
-
- _ ->
- {:error, "Actor does not own its public key"}
- end
- end
-
- # pubkey is optional atm
- defp validate_pubkey(_data), do: :ok
-
defp validate_inbox(%{"id" => id, "inbox" => inbox}) do
case Containment.same_origin(id, inbox) do
:ok -> :ok
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index 4fe394be6..07f430805 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -112,7 +112,7 @@ defp allowed_instances do
Config.get([:mrf_simple, :accept])
end
- def should_federate?(url) do
+ def should_federate?(url) when is_binary(url) do
%{host: host} = URI.parse(url)
with {nil, false} <- {nil, is_nil(host)},
@@ -137,6 +137,8 @@ def should_federate?(url) do
end
end
+ def should_federate?(_), do: false
+
@spec recipients(User.t(), Activity.t()) :: list(User.t()) | []
defp recipients(actor, activity) do
followers =
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index ca5e85f2e..5c4db39b9 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -950,8 +950,7 @@ defp build_emoji_tag({name, url}) do
"icon" => %{"url" => "#{URI.encode(url)}", "type" => "Image"},
"name" => ":" <> name <> ":",
"type" => "Emoji",
- "updated" => "1970-01-01T00:00:00Z",
- "id" => url
+ "updated" => "1970-01-01T00:00:00Z"
}
end
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 47b8e37e5..2ca31fc3c 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -5,7 +5,6 @@
defmodule Pleroma.Web.ActivityPub.UserView do
use Pleroma.Web, :view
- alias Pleroma.Keys
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
@@ -33,9 +32,7 @@ def render("endpoints.json", %{user: %User{local: true} = _user}) 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])
+ {:ok, public_key} = User.SigningKey.public_key_pem(user)
endpoints = render("endpoints.json", %{user: user})
@@ -52,7 +49,7 @@ def render("service.json", %{user: user}) do
"url" => user.ap_id,
"manuallyApprovesFollowers" => false,
"publicKey" => %{
- "id" => "#{user.ap_id}#main-key",
+ "id" => User.SigningKey.local_key_id(user.ap_id),
"owner" => user.ap_id,
"publicKeyPem" => public_key
},
@@ -70,9 +67,12 @@ def render("user.json", %{user: %User{nickname: "internal." <> _} = user}),
do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname)
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])
+ public_key =
+ case User.SigningKey.public_key_pem(user) do
+ {:ok, public_key} -> public_key
+ _ -> nil
+ end
+
user = User.sanitize_html(user)
endpoints = render("endpoints.json", %{user: user})
@@ -97,7 +97,7 @@ def render("user.json", %{user: user}) do
"url" => user.ap_id,
"manuallyApprovesFollowers" => user.is_locked,
"publicKey" => %{
- "id" => "#{user.ap_id}#main-key",
+ "id" => User.SigningKey.local_key_id(user.ap_id),
"owner" => user.ap_id,
"publicKeyPem" => public_key
},
@@ -116,6 +116,20 @@ def render("user.json", %{user: user}) do
|> Map.merge(Utils.make_json_ld_header())
end
+ def render("keys.json", %{user: user}) do
+ {:ok, public_key} = User.SigningKey.public_key_pem(user)
+
+ %{
+ "id" => user.ap_id,
+ "publicKey" => %{
+ "id" => User.SigningKey.key_id_of_local_user(user),
+ "owner" => user.ap_id,
+ "publicKeyPem" => public_key
+ }
+ }
+ |> Map.merge(Utils.make_json_ld_header())
+ end
+
def render("following.json", %{user: user, page: page} = opts) do
showing_items = (opts[:for] && opts[:for] == user) || !user.hide_follows
showing_count = showing_items || !user.hide_follows_count
diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex
index 943ad8bd4..13a5e8be2 100644
--- a/lib/pleroma/web/api_spec/schemas/poll.ex
+++ b/lib/pleroma/web/api_spec/schemas/poll.ex
@@ -32,7 +32,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
},
voters_count: %Schema{
type: :integer,
- description: "How many unique accounts have voted. Number."
+ nullable: true,
+ description:
+ "How many unique accounts have voted for a multi-selection poll. Number, or null if single-selection poll."
},
voted: %Schema{
type: :boolean,
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index 6628fcaf3..3c9fcb48c 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -66,10 +66,10 @@ defmodule Pleroma.Web.Endpoint do
}
)
- plug(Plug.Static.IndexHtml, at: "/akkoma/swaggerui")
+ plug(Plug.Static.IndexHtml, at: "/pleroma/swaggerui/")
plug(Pleroma.Web.Plugs.FrontendStatic,
- at: "/akkoma/swaggerui",
+ at: "/pleroma/swaggerui",
frontend_type: :swagger,
gzip: true,
if: &Pleroma.Web.Swagger.ui_enabled?/0,
diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex
index aa6443754..411cbd15a 100644
--- a/lib/pleroma/web/mastodon_api/views/poll_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex
@@ -19,7 +19,7 @@ def render("show.json", %{object: object, multiple: multiple, options: options}
expired: expired,
multiple: multiple,
votes_count: votes_count,
- voters_count: voters_count(object),
+ voters_count: voters_count(multiple, object),
options: options,
emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"])
}
@@ -68,11 +68,19 @@ defp options_and_votes_count(options) do
end)
end
- defp voters_count(%{data: %{"voters" => voters}}) when is_list(voters) do
+ defp voters_count(false, _poll_data) do
+ # Mastodon always sets voter count to "null" unless multiple options were selectable
+ # Some clients may rely on this to detect multiple selection polls and it can mess
+ # up percentages for some clients if we never got a correct remote voter count and
+ # only count local voters here; see https://akkoma.dev/AkkomaGang/akkoma/issues/190
+ nil
+ end
+
+ defp voters_count(_multiple, %{data: %{"voters" => voters}}) when is_list(voters) do
length(voters)
end
- defp voters_count(_), do: 0
+ defp voters_count(_, _), do: 0
defp voted_and_own_votes(%{object: object} = params, options) do
if params[:for] do
diff --git a/lib/pleroma/web/media_proxy.ex b/lib/pleroma/web/media_proxy.ex
index 61b6f2a62..9e48dda74 100644
--- a/lib/pleroma/web/media_proxy.ex
+++ b/lib/pleroma/web/media_proxy.ex
@@ -52,11 +52,11 @@ def url(url) do
@spec url_proxiable?(String.t()) :: boolean()
def url_proxiable?(url) do
- not local?(url) and not whitelisted?(url) and not blocked?(url)
+ not local?(url) and not whitelisted?(url) and not blocked?(url) and http_scheme?(url)
end
def preview_url(url, preview_params \\ []) do
- if preview_enabled?() do
+ if preview_enabled?() and url_proxiable?(url) do
encode_preview_url(url, preview_params)
else
url(url)
@@ -71,6 +71,8 @@ def preview_enabled?, do: enabled?() and !!Config.get([:media_preview_proxy, :en
def local?(url), do: String.starts_with?(url, Endpoint.url())
+ def http_scheme?(url), do: String.starts_with?(url, ["http:", "https:"])
+
def whitelisted?(url) do
%{host: domain} = URI.parse(url)
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
index ea2d86f92..9975b8dbb 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
@@ -31,7 +31,7 @@ def nodeinfo(conn, %{"version" => version}) when version in ["2.0", "2.1"] do
conn
|> put_resp_header(
"content-type",
- "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8"
+ "application/json; profile=\"http://nodeinfo.diaspora.software/ns/schema/#{version}#\"; charset=utf-8"
)
|> json(Nodeinfo.get_nodeinfo(version))
end
diff --git a/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex b/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex
new file mode 100644
index 000000000..5b090f289
--- /dev/null
+++ b/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex
@@ -0,0 +1,32 @@
+defmodule Pleroma.Web.Plugs.EnsureUserPublicKeyPlug do
+ @moduledoc """
+ This plug will attempt to pull in a user's public key if we do not have it.
+ We _should_ be able to request the URL from the key URL...
+ """
+
+ alias Pleroma.User
+
+ def init(options), do: options
+
+ def call(conn, _opts) do
+ key_id = key_id_from_conn(conn)
+
+ unless is_nil(key_id) do
+ User.SigningKey.get_or_fetch_by_key_id(key_id)
+ # now we SHOULD have the user that owns the key locally. maybe.
+ # if we don't, we'll error out when we try to validate.
+ end
+
+ conn
+ end
+
+ defp key_id_from_conn(conn) do
+ case HTTPSignatures.signature_for_conn(conn) do
+ %{"keyId" => key_id} when is_binary(key_id) ->
+ key_id
+
+ _ ->
+ nil
+ end
+ end
+end
diff --git a/lib/pleroma/web/plugs/http_signature_plug.ex b/lib/pleroma/web/plugs/http_signature_plug.ex
index e3ae99636..06527cada 100644
--- a/lib/pleroma/web/plugs/http_signature_plug.ex
+++ b/lib/pleroma/web/plugs/http_signature_plug.ex
@@ -139,12 +139,17 @@ defp maybe_require_signature(
defp maybe_require_signature(conn), do: conn
defp signature_host(conn) do
- with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
- {:ok, actor_id} <- Signature.key_id_to_actor_id(kid) do
+ with {:key_id, %{"keyId" => kid}} <- {:key_id, HTTPSignatures.signature_for_conn(conn)},
+ {:actor_id, {:ok, actor_id}} <- {:actor_id, Signature.key_id_to_actor_id(kid)} do
actor_id
else
- e ->
- {:error, e}
+ {:key_id, e} ->
+ Logger.error("Failed to extract key_id from signature: #{inspect(e)}")
+ nil
+
+ {:actor_id, e} ->
+ Logger.error("Failed to extract actor_id from signature: #{inspect(e)}")
+ nil
end
end
end
diff --git a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex
index a73def682..0d5cb9eab 100644
--- a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex
+++ b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex
@@ -4,7 +4,6 @@
defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
alias Pleroma.Helpers.AuthHelper
- alias Pleroma.Signature
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Utils
@@ -33,7 +32,7 @@ def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = con
|> assign(:valid_signature, false)
# remove me once testsuite uses mapped capabilities instead of what we do now
- {:user, nil} ->
+ {:user, _} ->
Logger.debug("Failed to map identity from signature (lookup failure)")
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}")
@@ -68,7 +67,8 @@ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
Logger.debug("Failed to map identity from signature (lookup failure)")
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}")
- only_permit_user_routes(conn)
+ conn
+ |> assign(:valid_signature, false)
_ ->
Logger.debug("Failed to map identity from signature (no payload actor mismatch)")
@@ -82,33 +82,34 @@ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
# no signature at all
def call(conn, _opts), do: conn
- defp only_permit_user_routes(%{path_info: ["users", _]} = conn) do
- conn
- |> assign(:limited_ap, true)
- end
-
- defp only_permit_user_routes(conn) do
- conn
- |> assign(:valid_signature, false)
- end
-
defp key_id_from_conn(conn) do
- with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn),
- {:ok, ap_id} <- Signature.key_id_to_actor_id(key_id) do
- ap_id
- else
+ case HTTPSignatures.signature_for_conn(conn) do
+ %{"keyId" => key_id} when is_binary(key_id) ->
+ key_id
+
_ ->
nil
end
end
defp user_from_key_id(conn) do
- with key_actor_id when is_binary(key_actor_id) <- key_id_from_conn(conn),
- {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(key_actor_id) do
+ with {:key_id, key_id} when is_binary(key_id) <- {:key_id, key_id_from_conn(conn)},
+ {:mapped_ap_id, ap_id} when is_binary(ap_id) <-
+ {:mapped_ap_id, User.SigningKey.key_id_to_ap_id(key_id)},
+ {:user_fetch, {:ok, %User{} = user}} <- {:user_fetch, User.get_or_fetch_by_ap_id(ap_id)} do
user
else
- _ ->
- nil
+ {:key_id, nil} ->
+ Logger.debug("Failed to map identity from signature (no key ID)")
+ {:key_id, nil}
+
+ {:mapped_ap_id, nil} ->
+ Logger.debug("Failed to map identity from signature (could not map key ID to AP ID)")
+ {:mapped_ap_id, nil}
+
+ {:user_fetch, {:error, _}} ->
+ Logger.debug("Failed to map identity from signature (lookup failure)")
+ {:user_fetch, nil}
end
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 8524cdad6..3ff7ec5f6 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -144,7 +144,14 @@ defmodule Pleroma.Web.Router do
})
end
+ pipeline :optional_http_signature do
+ plug(Pleroma.Web.Plugs.EnsureUserPublicKeyPlug)
+ plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
+ plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
+ end
+
pipeline :http_signature do
+ plug(Pleroma.Web.Plugs.EnsureUserPublicKeyPlug)
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
plug(Pleroma.Web.Plugs.EnsureHTTPSignaturePlug)
@@ -749,7 +756,7 @@ defmodule Pleroma.Web.Router do
scope "/", Pleroma.Web do
# Note: html format is supported only if static FE is enabled
# Note: http signature is only considered for json requests (no auth for non-json requests)
- pipe_through([:accepts_html_xml_json, :http_signature, :static_fe])
+ pipe_through([:accepts_html_xml_json, :optional_http_signature, :static_fe])
# Note: returns user _profile_ for json requests, redirects to user _feed_ for non-json ones
get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed)
diff --git a/lib/pleroma/workers/attachments_cleanup_worker.ex b/lib/pleroma/workers/attachments_cleanup_worker.ex
index f5090dae7..f1204a861 100644
--- a/lib/pleroma/workers/attachments_cleanup_worker.ex
+++ b/lib/pleroma/workers/attachments_cleanup_worker.ex
@@ -5,30 +5,65 @@
defmodule Pleroma.Workers.AttachmentsCleanupWorker do
import Ecto.Query
+ alias Pleroma.Config
alias Pleroma.Object
alias Pleroma.Repo
use Pleroma.Workers.WorkerHelper, queue: "attachments_cleanup"
+ @doc """
+ Takes object data and if necessary enqueues a job,
+ deleting all attachments of the post eligible for cleanup
+ """
+ @spec enqueue_if_needed(map()) :: {:ok, Oban.Job.t()} | {:ok, :skip} | {:error, any()}
+ def enqueue_if_needed(%{
+ "actor" => actor,
+ "attachment" => [_ | _] = attachments
+ }) do
+ with true <- Config.get([:instance, :cleanup_attachments]),
+ true <- URI.parse(actor).host == Pleroma.Web.Endpoint.host(),
+ [_ | _] <- attachments do
+ enqueue(
+ "cleanup_attachments",
+ %{"actor" => actor, "attachments" => attachments},
+ schedule_in: Config.get!([:instance, :cleanup_attachments_delay])
+ )
+ else
+ _ -> {:ok, :skip}
+ end
+ end
+
+ def enqueue_if_needed(_), do: {:ok, :skip}
+
@impl Oban.Worker
def perform(%Job{
args: %{
"op" => "cleanup_attachments",
- "object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}}
+ "attachments" => [_ | _] = attachments,
+ "actor" => actor
}
}) do
- if Pleroma.Config.get([:instance, :cleanup_attachments], false) do
- attachments
- |> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end)
- |> fetch_objects
- |> prepare_objects(actor, Enum.map(attachments, & &1["name"]))
- |> filter_objects
- |> do_clean
- end
+ attachments
+ |> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end)
+ |> fetch_objects
+ |> prepare_objects(actor, Enum.map(attachments, & &1["name"]))
+ |> filter_objects
+ |> do_clean
{:ok, :success}
end
+ # Left over already enqueued jobs in the old format
+ # This function clause can be deleted once sufficient time passed after 3.14
+ def perform(%Job{
+ args: %{
+ "op" => "cleanup_attachments",
+ "object" => %{"data" => data}
+ }
+ }) do
+ enqueue_if_needed(data)
+ end
+
def perform(%Job{args: %{"op" => "cleanup_attachments", "object" => _object}}), do: {:ok, :skip}
defp do_clean({object_ids, attachment_urls}) do
diff --git a/mix.exs b/mix.exs
index 2fe51edee..f3ddea9ce 100644
--- a/mix.exs
+++ b/mix.exs
@@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
def project do
[
app: :pleroma,
- version: version("3.13.2"),
+ version: version("3.14.1"),
elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),
@@ -145,7 +145,7 @@ defp deps do
{:ex_aws, "~> 2.4"},
{:ex_aws_s3, "~> 2.4"},
{:sweet_xml, "~> 0.7"},
- {:earmark, "~> 1.4"},
+ {:earmark, "1.4.46"},
{:bbcode_pleroma, "~> 0.2.0"},
{:argon2_elixir, "~> 3.1"},
{:cors_plug, "~> 3.0"},
@@ -181,7 +181,7 @@ defp deps do
{:remote_ip, "~> 1.1.0"},
{:captcha,
git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
- ref: "90f6ce7672f70f56708792a98d98bd05176c9176"},
+ ref: "6630c42aaaab124e697b4e513190c89d8b64e410"},
{:restarter, path: "./restarter"},
{:majic,
git: "https://akkoma.dev/AkkomaGang/majic.git",
@@ -201,7 +201,7 @@ defp deps do
## dev & test
{:ex_doc, "~> 0.30", only: :dev, runtime: false},
- {:ex_machina, "~> 2.7", only: :test},
+ {:ex_machina, "~> 2.8", only: :test},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:mock, "~> 0.3.8", only: :test},
{:excoveralls, "0.16.1", only: :test},
diff --git a/mix.lock b/mix.lock
index 09997f29d..8c86567a6 100644
--- a/mix.lock
+++ b/mix.lock
@@ -7,51 +7,51 @@
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"},
"calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.1.201603 or ~> 0.5.20 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
- "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "90f6ce7672f70f56708792a98d98bd05176c9176", [ref: "90f6ce7672f70f56708792a98d98bd05176c9176"]},
- "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"},
+ "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "6630c42aaaab124e697b4e513190c89d8b64e410", [ref: "6630c42aaaab124e697b4e513190c89d8b64e410"]},
+ "castore": {:hex, :castore, "1.0.9", "5cc77474afadf02c7c017823f460a17daa7908e991b0cc917febc90e466a375c", [:mix], [], "hexpm", "5ea956504f1ba6f2b4eb707061d8e17870de2bee95fb59d512872c2ef06925e7"},
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
- "comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"},
+ "comeonin": {:hex, :comeonin, "5.5.0", "364d00df52545c44a139bad919d7eacb55abf39e86565878e17cebb787977368", [:mix], [], "hexpm", "6287fc3ba0aad34883cbe3f7949fc1d1e738e5ccdce77165bc99490aa69f47fb"},
"concurrent_limiter": {:git, "https://akkoma.dev/AkkomaGang/concurrent-limiter.git", "a9e0b3d64574bdba761f429bb4fba0cf687b3338", [ref: "a9e0b3d64574bdba761f429bb4fba0cf687b3338"]},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"},
"cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"},
- "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"},
+ "credo": {:hex, :credo, "1.7.8", "9722ba1681e973025908d542ec3d95db5f9c549251ba5b028e251ad8c24ab8c5", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cb9e87cc64f152f3ed1c6e325e7b894dea8f5ef2e41123bd864e3cd5ceb44968"},
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
- "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"},
+ "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
- "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
+ "dialyxir": {:hex, :dialyxir, "1.4.4", "fb3ce8741edeaea59c9ae84d5cec75da00fa89fe401c72d6e047d11a61f65f70", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "cd6111e8017ccd563e65621a4d9a4a1c5cd333df30cebc7face8029cacb4eff6"},
"earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"},
- "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
+ "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"},
"eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"},
"ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
- "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.8.0", "440719cd74f09b3f01c84455707a2c3972b725c513808e68eb6c5b0ab82bf523", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 0.18.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "f1512812dc196bcb932a96c82e55f69b543dc125e9d39f5e3631a9c4ec65ef12"},
+ "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.8.2", "79350a53246ac5ec27326d208496aebceb77fa82a91744f66a9154560f0759d3", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "> 0.16.0 and < 0.20.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "6149c1c4a5ba6602a76cb09ee7a269eb60dab9694a1dbbb797f032555212de75"},
"ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"},
"elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]},
"elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"},
"elixir_xml_to_map": {:hex, :elixir_xml_to_map, "3.1.0", "4d6260486a8cce59e4bf3575fe2dd2a24766546ceeef9f93fcec6f7c62a2827a", [:mix], [{:erlsom, "~> 1.4", [hex: :erlsom, repo: "hexpm", optional: false]}], "hexpm", "8fe5f2e75f90bab07ee2161120c2dc038ebcae8135554f5582990f1c8c21f911"},
- "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
+ "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
"erlsom": {:hex, :erlsom, "1.5.1", "c8fe2babd33ff0846403f6522328b8ab676f896b793634cfe7ef181c05316c03", [:rebar3], [], "hexpm", "7965485494c5844dd127656ac40f141aadfa174839ec1be1074e7edf5b4239eb"},
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
- "ex_aws": {:hex, :ex_aws, "2.5.4", "86c5bb870a49e0ab6f5aa5dd58cf505f09d2624ebe17530db3c1b61c88a673af", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e82bd0091bb9a5bb190139599f922ff3fc7aebcca4374d65c99c4e23aa6d1625"},
- "ex_aws_s3": {:hex, :ex_aws_s3, "2.5.3", "422468e5c3e1a4da5298e66c3468b465cfd354b842e512cb1f6fbbe4e2f5bdaf", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "4f09dd372cc386550e484808c5ac5027766c8d0cd8271ccc578b82ee6ef4f3b8"},
+ "ex_aws": {:hex, :ex_aws, "2.5.6", "6f642e0f82eff10a9b470044f084b81a791cf15b393d647ea5f3e65da2794e3d", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:req, "~> 0.3", [hex: :req, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c69eec59e31fdd89d0beeb1d97e16518dd1b23ad95b3d5c9f1dcfec23d97f960"},
+ "ex_aws_s3": {:hex, :ex_aws_s3, "2.5.4", "87aaf4a2f24a48f516d7f5aaced9d128dd5d0f655c4431f9037a11a85c71109c", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "c06e7f68b33f7c0acba1361dbd951c79661a28f85aa2e0582990fccca4425355"},
"ex_const": {:hex, :ex_const, "0.3.0", "9d79516679991baf540ef445438eef1455ca91cf1a3c2680d8fb9e5bea2fe4de", [:mix], [], "hexpm", "76546322abb9e40ee4a2f454cf1c8a5b25c3672fa79bed1ea52c31e0d2428ca9"},
- "ex_doc": {:hex, :ex_doc, "0.34.0", "ab95e0775db3df71d30cf8d78728dd9261c355c81382bcd4cefdc74610bef13e", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "60734fb4c1353f270c3286df4a0d51e65a2c1d9fba66af3940847cc65a8066d7"},
- "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
+ "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"},
+ "ex_machina": {:hex, :ex_machina, "2.8.0", "a0e847b5712065055ec3255840e2c78ef9366634d62390839d4880483be38abe", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "79fe1a9c64c0c1c1fab6c4fa5d871682cb90de5885320c187d117004627a7729"},
"ex_syslogger": {:hex, :ex_syslogger, "2.0.0", "de6de5c5472a9c4fdafb28fa6610e381ae79ebc17da6490b81d785d68bd124c9", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "a52b2fe71764e9e6ecd149ab66635812f68e39279cbeee27c52c0e35e8b8019e"},
"excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"},
"expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"},
"fast_html": {:hex, :fast_html, "2.3.0", "08c1d8ead840dd3060ba02c761bed9f37f456a1ddfe30bcdcfee8f651cec06a6", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "f18e3c7668f82d3ae0b15f48d48feeb257e28aa5ab1b0dbf781c7312e5da029d"},
"fast_sanitize": {:hex, :fast_sanitize, "0.2.3", "67b93dfb34e302bef49fec3aaab74951e0f0602fd9fa99085987af05bd91c7a5", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2"},
"file_ex": {:git, "https://akkoma.dev/AkkomaGang/file_ex.git", "cc7067c7d446c2526e9ecf91d40896b088851569", [ref: "cc7067c7d446c2526e9ecf91d40896b088851569"]},
- "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
+ "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
"finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
"flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
- "floki": {:hex, :floki, "0.36.2", "a7da0193538c93f937714a6704369711998a51a6164a222d710ebd54020aa7a3", [:mix], [], "hexpm", "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"},
+ "floki": {:hex, :floki, "0.36.3", "1102f93b16a55bc5383b85ae3ec470f82dee056eaeff9195e8afdf0ef2a43c30", [:mix], [], "hexpm", "fe0158bff509e407735f6d40b3ee0d7deb47f3f3ee7c6c182ad28599f9f6b27a"},
"gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"},
"gettext": {:hex, :gettext, "0.22.3", "c8273e78db4a0bb6fba7e9f0fd881112f349a3117f7f7c598fa18c66c888e524", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "935f23447713954a6866f1bb28c3a878c4c011e802bcd68a726f5e558e4b64bd"},
"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"},
@@ -61,33 +61,34 @@
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"},
- "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
- "joken": {:hex, :joken, "2.6.1", "2ca3d8d7f83bf7196296a3d9b2ecda421a404634bfc618159981a960020480a1", [:mix], [{:jose, "~> 1.11.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "ab26122c400b3d254ce7d86ed066d6afad27e70416df947cdcb01e13a7382e68"},
+ "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
+ "joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"},
"jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"},
"jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"},
"linkify": {:hex, :linkify, "0.5.3", "5f8143d8f61f5ff08d3aeeff47ef6509492b4948d8f08007fbf66e4d2246a7f2", [:mix], [], "hexpm", "3ef35a1377d47c25506e07c1c005ea9d38d700699d92ee92825f024434258177"},
- "mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"},
+ "mail": {:hex, :mail, "0.4.2", "959229676ef11dfdfb1269ce4a611622cb70415a1d6f925d79e547848bafc14d", [:mix], [], "hexpm", "08e5b70c72b8d1605cb88ef2df2c7e41d002210a621503ea1c13f1a7916b6bd3"},
"majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "80540b36939ec83f48e76c61e5000e0fd67706f0", [ref: "80540b36939ec83f48e76c61e5000e0fd67706f0"]},
"makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
- "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"},
+ "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"},
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mfm_parser": {:git, "https://akkoma.dev/AkkomaGang/mfm-parser.git", "b21ab7754024af096f2d14247574f55f0063295b", [ref: "b21ab7754024af096f2d14247574f55f0063295b"]},
- "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
+ "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
"mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"},
"mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"},
"mogrify": {:hex, :mogrify, "0.9.3", "238c782f00271dace01369ad35ae2e9dd020feee3443b9299ea5ea6bed559841", [:mix], [], "hexpm", "0189b1e1de27455f2b9ae8cf88239cefd23d38de9276eb5add7159aea51731e6"},
- "mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"},
+ "mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
+ "nimble_ownership": {:hex, :nimble_ownership, "1.0.0", "3f87744d42c21b2042a0aa1d48c83c77e6dd9dd357e425a038dd4b49ba8b79a1", [:mix], [], "hexpm", "7c16cc74f4e952464220a73055b557a273e8b1b7ace8489ec9d86e9ad56cb2cc"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
- "oban": {:hex, :oban, "2.17.10", "c3e5bd739b5c3fdc38eba1d43ab270a8c6ca4463bb779b7705c69400b0d87678", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4afd027b8e2bc3c399b54318b4f46ee8c40251fb55a285cb4e38b5363f0ee7c4"},
- "open_api_spex": {:hex, :open_api_spex, "3.19.1", "65ccb5d06e3d664d1eec7c5ea2af2289bd2f37897094a74d7219fb03fc2b5994", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "392895827ce2984a3459c91a484e70708132d8c2c6c5363972b4b91d6bbac3dd"},
+ "oban": {:hex, :oban, "2.17.12", "33fb0cbfb92b910d48dd91a908590fe3698bb85eacec8cd0d9bc6aa13dddd6d6", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7a647d6cd6bb300073db17faabce22d80ae135da3baf3180a064fa7c4fa046e3"},
+ "open_api_spex": {:hex, :open_api_spex, "3.21.2", "6a704f3777761feeb5657340250d6d7332c545755116ca98f33d4b875777e1e5", [: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", "f42ae6ed668b895ebba3e02773cfb4b41050df26f803f2ef634c72a7687dc387"},
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
- "phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"},
- "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.1", "96798325fab2fed5a824ca204e877b81f9afd2e480f581e81f7b4b64a5a477f2", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.17", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "0ae544ff99f3c482b0807c5cec2c8289e810ecacabc04959d82c3337f4703391"},
+ "phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"},
+ "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.3", "f686701b0499a07f2e3b122d84d52ff8a31f5def386e03706c916f6feddf69ef", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "909502956916a657a197f94cc1206d9a65247538de8a5e186f7537c895d95764"},
"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.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [: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]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [: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", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"},
@@ -95,8 +96,8 @@
"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.16.0", "1d07d50cb9bb05097fdf187b31cf087c7297aafc3fed8299aac79c128a707e47", [: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", "cbf53aa1f5c4d758a7559c0bd6d59e286c2be0c6a1fac8cc3eee2f638243b93e"},
- "plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [: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", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"},
+ "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [: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", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
+ "plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [: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", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"},
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
"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"},
"poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
@@ -104,7 +105,7 @@
"postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"},
"pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
- "recon": {:hex, :recon, "2.5.5", "c108a4c406fa301a529151a3bb53158cadc4064ec0c5f99b03ddb8c0e4281bdf", [:mix, :rebar3], [], "hexpm", "632a6f447df7ccc1a4a10bdcfce71514412b16660fe59deca0fcf0aa3c054404"},
+ "recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"},
"remote_ip": {:hex, :remote_ip, "1.1.0", "cb308841595d15df3f9073b7c39243a1dd6ca56e5020295cb012c76fbec50f2d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "616ffdf66aaad6a72fc546dabf42eed87e2a99e97b09cbd92b10cc180d02ed74"},
"search_parser": {:git, "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git", "08971a81e68686f9ac465cfb6661d51c5e4e1e7f", [ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"]},
"sleeplocks": {:hex, :sleeplocks, "1.1.3", "96a86460cc33b435c7310dbd27ec82ca2c1f24ae38e34f8edde97f756503441a", [:rebar3], [], "hexpm", "d3b3958552e6eb16f463921e70ae7c767519ef8f5be46d7696cc1ed649421321"},
@@ -114,22 +115,22 @@
"swoosh": {:hex, :swoosh, "1.14.4", "94e9dba91f7695a10f49b0172c4a4cb658ef24abef7e8140394521b7f3bbb2d4", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "081c5a590e4ba85cc89baddf7b2beecf6c13f7f84a958f1cd969290815f0f026"},
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
"table_rex": {:hex, :table_rex, "4.0.0", "3c613a68ebdc6d4d1e731bc973c233500974ec3993c99fcdabb210407b90959b", [:mix], [], "hexpm", "c35c4d5612ca49ebb0344ea10387da4d2afe278387d4019e4d8111e815df8f55"},
- "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
+ "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"},
"telemetry_metrics_prometheus": {:hex, :telemetry_metrics_prometheus, "1.1.0", "1cc23e932c1ef9aa3b91db257ead31ea58d53229d407e059b29bb962c1505a13", [:mix], [{:plug_cowboy, "~> 2.1", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}], "hexpm", "d43b3659b3244da44fe0275b717701542365d4519b79d9ce895b9719c1ce4d26"},
"telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.1.0", "4e15f6d7dbedb3a4e3aed2262b7e1407f166fcb9c30ca3f96635dfbbef99965c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0dd10e7fe8070095df063798f82709b0a1224c31b8baf6278b423898d591a069"},
"telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
"temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]},
- "tesla": {:hex, :tesla, "1.9.0", "8c22db6a826e56a087eeb8cdef56889731287f53feeb3f361dec5d4c8efb6f14", [: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.6", [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]}, {: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", "7c240c67e855f7e63e795bf16d6b3f5115a81d1f44b7fe4eadbf656bae0fef8a"},
+ "tesla": {:hex, :tesla, "1.13.0", "24a068a48d107080dd7c943a593997eee265977a38020eb2ab657cca78a12502", [: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.6", [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", "7b8fc8f6b0640fa0d090af7889d12eb396460e044b6f8688a8e55e30406a2200"},
"timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
- "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"},
+ "tzdata": {:hex, :tzdata, "1.1.2", "45e5f1fcf8729525ec27c65e163be5b3d247ab1702581a94674e008413eef50b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cec7b286e608371602318c414f344941d5eb0375e14cfdab605cca2fe66cba8b"},
"ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"},
"vex": {:hex, :vex, "0.9.2", "fe061acc9e0907d983d46b51bf35d58176f0fe6eb7ba3b33c9336401bf42b6d1", [:mix], [], "hexpm", "76e709a9762e98c6b462dfce92e9b5dfbf712839227f2da8add6dd11549b12cb"},
"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.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [: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", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"},
+ "websock_adapter": {:hex, :websock_adapter, "0.5.7", "65fa74042530064ef0570b75b43f5c49bb8b235d6515671b3d250022cb8a1f9e", [: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", "d0f478ee64deddfec64b800673fd6e0c8888b079d9f3444dd96d2a98383bdbd1"},
"websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"},
}
diff --git a/priv/repo/migrations/20240625213637_create_signing_key_table.exs b/priv/repo/migrations/20240625213637_create_signing_key_table.exs
new file mode 100644
index 000000000..062edc66f
--- /dev/null
+++ b/priv/repo/migrations/20240625213637_create_signing_key_table.exs
@@ -0,0 +1,13 @@
+defmodule Pleroma.Repo.Migrations.CreateSigningKeyTable do
+ use Ecto.Migration
+
+ def change do
+ create table(:signing_keys, primary_key: false) do
+ add :user_id, references(:users, type: :uuid, on_delete: :delete_all)
+ add :key_id, :text, primary_key: true
+ add :public_key, :text
+ add :private_key, :text
+ timestamps()
+ end
+ end
+end
diff --git a/priv/repo/migrations/20240625220752_move_signing_keys.exs b/priv/repo/migrations/20240625220752_move_signing_keys.exs
new file mode 100644
index 000000000..f5569ce09
--- /dev/null
+++ b/priv/repo/migrations/20240625220752_move_signing_keys.exs
@@ -0,0 +1,38 @@
+defmodule Pleroma.Repo.Migrations.MoveSigningKeys do
+ use Ecto.Migration
+ alias Pleroma.User
+ alias Pleroma.Repo
+ import Ecto.Query
+
+ def up do
+ # we do not handle remote users here!
+ # because we want to store a key id -> user id mapping, and we don't
+ # currently store key ids for remote users...
+ # Also this MUST use select, else the migration will fail in future installs with new user fields!
+ from(u in Pleroma.User,
+ where: u.local == true,
+ select: {u.id, u.keys, u.ap_id}
+ )
+ |> Repo.stream(timeout: :infinity)
+ |> Enum.each(fn
+ {user_id, private_key, ap_id} ->
+ IO.puts("Migrating user #{user_id}")
+ # we can precompute the public key here...
+ # we do use it on every user view which makes it a bit of a dos attack vector
+ # so we should probably cache it
+ {:ok, public_key} = User.SigningKey.private_pem_to_public_pem(private_key)
+
+ key = %User.SigningKey{
+ user_id: user_id,
+ public_key: public_key,
+ key_id: User.SigningKey.local_key_id(ap_id),
+ private_key: private_key
+ }
+
+ {:ok, _} = Repo.insert(key)
+ end)
+ end
+
+ # no need to rollback
+ def down, do: :ok
+end
diff --git a/priv/repo/migrations/20241126093029_add_signing_key_index.exs b/priv/repo/migrations/20241126093029_add_signing_key_index.exs
new file mode 100644
index 000000000..25df1bb7f
--- /dev/null
+++ b/priv/repo/migrations/20241126093029_add_signing_key_index.exs
@@ -0,0 +1,7 @@
+defmodule Pleroma.Repo.Migrations.AddSigningKeyIndex do
+ use Ecto.Migration
+
+ def change do
+ create_if_not_exists(index(:signing_keys, [:user_id], name: :signing_keys_user_id_index))
+ end
+end
diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex
index 74de910fd..96473203e 100644
--- a/priv/scrubbers/default.ex
+++ b/priv/scrubbers/default.ex
@@ -124,6 +124,119 @@ defmodule Pleroma.HTML.Scrubber.Default do
Meta.allow_tag_with_these_attributes(:font, ["face"])
end
+ if Pleroma.Config.get!([:markup, :allow_math]) do
+ Meta.allow_tag_with_these_attributes("annotation", ["encoding"])
+ Meta.allow_tag_with_these_attributes(:"annotation-xml", ["encoding"])
+
+ Meta.allow_tag_with_these_attributes(:math, [
+ "display",
+ "displaystyle",
+ "mathvariant",
+ "scriptlevel"
+ ])
+
+ basic_math_tags = [
+ "maction",
+ "merror",
+ :mi,
+ "mmultiscripts",
+ :mn,
+ "mphantom",
+ "mprescripts",
+ "mroot",
+ "mrow",
+ "ms",
+ "msqrt",
+ "mstyle",
+ "msub",
+ "msubsup",
+ "msup",
+ "mtable",
+ "mtext",
+ "mtr",
+ "semantics"
+ ]
+
+ for tag <- basic_math_tags do
+ Meta.allow_tag_with_these_attributes(unquote(tag), [
+ "mathvariant",
+ "displaystyle",
+ "scriptlevel"
+ ])
+ end
+
+ Meta.allow_tag_with_these_attributes("mfrac", [
+ "displaystyle",
+ "linethickness",
+ "mathvariant",
+ "scriptlevel"
+ ])
+
+ Meta.allow_tag_with_these_attributes(:mo, [
+ "displaystyle",
+ "form",
+ "largeop",
+ "lspace",
+ "mathvariant",
+ "minsize",
+ "movablelimits",
+ "rspace",
+ "scriptlevel",
+ "stretchy",
+ "symmetric"
+ ])
+
+ Meta.allow_tag_with_these_attributes("mover", [
+ "accent",
+ "displaystyle",
+ "mathvariant",
+ "scriptlevel"
+ ])
+
+ Meta.allow_tag_with_these_attributes("mpadded", [
+ "depth",
+ "displaystyle",
+ "height",
+ "lspace",
+ "mathvariant",
+ "scriptlevel",
+ "voffset",
+ "width"
+ ])
+
+ Meta.allow_tag_with_these_attributes("mspace", [
+ "depth",
+ "displaystyle",
+ "height",
+ "mathvariant",
+ "scriptlevel",
+ "width"
+ ])
+
+ Meta.allow_tag_with_these_attributes("mtd", [
+ "columnspan",
+ "displaystyle",
+ "mathvariant",
+ "rowspan",
+ "scriptlevel"
+ ])
+
+ Meta.allow_tag_with_these_attributes("munder", [
+ "accentunder",
+ "displaystyle",
+ "mathvariant",
+ "scriptlevel"
+ ])
+
+ Meta.allow_tag_with_these_attributes("munderover", [
+ "accent",
+ "accentunder",
+ "displaystyle",
+ "mathvariant",
+ "scriptlevel"
+ ])
+ end
+
Meta.allow_tag_with_these_attributes(:center, [])
Meta.allow_tag_with_these_attributes(:small, [])
diff --git a/test/mix/tasks/pleroma/config_test.exs b/test/mix/tasks/pleroma/config_test.exs
index 3b09f656b..75c67efb4 100644
--- a/test/mix/tasks/pleroma/config_test.exs
+++ b/test/mix/tasks/pleroma/config_test.exs
@@ -51,7 +51,6 @@ test "error if file with custom settings doesn't exist" do
clear_config(:configurable_from_database, true)
end
- @tag capture_log: true
test "config migration refused when deprecated settings are found" do
clear_config([:media_proxy, :whitelist], ["domain_without_scheme.com"])
assert config_records() == []
diff --git a/test/mix/tasks/pleroma/database_test.exs b/test/mix/tasks/pleroma/database_test.exs
index 20f113e6e..4f97a978a 100644
--- a/test/mix/tasks/pleroma/database_test.exs
+++ b/test/mix/tasks/pleroma/database_test.exs
@@ -353,7 +353,7 @@ test "with the --keep-threads option it keeps old threads with bookmarked posts"
test "We don't have unexpected tables which may contain objects that are referenced by activities" do
# We can delete orphaned activities. For that we look for the objects they reference in the 'objects', 'activities', and 'users' table.
- # If someone adds another table with objects (idk, maybe with separate relations, or collections or w/e), then we need to make sure we
+ # If someone adds another table with objects (idk, maybe with separate relations, or collections or w/e), then we need to make sure we
# add logic for that in the 'prune_objects' task so that we don't wrongly delete their corresponding activities.
# So when someone adds (or removes) a table, this test will fail.
# Either the table contains objects which can be referenced from the activities table
@@ -401,6 +401,7 @@ test "We don't have unexpected tables which may contain objects that are referen
["rich_media_card"],
["scheduled_activities"],
["schema_migrations"],
+ ["signing_keys"],
["thread_mutes"],
["user_follows_hashtag"],
["user_frontend_setting_profiles"],
diff --git a/test/pleroma/emoji/pack_test.exs b/test/pleroma/emoji/pack_test.exs
index f5d2e2eef..049f27a92 100644
--- a/test/pleroma/emoji/pack_test.exs
+++ b/test/pleroma/emoji/pack_test.exs
@@ -64,7 +64,10 @@ test "returns error when zip file is bad", %{pack: pack} do
path: Path.absname("test/instance_static/emoji/test_pack/blank.png")
}
- assert Pack.add_file(pack, nil, nil, file) == {:error, :einval}
+ # this varies by erlang OTP
+ possible_error_codes = [:bad_eocd, :einval]
+ {:error, code} = Pack.add_file(pack, nil, nil, file)
+ assert Enum.member?(possible_error_codes, code)
end
test "returns pack when zip file is empty", %{pack: pack} do
diff --git a/test/pleroma/keys_test.exs b/test/pleroma/keys_test.exs
deleted file mode 100644
index 9a15bf06e..000000000
--- a/test/pleroma/keys_test.exs
+++ /dev/null
@@ -1,24 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.KeysTest do
- use Pleroma.DataCase, async: true
-
- alias Pleroma.Keys
-
- test "generates an RSA private key pem" do
- {:ok, key} = Keys.generate_rsa_pem()
-
- assert is_binary(key)
- assert Regex.match?(~r/RSA/, key)
- end
-
- test "returns a public and private key from a pem" do
- pem = File.read!("test/fixtures/private_key.pem")
- {:ok, private, public} = Keys.keys_from_pem(pem)
-
- assert elem(private, 0) == :RSAPrivateKey
- assert elem(public, 0) == :RSAPublicKey
- end
-end
diff --git a/test/pleroma/object/containment_test.exs b/test/pleroma/object/containment_test.exs
index f8f40a3ac..1a1d01473 100644
--- a/test/pleroma/object/containment_test.exs
+++ b/test/pleroma/object/containment_test.exs
@@ -9,7 +9,6 @@ defmodule Pleroma.Object.ContainmentTest do
alias Pleroma.User
import Pleroma.Factory
- import ExUnit.CaptureLog
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@@ -136,23 +135,17 @@ test "contain_id_to_fetch() allows matching IDs" do
)
end
- test "contain_id_to_fetch() allows display URLs" do
+ test "contain_id_to_fetch() allows fragments and normalises domain casing" do
data = %{
- "id" => "http://example.com/~alyssa/activities/1234.json",
- "url" => "http://example.com/@alyssa/status/1234"
+ "id" => "http://example.com/users/capybara",
+ "url" => "http://example.com/@capybara"
}
- :ok =
- Containment.contain_id_to_fetch(
- "http://example.com/@alyssa/status/1234",
- data
- )
-
- :ok =
- Containment.contain_id_to_fetch(
- "http://example.com/@alyssa/status/1234/",
- data
- )
+ assert :ok ==
+ Containment.contain_id_to_fetch(
+ "http://EXAMPLE.com/users/capybara#key",
+ data
+ )
end
test "users cannot be collided through fake direction spoofing attempts" do
@@ -164,10 +157,14 @@ test "users cannot be collided through fake direction spoofing attempts" do
follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
})
- assert capture_log(fn ->
- {:error, _} = User.get_or_fetch_by_ap_id("https://n1u.moe/users/rye")
- end) =~
- "[error] Could not decode user at fetch https://n1u.moe/users/rye"
+ # Fetch from an attempted spoof id will suceed, but automatically retrieve
+ # the real data from the homeserver instead of naïvely using the spoof
+ {:ok, fetched_user} = User.get_or_fetch_by_ap_id("https://n1u.moe/users/rye")
+
+ refute fetched_user.name == "evil rye"
+ refute fetched_user.raw_bio == "boooo!"
+ assert fetched_user.name == "♡ rye ♡"
+ assert fetched_user.nickname == "rye@niu.moe"
end
test "contain_origin_from_id() gracefully handles cases where no ID is present" do
diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs
index 12154cb05..6b87635eb 100644
--- a/test/pleroma/object/fetcher_test.exs
+++ b/test/pleroma/object/fetcher_test.exs
@@ -22,6 +22,7 @@ defp spoofed_object_with_ids(
|> Jason.decode!()
|> Map.put("id", id)
|> Map.put("actor", actor_id)
+ |> Map.put("attributedTo", actor_id)
|> Jason.encode!()
end
@@ -109,7 +110,7 @@ defp spoofed_object_with_ids(
body: spoofed_object_with_ids("https://patch.cx/objects/spoof_media_redirect1")
}
- # Spoof: cross-domain redirect with final domain id
+ # Spoof: cross-domain redirect with final domain id, but original id actor
%{method: :get, url: "https://patch.cx/objects/spoof_media_redirect2"} ->
%Tesla.Env{
status: 200,
@@ -118,6 +119,19 @@ defp spoofed_object_with_ids(
body: spoofed_object_with_ids("https://media.patch.cx/objects/spoof_media_redirect2")
}
+ # No-Spoof: cross-domain redirect with id and actor from final domain
+ %{method: :get, url: "https://patch.cx/objects/spoof_media_redirect3"} ->
+ %Tesla.Env{
+ status: 200,
+ url: "https://media.patch.cx/objects/spoof_media_redirect3",
+ headers: [{"content-type", "application/activity+json"}],
+ body:
+ spoofed_object_with_ids(
+ "https://media.patch.cx/objects/spoof_media_redirect3",
+ "https://media.patch.cx/users/rin"
+ )
+ }
+
# No-Spoof: same domain redirect
%{method: :get, url: "https://patch.cx/objects/spoof_redirect"} ->
%Tesla.Env{
@@ -189,7 +203,6 @@ defp spoofed_object_with_ids(
:ok
end
- @tag capture_log: true
test "it works when fetching the OP actor errors out" do
# Here we simulate a case where the author of the OP can't be read
assert {:ok, _} =
@@ -252,7 +265,7 @@ test "it does not fetch a spoofed object with wrong content type" do
end
test "it does not fetch a spoofed object with id different from URL" do
- assert {:error, :id_mismatch} =
+ assert {:error, :not_found} =
Fetcher.fetch_and_contain_remote_object_from_id(
"https://patch.cx/media/03ca3c8b4ac3ddd08bf0f84be7885f2f88de0f709112131a22d83650819e36c2.json"
)
@@ -264,19 +277,29 @@ test "it does not fetch a spoofed object with id different from URL" do
end
test "it does not fetch an object via cross-domain redirects (initial id)" do
- assert {:error, {:cross_domain_redirect, true}} =
+ assert {:error, {:containment, _}} =
Fetcher.fetch_and_contain_remote_object_from_id(
"https://patch.cx/objects/spoof_media_redirect1"
)
end
- test "it does not fetch an object via cross-domain redirects (final id)" do
- assert {:error, {:cross_domain_redirect, true}} =
+ test "it does not fetch an object via cross-domain redirect if the actor is from the original domain" do
+ assert {:error, {:containment, :error}} =
Fetcher.fetch_and_contain_remote_object_from_id(
"https://patch.cx/objects/spoof_media_redirect2"
)
end
+ test "it allows cross-domain redirects when id and author are from final domain" do
+ assert {:ok, %{"id" => id, "attributedTo" => author}} =
+ Fetcher.fetch_and_contain_remote_object_from_id(
+ "https://patch.cx/objects/spoof_media_redirect3"
+ )
+
+ assert URI.parse(id).host == "media.patch.cx"
+ assert URI.parse(author).host == "media.patch.cx"
+ end
+
test "it accepts same-domain redirects" do
assert {:ok, %{"id" => id} = _object} =
Fetcher.fetch_and_contain_remote_object_from_id(
@@ -755,7 +778,7 @@ test "should return ok if the content type is application/activity+json" do
assert {:ok, _, "{}"} = Fetcher.get_object("https://mastodon.social/2")
end
- test "should return ok if the content type is application/ld+json with a profile" do
+ test "should return ok if the content type is application/ld+json with the ActivityStream profile" do
Tesla.Mock.mock(fn
%{
method: :get,
@@ -775,6 +798,26 @@ test "should return ok if the content type is application/ld+json with a profile
assert {:ok, _, "{}"} = Fetcher.get_object("https://mastodon.social/2")
end
+ test "should return ok if the content type is application/ld+json with several profiles" do
+ Tesla.Mock.mock(fn
+ %{
+ method: :get,
+ url: "https://mastodon.social/2"
+ } ->
+ %Tesla.Env{
+ status: 200,
+ url: "https://mastodon.social/2",
+ headers: [
+ {"content-type",
+ "application/ld+json; profile=\"https://example.org/ns/superduperspec https://www.w3.org/ns/activitystreams\""}
+ ],
+ body: "{}"
+ }
+ end)
+
+ assert {:ok, _, "{}"} = Fetcher.get_object("https://mastodon.social/2")
+ end
+
test "should not return ok with other content types" do
Tesla.Mock.mock(fn
%{
diff --git a/test/pleroma/signature_test.exs b/test/pleroma/signature_test.exs
index 768c78f21..86e2b138a 100644
--- a/test/pleroma/signature_test.exs
+++ b/test/pleroma/signature_test.exs
@@ -6,7 +6,6 @@ defmodule Pleroma.SignatureTest do
use Pleroma.DataCase, async: false
@moduletag :mocked
- import ExUnit.CaptureLog
import Pleroma.Factory
import Tesla.Mock
import Mock
@@ -35,25 +34,23 @@ defp make_fake_conn(key_id),
do: %Plug.Conn{req_headers: %{"signature" => make_fake_signature(key_id <> "#main-key")}}
describe "fetch_public_key/1" do
- test "it returns key" do
+ test "it returns the key" do
expected_result = {:ok, @rsa_public_key}
- user = insert(:user, public_key: @public_key)
+ user =
+ insert(:user)
+ |> with_signing_key(public_key: @public_key)
assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == expected_result
end
- test "it returns error when not found user" do
- assert capture_log(fn ->
- assert Signature.fetch_public_key(make_fake_conn("https://test-ap-id")) ==
- {:error, :error}
- end) =~ "[error] Could not decode user"
- end
-
test "it returns error if public key is nil" do
- user = insert(:user, public_key: nil)
+ # this actually needs the URL to be valid
+ user = insert(:user)
+ key_id = user.ap_id <> "#main-key"
+ Tesla.Mock.mock(fn %{url: ^key_id} -> {:ok, %{status: 404}} end)
- assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == {:error, :error}
+ assert {:error, _} = Signature.fetch_public_key(make_fake_conn(user.ap_id))
end
end
@@ -63,12 +60,6 @@ test "it returns key" do
assert Signature.refetch_public_key(make_fake_conn(ap_id)) == {:ok, @rsa_public_key}
end
-
- test "it returns error when not found user" do
- assert capture_log(fn ->
- {:error, _} = Signature.refetch_public_key(make_fake_conn("https://test-ap_id"))
- end) =~ "[error] Could not decode user"
- end
end
defp split_signature(sig) do
@@ -104,9 +95,9 @@ defp assert_part_equal(part_a, part_b) do
test "it returns signature headers" do
user =
insert(:user, %{
- ap_id: "https://mastodon.social/users/lambadalambda",
- keys: @private_key
+ ap_id: "https://mastodon.social/users/lambadalambda"
})
+ |> with_signing_key(private_key: @private_key)
headers = %{
host: "test.test",
@@ -121,50 +112,15 @@ test "it returns signature headers" do
"keyId=\"https://mastodon.social/users/lambadalambda#main-key\",algorithm=\"rsa-sha256\",headers=\"content-length host\",signature=\"sibUOoqsFfTDerquAkyprxzDjmJm6erYc42W5w1IyyxusWngSinq5ILTjaBxFvfarvc7ci1xAi+5gkBwtshRMWm7S+Uqix24Yg5EYafXRun9P25XVnYBEIH4XQ+wlnnzNIXQkU3PU9e6D8aajDZVp3hPJNeYt1gIPOA81bROI8/glzb1SAwQVGRbqUHHHKcwR8keiR/W2h7BwG3pVRy4JgnIZRSW7fQogKedDg02gzRXwUDFDk0pr2p3q6bUWHUXNV8cZIzlMK+v9NlyFbVYBTHctAR26GIAN6Hz0eV0mAQAePHDY1mXppbA8Gpp6hqaMuYfwifcXmcc+QFm4e+n3A==\""
)
end
-
- test "it returns error" do
- user = insert(:user, %{ap_id: "https://mastodon.social/users/lambadalambda", keys: ""})
-
- assert Signature.sign(
- user,
- %{host: "test.test", "content-length": "100"}
- ) == {:error, []}
- end
end
describe "key_id_to_actor_id/1" do
- test "it properly deduces the actor id for misskey" do
- assert Signature.key_id_to_actor_id("https://example.com/users/1234/publickey") ==
- {:ok, "https://example.com/users/1234"}
- end
+ test "it reverses the key id to actor id" do
+ user =
+ insert(:user)
+ |> with_signing_key()
- test "it properly deduces the actor id for mastodon and pleroma" do
- assert Signature.key_id_to_actor_id("https://example.com/users/1234#main-key") ==
- {:ok, "https://example.com/users/1234"}
- end
-
- test "it deduces the actor id for gotoSocial" do
- assert Signature.key_id_to_actor_id("https://example.com/users/1234/main-key") ==
- {:ok, "https://example.com/users/1234"}
- end
-
- test "it deduces the actor ID for streams" do
- assert Signature.key_id_to_actor_id("https://example.com/users/1234?operation=getkey") ==
- {:ok, "https://example.com/users/1234"}
- end
-
- test "it deduces the actor ID for bridgy" do
- assert Signature.key_id_to_actor_id("https://example.com/1234#key") ==
- {:ok, "https://example.com/1234"}
- end
-
- test "it calls webfinger for 'acct:' accounts" do
- with_mock(Pleroma.Web.WebFinger,
- finger: fn _ -> {:ok, %{"ap_id" => "https://gensokyo.2hu/users/raymoo"}} end
- ) do
- assert Signature.key_id_to_actor_id("acct:raymoo@gensokyo.2hu") ==
- {:ok, "https://gensokyo.2hu/users/raymoo"}
- end
+ assert Signature.key_id_to_actor_id(user.signing_key.key_id) == {:ok, user.ap_id}
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 2e017cd7e..1f798556b 100644
--- a/test/pleroma/upload/filter/exiftool/strip_location_test.exs
+++ b/test/pleroma/upload/filter/exiftool/strip_location_test.exs
@@ -115,6 +115,15 @@ test "verify webp files are skipped" do
assert Filter.Exiftool.StripMetadata.filter(upload) == {:ok, :noop}
end
+ test "verify bmp files are skipped" do
+ upload = %Pleroma.Upload{
+ name: "sample.bmp",
+ content_type: "image/bmp"
+ }
+
+ assert Filter.Exiftool.StripMetadata.filter(upload) == {:ok, :noop}
+ end
+
test "verify svg files are skipped" do
upload = %Pleroma.Upload{
name: "sample.svg",
diff --git a/test/pleroma/user_search_test.exs b/test/pleroma/user_search_test.exs
index 2af19b6de..b499629c8 100644
--- a/test/pleroma/user_search_test.exs
+++ b/test/pleroma/user_search_test.exs
@@ -259,7 +259,7 @@ test "works with URIs" do
|> Map.put(:multi_factor_authentication_settings, nil)
|> Map.put(:notification_settings, nil)
- assert user == expected
+ assert_user_match(user, expected)
end
test "excludes a blocked users from search result" do
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index e3f0bb415..ac886aaf9 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -639,11 +639,12 @@ test "it sets the password_hash, ap_id, private key and followers collection add
changeset = User.register_changeset(%User{}, @full_user_data)
assert changeset.valid?
-
assert is_binary(changeset.changes[:password_hash])
- assert is_binary(changeset.changes[:keys])
assert changeset.changes[:ap_id] == User.ap_id(%User{nickname: @full_user_data.nickname})
- assert is_binary(changeset.changes[:keys])
+ assert changeset.changes[:signing_key]
+ assert changeset.changes[:signing_key].valid?
+ assert is_binary(changeset.changes[:signing_key].changes.private_key)
+ assert is_binary(changeset.changes[:signing_key].changes.public_key)
assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers"
end
@@ -814,7 +815,6 @@ test "gets an existing user by fully qualified nickname, case insensitive" do
assert user == fetched_user
end
- @tag capture_log: true
test "returns nil if no user could be fetched" do
{:error, fetched_user} = User.get_or_fetch_by_nickname("nonexistant@social.heldscal.la")
assert fetched_user == "not found nonexistant@social.heldscal.la"
@@ -871,7 +871,6 @@ test "if nicknames clash, the old user gets a prefix with the old id to the nick
assert orig_user.nickname == "#{orig_user.id}.admin@mastodon.example.org"
end
- @tag capture_log: true
test "it returns the old user if stale, but unfetchable" do
a_week_ago = NaiveDateTime.add(NaiveDateTime.utc_now(), -604_800)
@@ -967,6 +966,21 @@ test "it is invalid given a local user" do
refute cs.valid?
end
+
+ test "it truncates fields" do
+ clear_config([:instance, :max_remote_account_fields], 2)
+
+ fields = [
+ %{"name" => "One", "value" => "Uno"},
+ %{"name" => "Two", "value" => "Dos"},
+ %{"name" => "Three", "value" => "Tres"}
+ ]
+
+ cs = User.remote_user_changeset(@valid_remote |> Map.put(:fields, fields))
+
+ assert [%{"name" => "One", "value" => "Uno"}, %{"name" => "Two", "value" => "Dos"}] ==
+ Ecto.Changeset.get_field(cs, :fields)
+ end
end
describe "followers and friends" do
@@ -1665,7 +1679,6 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do
name: "qqqqqqq",
password_hash: "pdfk2$1b3n159001",
keys: "RSA begin buplic key",
- public_key: "--PRIVATE KEYE--",
avatar: %{"a" => "b"},
tags: ["qqqqq"],
banner: %{"a" => "b"},
@@ -1704,8 +1717,6 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do
email: nil,
name: nil,
password_hash: nil,
- keys: "RSA begin buplic key",
- public_key: "--PRIVATE KEYE--",
avatar: %{},
tags: [],
last_refreshed_at: nil,
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index b325bcb9a..5b3697244 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -573,7 +573,6 @@ test "it inserts an incoming activity into the database", %{conn: conn} do
assert Activity.get_by_ap_id(data["id"])
end
- @tag capture_log: true
test "it inserts an incoming activity into the database" <>
"even if we can't fetch the user but have it in our db",
%{conn: conn} do
@@ -584,6 +583,7 @@ test "it inserts an incoming activity into the database" <>
local: false,
last_refreshed_at: nil
)
+ |> with_signing_key()
data =
File.read!("test/fixtures/mastodon-post-activity.json")
@@ -594,7 +594,7 @@ test "it inserts an incoming activity into the database" <>
conn =
conn
|> assign(:valid_signature, true)
- |> put_req_header("signature", "keyId=\"#{user.ap_id}/main-key\"")
+ |> put_req_header("signature", "keyId=\"#{user.signing_key.key_id}\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data)
@@ -608,7 +608,10 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn} do
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
sender_url = data["actor"]
- sender = insert(:user, ap_id: data["actor"])
+
+ sender =
+ insert(:user, ap_id: data["actor"])
+ |> with_signing_key()
Instances.set_consistently_unreachable(sender_url)
refute Instances.reachable?(sender_url)
@@ -616,7 +619,7 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn} do
conn =
conn
|> assign(:valid_signature, true)
- |> put_req_header("signature", "keyId=\"#{sender.ap_id}/main-key\"")
+ |> put_req_header("signature", "keyId=\"#{sender.signing_key.key_id}\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data)
@@ -641,7 +644,7 @@ test "accept follow activity", %{conn: conn} do
assert "ok" ==
conn
|> assign(:valid_signature, true)
- |> put_req_header("signature", "keyId=\"#{followed_relay.ap_id}/main-key\"")
+ |> put_req_header("signature", "keyId=\"#{followed_relay.ap_id}#main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/inbox", accept)
|> json_response(200)
@@ -678,6 +681,7 @@ test "accepts Add/Remove activities", %{conn: conn} do
|> String.replace("{{nickname}}", "lain")
actor = "https://example.com/users/lain"
+ key_id = "#{actor}/main-key"
insert(:user,
ap_id: actor,
@@ -705,6 +709,16 @@ test "accepts Add/Remove activities", %{conn: conn} do
headers: [{"content-type", "application/activity+json"}]
}
+ %{
+ method: :get,
+ url: ^key_id
+ } ->
+ %Tesla.Env{
+ status: 200,
+ body: user,
+ headers: [{"content-type", "application/activity+json"}]
+ }
+
%{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
%Tesla.Env{
status: 200,
@@ -778,12 +792,14 @@ test "mastodon pin/unpin", %{conn: conn} do
|> String.replace("{{nickname}}", "lain")
actor = "https://example.com/users/lain"
+ key_id = "#{actor}/main-key"
sender =
insert(:user,
ap_id: actor,
featured_address: "https://example.com/users/lain/collections/featured"
)
+ |> with_signing_key()
Tesla.Mock.mock(fn
%{
@@ -806,6 +822,16 @@ test "mastodon pin/unpin", %{conn: conn} do
headers: [{"content-type", "application/activity+json"}]
}
+ %{
+ method: :get,
+ url: ^key_id
+ } ->
+ %Tesla.Env{
+ status: 200,
+ body: user,
+ headers: [{"content-type", "application/activity+json"}]
+ }
+
%{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
%Tesla.Env{
status: 200,
@@ -839,7 +865,7 @@ test "mastodon pin/unpin", %{conn: conn} do
assert "ok" ==
conn
|> assign(:valid_signature, true)
- |> put_req_header("signature", "keyId=\"#{sender.ap_id}/main-key\"")
+ |> put_req_header("signature", "keyId=\"#{sender.signing_key.key_id}\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data)
|> json_response(200)
@@ -901,7 +927,9 @@ test "it inserts an incoming activity into the database", %{conn: conn, data: da
end
test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
- user = insert(:user)
+ user =
+ insert(:user)
+ |> with_signing_key()
data =
data
@@ -946,7 +974,9 @@ test "it accepts messages with cc as string instead of array", %{conn: conn, dat
end
test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
- user = insert(:user)
+ user =
+ insert(:user)
+ |> with_signing_key()
data =
data
@@ -973,7 +1003,10 @@ test "it accepts announces with to as string instead of array", %{conn: conn} do
user = insert(:user)
{:ok, post} = CommonAPI.post(user, %{status: "hey"})
- announcer = insert(:user, local: false)
+
+ announcer =
+ insert(:user, local: false)
+ |> with_signing_key()
data = %{
"@context" => "https://www.w3.org/ns/activitystreams",
@@ -988,7 +1021,7 @@ test "it accepts announces with to as string instead of array", %{conn: conn} do
conn =
conn
|> assign(:valid_signature, true)
- |> put_req_header("signature", "keyId=\"#{announcer.ap_id}/main-key\"")
+ |> put_req_header("signature", "keyId=\"#{announcer.signing_key.key_id}\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data)
@@ -1003,7 +1036,10 @@ test "it accepts messages from actors that are followed by the user", %{
data: data
} do
recipient = insert(:user)
- actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
+
+ actor =
+ insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
+ |> with_signing_key()
{:ok, recipient, actor} = User.follow(recipient, actor)
@@ -1019,7 +1055,7 @@ test "it accepts messages from actors that are followed by the user", %{
conn =
conn
|> assign(:valid_signature, true)
- |> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"")
+ |> put_req_header("signature", "keyId=\"#{actor.signing_key.key_id}\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{recipient.nickname}/inbox", data)
@@ -1056,7 +1092,10 @@ test "it returns a note activity in a collection", %{conn: conn} do
end
test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
- user = insert(:user)
+ user =
+ insert(:user, ap_id: data["actor"])
+ |> with_signing_key()
+
data = Map.put(data, "bcc", [user.ap_id])
sender_host = URI.parse(data["actor"]).host
@@ -1066,7 +1105,7 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn, da
conn =
conn
|> assign(:valid_signature, true)
- |> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
+ |> put_req_header("signature", "keyId=\"#{user.signing_key.key_id}\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data)
@@ -1074,9 +1113,9 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn, da
assert Instances.reachable?(sender_host)
end
- @tag capture_log: true
test "it removes all follower collections but actor's", %{conn: conn} do
[actor, recipient] = insert_pair(:user)
+ actor = with_signing_key(actor)
to = [
recipient.ap_id,
@@ -1105,7 +1144,7 @@ test "it removes all follower collections but actor's", %{conn: conn} do
conn
|> assign(:valid_signature, true)
- |> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"")
+ |> put_req_header("signature", "keyId=\"#{actor.signing_key.key_id}\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{recipient.nickname}/inbox", data)
|> json_response(200)
@@ -1138,10 +1177,13 @@ test "it requires authentication", %{conn: conn} do
assert json_response(ret_conn, 200)
end
- @tag capture_log: true
test "forwarded report", %{conn: conn} do
admin = insert(:user, is_admin: true)
- actor = insert(:user, local: false)
+
+ actor =
+ insert(:user, local: false)
+ |> with_signing_key()
+
remote_domain = URI.parse(actor.ap_id).host
reported_user = insert(:user)
@@ -1198,7 +1240,7 @@ test "forwarded report", %{conn: conn} do
conn
|> assign(:valid_signature, true)
- |> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"")
+ |> put_req_header("signature", "keyId=\"#{actor.signing_key.key_id}\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{reported_user.nickname}/inbox", data)
|> json_response(200)
@@ -1215,7 +1257,6 @@ test "forwarded report", %{conn: conn} do
)
end
- @tag capture_log: true
test "forwarded report from mastodon", %{conn: conn} do
admin = insert(:user, is_admin: true)
actor = insert(:user, local: false)
@@ -1232,12 +1273,22 @@ test "forwarded report from mastodon", %{conn: conn} do
|> File.read!()
|> String.replace("{{DOMAIN}}", remote_domain)
- Tesla.Mock.mock(fn %{url: ^remote_actor} ->
- %Tesla.Env{
- status: 200,
- body: mock_json_body,
- headers: [{"content-type", "application/activity+json"}]
- }
+ key_url = "#{remote_actor}#main-key"
+
+ Tesla.Mock.mock(fn
+ %{url: ^remote_actor} ->
+ %Tesla.Env{
+ status: 200,
+ body: mock_json_body,
+ headers: [{"content-type", "application/activity+json"}]
+ }
+
+ %{url: ^key_url} ->
+ %Tesla.Env{
+ status: 200,
+ body: mock_json_body,
+ headers: [{"content-type", "application/activity+json"}]
+ }
end)
data = %{
@@ -1254,7 +1305,7 @@ test "forwarded report from mastodon", %{conn: conn} do
conn
|> assign(:valid_signature, true)
- |> put_req_header("signature", "keyId=\"#{remote_actor}/main-key\"")
+ |> put_req_header("signature", "keyId=\"#{remote_actor}#main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{reported_user.nickname}/inbox", data)
|> json_response(200)
diff --git a/test/pleroma/web/activity_pub/mrf/object_age_policy_test.exs b/test/pleroma/web/activity_pub/mrf/object_age_policy_test.exs
index 2f649a0a4..9b61d31f4 100644
--- a/test/pleroma/web/activity_pub/mrf/object_age_policy_test.exs
+++ b/test/pleroma/web/activity_pub/mrf/object_age_policy_test.exs
@@ -79,7 +79,7 @@ test "works with objects with empty to or cc fields" do
{:ok, data} = ObjectAgePolicy.filter(data)
- assert Visibility.get_visibility(%{data: data}) == "unlisted"
+ assert Visibility.get_visibility(%{data: data}) == "direct"
end
test "it delists an old post" do
diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs
index 87930b7b1..5896568b8 100644
--- a/test/pleroma/web/activity_pub/publisher_test.exs
+++ b/test/pleroma/web/activity_pub/publisher_test.exs
@@ -140,7 +140,9 @@ test "publish to url with with different ports" do
{:ok, %Tesla.Env{status: 200, body: "port 80"}}
end)
- actor = insert(:user)
+ actor =
+ insert(:user)
+ |> with_signing_key()
assert {:ok, %{body: "port 42"}} =
Publisher.publish_one(%{
@@ -165,7 +167,10 @@ test "publish to url with with different ports" do
Instances,
[:passthrough],
[] do
- actor = insert(:user)
+ actor =
+ insert(:user)
+ |> with_signing_key()
+
inbox = "http://200.site/users/nick1/inbox"
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
@@ -176,7 +181,10 @@ test "publish to url with with different ports" do
Instances,
[:passthrough],
[] do
- actor = insert(:user)
+ actor =
+ insert(:user)
+ |> with_signing_key()
+
inbox = "http://200.site/users/nick1/inbox"
assert {:ok, _} =
@@ -195,7 +203,10 @@ test "publish to url with with different ports" do
Instances,
[:passthrough],
[] do
- actor = insert(:user)
+ actor =
+ insert(:user)
+ |> with_signing_key()
+
inbox = "http://200.site/users/nick1/inbox"
assert {:ok, _} =
@@ -214,7 +225,10 @@ test "publish to url with with different ports" do
Instances,
[:passthrough],
[] do
- actor = insert(:user)
+ actor =
+ insert(:user)
+ |> with_signing_key()
+
inbox = "http://404.site/users/nick1/inbox"
assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
@@ -226,7 +240,10 @@ test "publish to url with with different ports" do
Instances,
[:passthrough],
[] do
- actor = insert(:user)
+ actor =
+ insert(:user)
+ |> with_signing_key()
+
inbox = "http://connrefused.site/users/nick1/inbox"
assert capture_log(fn ->
@@ -241,7 +258,10 @@ test "publish to url with with different ports" do
Instances,
[:passthrough],
[] do
- actor = insert(:user)
+ actor =
+ insert(:user)
+ |> with_signing_key()
+
inbox = "http://200.site/users/nick1/inbox"
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
@@ -253,7 +273,10 @@ test "publish to url with with different ports" do
Instances,
[:passthrough],
[] do
- actor = insert(:user)
+ actor =
+ insert(:user)
+ |> with_signing_key()
+
inbox = "http://connrefused.site/users/nick1/inbox"
assert capture_log(fn ->
@@ -294,7 +317,9 @@ test "publish to url with with different ports" do
ap_enabled: true
})
- actor = insert(:user, follower_address: follower.ap_id)
+ actor =
+ insert(:user, follower_address: follower.ap_id)
+ |> with_signing_key()
{:ok, follower, actor} = Pleroma.User.follow(follower, actor)
{:ok, _another_follower, actor} = Pleroma.User.follow(another_follower, actor)
@@ -365,7 +390,9 @@ test "publish to url with with different ports" do
ap_enabled: true
})
- actor = insert(:user, follower_address: follower.ap_id)
+ actor =
+ insert(:user, follower_address: follower.ap_id)
+ |> with_signing_key()
{:ok, follower, actor} = Pleroma.User.follow(follower, actor)
actor = refresh_record(actor)
@@ -492,6 +519,9 @@ test "publish to url with with different ports" do
test "should not obliterate itself if the inbox URL is bad" do
url = "/inbox"
refute Pleroma.Web.ActivityPub.Publisher.should_federate?(url)
+
+ url = nil
+ refute Pleroma.Web.ActivityPub.Publisher.should_federate?(url)
end
end
end
diff --git a/test/pleroma/web/activity_pub/relay_test.exs b/test/pleroma/web/activity_pub/relay_test.exs
index 920aef9da..99cc2071e 100644
--- a/test/pleroma/web/activity_pub/relay_test.exs
+++ b/test/pleroma/web/activity_pub/relay_test.exs
@@ -114,7 +114,6 @@ test "returns error when activity not `Create` type" do
assert Relay.publish(activity) == {:error, "Not implemented"}
end
- @tag capture_log: true
test "returns error when activity not public" do
activity = insert(:direct_note_activity)
assert Relay.publish(activity) == {:error, false}
diff --git a/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs
index 524acddaf..88f42cada 100644
--- a/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs
@@ -83,7 +83,6 @@ test "it works for incoming announces, fetching the announced object" do
assert(Activity.get_create_by_object_ap_id(data["object"]))
end
- @tag capture_log: true
test "it works for incoming announces with an existing activity" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "hey"})
@@ -136,7 +135,6 @@ test "it works for incoming announces with an inlined activity" do
assert object.data["content"] == "this is a private toot"
end
- @tag capture_log: true
test "it rejects incoming announces with an inlined activity from another origin" do
Tesla.Mock.mock(fn
%{method: :get} -> %Tesla.Env{status: 404, body: ""}
diff --git a/test/pleroma/web/activity_pub/transmogrifier/delete_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/delete_handling_test.exs
index b7160bf58..e5d6f940a 100644
--- a/test/pleroma/web/activity_pub/transmogrifier/delete_handling_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier/delete_handling_test.exs
@@ -86,7 +86,6 @@ test "it fails for incoming deletes with spoofed origin" do
assert match?({:error, _}, Transmogrifier.handle_incoming(data))
end
- @tag capture_log: true
test "it works for incoming user deletes" do
%{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
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 16ee31483..234a48990 100644
--- a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
@@ -57,7 +57,6 @@ test "it ignores an incoming notice if we already have it" do
assert activity == returned_activity
end
- @tag capture_log: true
test "it fetches reply-to activities if we don't have them" do
data =
File.read!("test/fixtures/mastodon-post-activity.json")
@@ -537,7 +536,6 @@ test "returns object with inReplyTo when denied incoming reply", %{data: data} d
assert modified_object["inReplyTo"] == []
end
- @tag capture_log: true
test "returns modified object when allowed incoming reply", %{data: data} do
object_with_reply =
Map.put(
@@ -700,7 +698,6 @@ test "take_emoji_tags/1" do
assert Transmogrifier.take_emoji_tags(user) == [
%{
"icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"},
- "id" => "https://example.org/firefox.png",
"name" => ":firefox:",
"type" => "Emoji",
"updated" => "1970-01-01T00:00:00Z"
diff --git a/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs
index b1a064772..35a5fe03d 100644
--- a/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs
@@ -119,8 +119,8 @@ test "it works with custom profile fields" do
user = User.get_cached_by_ap_id(user.ap_id)
assert user.fields == [
- %{"name" => "foo", "value" => "updated"},
- %{"name" => "foo1", "value" => "updated"}
+ %{"name" => "foo", "value" => "bar"},
+ %{"name" => "foo11", "value" => "bar11"}
]
update_data =
diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs
index dd7977593..1be69317c 100644
--- a/test/pleroma/web/activity_pub/transmogrifier_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs
@@ -561,7 +561,6 @@ test "returns nil when cannot normalize object" do
end) =~ ":valid_uri_scheme"
end
- @tag capture_log: true
test "returns {:ok, %Object{}} for success case" do
assert {:ok, %Object{}} =
Transmogrifier.get_obj_helper(
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 abe91cdea..4283fb0c8 100644
--- a/test/pleroma/web/activity_pub/views/user_view_test.exs
+++ b/test/pleroma/web/activity_pub/views/user_view_test.exs
@@ -11,7 +11,9 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
alias Pleroma.Web.CommonAPI
test "Renders a user, including the public key" do
- user = insert(:user)
+ user =
+ insert(:user)
+ |> with_signing_key()
result = UserView.render("user.json", %{user: user})
@@ -37,13 +39,14 @@ test "Renders profile fields" do
end
test "Renders with emoji tags" do
- user = insert(:user, emoji: %{"bib" => "/test"})
+ user =
+ insert(:user, emoji: %{"bib" => "/test"})
+ |> with_signing_key()
assert %{
"tag" => [
%{
"icon" => %{"type" => "Image", "url" => "/test"},
- "id" => "/test",
"name" => ":bib:",
"type" => "Emoji",
"updated" => "1970-01-01T00:00:00Z"
@@ -74,13 +77,18 @@ test "Does not add an avatar image if the user hasn't set one" do
end
test "renders an invisible user with the invisible property set to true" do
- user = insert(:user, invisible: true)
+ user =
+ insert(:user, invisible: true)
+ |> with_signing_key()
assert %{"invisible" => true} = UserView.render("service.json", %{user: user})
end
test "service has a few essential fields" do
- user = insert(:user)
+ user =
+ insert(:user)
+ |> with_signing_key()
+
result = UserView.render("service.json", %{user: user})
assert result["id"]
assert result["type"] == "Application"
@@ -120,7 +128,9 @@ test "remote users have an empty endpoints structure" do
end
test "instance users do not expose oAuth endpoints" do
- user = insert(:user, nickname: nil, local: true)
+ user =
+ insert(:user, nickname: nil, local: true)
+ |> with_signing_key()
result = UserView.render("user.json", %{user: user})
diff --git a/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs
index 6a0d25058..64364cfb3 100644
--- a/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs
@@ -79,7 +79,6 @@ test "search", %{conn: conn} do
assert status["id"] == to_string(activity.id)
end
- @tag capture_log: true
test "constructs hashtags from search query", %{conn: conn} do
results =
conn
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 3e7b6730c..ccaf17fe4 100644
--- a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs
@@ -152,7 +152,6 @@ test "filtering", %{conn: conn, user: user} do
end
describe "public" do
- @tag capture_log: true
test "the public timeline", %{conn: conn} do
user = insert(:user)
@@ -810,7 +809,6 @@ test "filtering", %{user: user, conn: conn} do
describe "hashtag" do
setup do: oauth_access(["n/a"])
- @tag capture_log: true
test "hashtag timeline", %{conn: conn} do
following = insert(:user)
diff --git a/test/pleroma/web/mastodon_api/views/poll_view_test.exs b/test/pleroma/web/mastodon_api/views/poll_view_test.exs
index 224b26cb9..91d95f229 100644
--- a/test/pleroma/web/mastodon_api/views/poll_view_test.exs
+++ b/test/pleroma/web/mastodon_api/views/poll_view_test.exs
@@ -43,7 +43,7 @@ test "renders a poll" do
%{title: "why are you even asking?", votes_count: 0}
],
votes_count: 0,
- voters_count: 0
+ voters_count: nil
}
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 6421df132..6315a4806 100644
--- a/test/pleroma/web/mastodon_api/views/status_view_test.exs
+++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs
@@ -588,6 +588,7 @@ test "attachments" do
end
test "put the url advertised in the Activity in to the url attribute" do
+ Pleroma.Config.put([:instance, :limit_to_local_content], false)
id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
[activity] = Activity.search(nil, id)
diff --git a/test/pleroma/web/media_proxy_test.exs b/test/pleroma/web/media_proxy_test.exs
index bd5efe4c9..1a6e9a521 100644
--- a/test/pleroma/web/media_proxy_test.exs
+++ b/test/pleroma/web/media_proxy_test.exs
@@ -37,6 +37,10 @@ test "ignores local url" do
assert MediaProxy.url(local_root) == local_root
end
+ test "ignores data url" do
+ assert MediaProxy.url("data:image/png;base64,") == "data:image/png;base64,"
+ end
+
test "encodes and decodes URL" do
url = "https://pleroma.soykaf.com/static/logo.png"
encoded = MediaProxy.url(url)
diff --git a/test/pleroma/web/plugs/http_signature_plug_test.exs b/test/pleroma/web/plugs/http_signature_plug_test.exs
index 0a602424d..5236b519e 100644
--- a/test/pleroma/web/plugs/http_signature_plug_test.exs
+++ b/test/pleroma/web/plugs/http_signature_plug_test.exs
@@ -14,6 +14,15 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
import Phoenix.Controller, only: [put_format: 2]
import Mock
+ setup do
+ user =
+ :user
+ |> insert(%{ap_id: "http://mastodon.example.org/users/admin"})
+ |> with_signing_key(%{key_id: "http://mastodon.example.org/users/admin#main-key"})
+
+ {:ok, %{user: user}}
+ end
+
setup_with_mocks([
{HTTPSignatures, [],
[
@@ -46,15 +55,15 @@ defp submit_to_plug(host, method, path) do
|> HTTPSignaturePlug.call(%{})
end
- test "it call HTTPSignatures to check validity if the actor signed it" do
- params = %{"actor" => "http://mastodon.example.org/users/admin"}
+ test "it call HTTPSignatures to check validity if the actor signed it", %{user: user} do
+ params = %{"actor" => user.ap_id}
conn = build_conn(:get, "/doesntmattter", params)
conn =
conn
|> put_req_header(
"signature",
- "keyId=\"http://mastodon.example.org/users/admin#main-key"
+ "keyId=\"#{user.signing_key.key_id}\""
)
|> put_format("activity+json")
|> HTTPSignaturePlug.call(%{})
diff --git a/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs b/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs
index 086a27885..5789cd756 100644
--- a/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs
+++ b/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs
@@ -8,52 +8,63 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlugTest do
import Tesla.Mock
import Plug.Conn
+ import Pleroma.Factory
import Pleroma.Tests.Helpers, only: [clear_config: 2]
setup do
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
- :ok
+
+ user =
+ insert(:user)
+ |> with_signing_key()
+
+ {:ok, %{user: user}}
end
- defp set_signature(conn, key_id) do
+ defp set_signature(conn, ap_id) do
conn
- |> put_req_header("signature", "keyId=\"#{key_id}\"")
+ |> put_req_header("signature", "keyId=\"#{ap_id}#main-key\"")
|> assign(:valid_signature, true)
end
- test "it successfully maps a valid identity with a valid signature" do
+ test "it successfully maps a valid identity with a valid signature", %{user: user} do
conn =
build_conn(:get, "/doesntmattter")
- |> set_signature("http://mastodon.example.org/users/admin")
+ |> set_signature(user.ap_id)
|> MappedSignatureToIdentityPlug.call(%{})
refute is_nil(conn.assigns.user)
end
- test "it successfully maps a valid identity with a valid signature with payload" do
+ test "it successfully maps a valid identity with a valid signature with payload", %{user: user} do
conn =
- build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
- |> set_signature("http://mastodon.example.org/users/admin")
+ build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
+ |> set_signature(user.ap_id)
|> MappedSignatureToIdentityPlug.call(%{})
refute is_nil(conn.assigns.user)
end
- test "it considers a mapped identity to be invalid when it mismatches a payload" do
+ test "it considers a mapped identity to be invalid when it mismatches a payload", %{user: user} do
conn =
- build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
+ build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
|> set_signature("https://niu.moe/users/rye")
|> MappedSignatureToIdentityPlug.call(%{})
assert %{valid_signature: false} == conn.assigns
end
- test "it considers a mapped identity to be invalid when the associated instance is blocked" do
+ test "it considers a mapped identity to be invalid when the associated instance is blocked", %{
+ user: user
+ } do
clear_config([:activitypub, :authorized_fetch_mode], true)
+ # extract domain from user.ap_id
+ url = URI.parse(user.ap_id)
+
clear_config([:mrf_simple, :reject], [
- {"mastodon.example.org", "anime is banned"}
+ {url.host, "anime is banned"}
])
on_exit(fn ->
@@ -62,18 +73,21 @@ test "it considers a mapped identity to be invalid when the associated instance
end)
conn =
- build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
- |> set_signature("http://mastodon.example.org/users/admin")
+ build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
+ |> set_signature(user.ap_id)
|> MappedSignatureToIdentityPlug.call(%{})
assert %{valid_signature: false} == conn.assigns
end
- test "allowlist federation: it considers a mapped identity to be valid when the associated instance is allowed" do
+ test "allowlist federation: it considers a mapped identity to be valid when the associated instance is allowed",
+ %{user: user} do
clear_config([:activitypub, :authorized_fetch_mode], true)
+ url = URI.parse(user.ap_id)
+
clear_config([:mrf_simple, :accept], [
- {"mastodon.example.org", "anime is allowed"}
+ {url.host, "anime is allowed"}
])
on_exit(fn ->
@@ -82,15 +96,16 @@ test "allowlist federation: it considers a mapped identity to be valid when the
end)
conn =
- build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
- |> set_signature("http://mastodon.example.org/users/admin")
+ build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
+ |> set_signature(user.ap_id)
|> MappedSignatureToIdentityPlug.call(%{})
assert conn.assigns[:valid_signature]
refute is_nil(conn.assigns.user)
end
- test "allowlist federation: it considers a mapped identity to be invalid when the associated instance is not allowed" do
+ test "allowlist federation: it considers a mapped identity to be invalid when the associated instance is not allowed",
+ %{user: user} do
clear_config([:activitypub, :authorized_fetch_mode], true)
clear_config([:mrf_simple, :accept], [
@@ -103,8 +118,8 @@ test "allowlist federation: it considers a mapped identity to be invalid when th
end)
conn =
- build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
- |> set_signature("http://mastodon.example.org/users/admin")
+ build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
+ |> set_signature(user.ap_id)
|> MappedSignatureToIdentityPlug.call(%{})
assert %{valid_signature: false} == conn.assigns
diff --git a/test/pleroma/web/push/impl_test.exs b/test/pleroma/web/push/impl_test.exs
index a40c4ff13..f9936095f 100644
--- a/test/pleroma/web/push/impl_test.exs
+++ b/test/pleroma/web/push/impl_test.exs
@@ -67,7 +67,6 @@ test "performs sending notifications" do
assert Impl.perform(notif) == {:ok, [:ok, :ok]}
end
- @tag capture_log: true
test "returns error if notif does not match " do
assert Impl.perform(%{}) == {:error, :unknown_type}
end
@@ -76,7 +75,6 @@ test "successful message sending" do
assert Impl.push_message(@message, @sub, @api_key, %Subscription{}) == :ok
end
- @tag capture_log: true
test "fail message sending" do
assert Impl.push_message(
@message,
diff --git a/test/pleroma/web/web_finger_test.exs b/test/pleroma/web/web_finger_test.exs
index 2af084090..4fc2a8ffa 100644
--- a/test/pleroma/web/web_finger_test.exs
+++ b/test/pleroma/web/web_finger_test.exs
@@ -190,7 +190,6 @@ test "prevents spoofing" do
end
end
- @tag capture_log: true
test "prevents forgeries" do
Tesla.Mock.mock(fn
%{url: "https://bad.com/.well-known/webfinger?resource=acct:meanie@bad.com"} ->
diff --git a/test/pleroma/workers/attachments_cleanup_worker_test.exs b/test/pleroma/workers/attachments_cleanup_worker_test.exs
new file mode 100644
index 000000000..d180763fb
--- /dev/null
+++ b/test/pleroma/workers/attachments_cleanup_worker_test.exs
@@ -0,0 +1,86 @@
+# Akkoma: Magically expressive social media
+# Copyright © 2024 Akkoma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.AttachmentsCleanupWorkerTest do
+ use Pleroma.DataCase, async: false
+ use Oban.Testing, repo: Pleroma.Repo
+
+ import Pleroma.Factory
+
+ alias Pleroma.Object
+ alias Pleroma.Workers.AttachmentsCleanupWorker
+ alias Pleroma.Tests.ObanHelpers
+
+ setup do
+ clear_config([:instance, :cleanup_attachments], true)
+
+ file = %Plug.Upload{
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ user = insert(:user)
+
+ {:ok, %Pleroma.Object{} = attachment} =
+ Pleroma.Web.ActivityPub.ActivityPub.upload(file, actor: user.ap_id)
+
+ {:ok, attachment: attachment, user: user}
+ end
+
+ test "does not enqueue remote post" do
+ remote_data = %{
+ "id" => "https://remote.example/obj/123",
+ "actor" => "https://remote.example/user/1",
+ "content" => "content",
+ "attachment" => [
+ %{
+ "type" => "Document",
+ "mediaType" => "image/png",
+ "name" => "marvellous image",
+ "url" => "https://remote.example/files/image.png"
+ }
+ ]
+ }
+
+ assert {:ok, :skip} = AttachmentsCleanupWorker.enqueue_if_needed(remote_data)
+ end
+
+ test "enqueues local post", %{attachment: attachment, user: user} do
+ local_url = Pleroma.Web.Endpoint.url()
+
+ local_data = %{
+ "id" => local_url <> "/obj/123",
+ "actor" => user.ap_id,
+ "content" => "content",
+ "attachment" => [attachment.data]
+ }
+
+ assert {:ok, %Oban.Job{}} = AttachmentsCleanupWorker.enqueue_if_needed(local_data)
+ end
+
+ test "doesn't delete immediately", %{attachment: attachment, user: user} do
+ delay = 6000
+ clear_config([:instance, :cleanup_attachments_delay], delay)
+
+ note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}})
+
+ uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads])
+ %{"url" => [%{"href" => href}]} = attachment.data
+ path = "#{uploads_dir}/#{Path.basename(href)}"
+
+ assert File.exists?(path)
+
+ Object.delete(note)
+ Process.sleep(2000)
+
+ assert File.exists?(path)
+
+ ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker))
+
+ assert Object.get_by_id(note.id).data["deleted"]
+ assert Object.get_by_id(attachment.id) == nil
+ refute File.exists?(path)
+ end
+end
diff --git a/test/support/factory.ex b/test/support/factory.ex
index 2a73a4ae6..54e5f91b7 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -47,7 +47,6 @@ def instance_factory(attrs \\ %{}) do
end
def user_factory(attrs \\ %{}) do
- pem = Enum.random(@rsa_keys)
# Argon2.hash_pwd_salt("test")
# it really eats CPU time, so we use a precomputed hash
password_hash =
@@ -64,8 +63,7 @@ def user_factory(attrs \\ %{}) do
last_refreshed_at: NaiveDateTime.utc_now(),
notification_settings: %Pleroma.User.NotificationSetting{},
multi_factor_authentication_settings: %Pleroma.MFA.Settings{},
- ap_enabled: true,
- keys: pem
+ ap_enabled: true
}
urls =
@@ -97,6 +95,28 @@ def user_factory(attrs \\ %{}) do
|> merge_attributes(attrs)
end
+ def with_signing_key(%User{} = user, attrs \\ %{}) do
+ signing_key =
+ build(:signing_key, %{user: user, key_id: "#{user.ap_id}#main-key"})
+ |> merge_attributes(attrs)
+
+ insert(signing_key)
+ %{user | signing_key: signing_key}
+ end
+
+ def signing_key_factory(attrs \\ %{}) do
+ pem = Enum.random(@rsa_keys)
+ user = attrs[:user] || insert(:user)
+ {:ok, public_key} = Pleroma.User.SigningKey.private_pem_to_public_pem(pem)
+
+ %Pleroma.User.SigningKey{
+ user_id: user.id,
+ public_key: attrs[:public_key] || public_key,
+ private_key: attrs[:private_key] || pem,
+ key_id: attrs[:key_id]
+ }
+ end
+
def user_relationship_factory(attrs \\ %{}) do
source = attrs[:source] || insert(:user)
target = attrs[:target] || insert(:user)
diff --git a/test/support/helpers.ex b/test/support/helpers.ex
index 2dfff70a2..f0beae8ec 100644
--- a/test/support/helpers.ex
+++ b/test/support/helpers.ex
@@ -66,6 +66,8 @@ defmacro __using__(_opts) do
clear_config: 2
]
+ import Pleroma.Test.MatchingHelpers
+
def time_travel(entity, seconds) do
new_time = NaiveDateTime.add(entity.inserted_at, seconds)
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
index 6a01393e3..d14434333 100644
--- a/test/support/http_request_mock.ex
+++ b/test/support/http_request_mock.ex
@@ -263,7 +263,12 @@ def get("https://n1u.moe/users/rye", _, _, @activitypub_accept_headers) do
{:ok,
%Tesla.Env{
status: 200,
- body: File.read!("test/fixtures/tesla_mock/rye.json"),
+ body:
+ File.read!("test/fixtures/tesla_mock/rye.json")
+ |> Jason.decode!()
+ |> Map.put("name", "evil rye")
+ |> Map.put("bio", "boooo!")
+ |> Jason.encode!(),
headers: activitypub_object_headers()
}}
end
@@ -419,6 +424,15 @@ def get("http://mastodon.example.org/users/admin", _, _, _) do
}}
end
+ def get("http://mastodon.example.org/users/admin/main-key", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/admin@mastdon.example.org.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
def get(
"http://mastodon.example.org/users/admin/statuses/99512778738411822/replies?min_id=99512778738411824&page=true",
_,
@@ -953,6 +967,15 @@ def get("https://mastodon.social/users/lambadalambda", _, _, _) do
}}
end
+ def get("https://mastodon.social/users/lambadalambda#main-key", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/lambadalambda.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
def get("https://mastodon.social/users/lambadalambda/collections/featured", _, _, _) do
{:ok,
%Tesla.Env{
@@ -1398,6 +1421,15 @@ def get("https://relay.mastodon.host/actor", _, _, _) do
}}
end
+ def get("https://relay.mastodon.host/actor#main-key", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/relay/relay.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
def get("http://localhost:4001/", _, "", [{"accept", "text/html"}]) do
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/7369654.html")}}
end
diff --git a/test/support/matching_helpers.ex b/test/support/matching_helpers.ex
new file mode 100644
index 000000000..c8411b907
--- /dev/null
+++ b/test/support/matching_helpers.ex
@@ -0,0 +1,10 @@
+defmodule Pleroma.Test.MatchingHelpers do
+ import ExUnit.Assertions
+
+ @assoc_fields [
+ :signing_key
+ ]
+ def assert_user_match(actor1, actor2) do
+ assert Ecto.reset_fields(actor1, @assoc_fields) == Ecto.reset_fields(actor2, @assoc_fields)
+ end
+end
diff --git a/test/test_helper.exs b/test/test_helper.exs
index dafa45099..6dcb87ff6 100644
--- a/test/test_helper.exs
+++ b/test/test_helper.exs
@@ -3,7 +3,11 @@
# SPDX-License-Identifier: AGPL-3.0-only
os_exclude = if :os.type() == {:unix, :darwin}, do: [skip_on_mac: true], else: []
-ExUnit.start(exclude: [:federated, :erratic] ++ os_exclude)
+
+ExUnit.start(
+ capture_log: true,
+ exclude: [:federated, :erratic] ++ os_exclude
+)
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)