Compare commits

...

52 commits

Author SHA1 Message Date
ad92e504d7 Update mix.exs 2025-01-06 12:01:53 +00:00
1ffbaa2924 don't allow a nil inbox to obliterate federation 2025-01-06 11:43:41 +00:00
2e049037de force CI build 2025-01-05 17:35:01 +00:00
a2256b3f9e add unreleased section 2025-01-05 17:30:32 +00:00
054396a99e ARM64 2025-01-05 17:23:52 +00:00
a846e60d71 and the docs one 2025-01-05 16:39:30 +00:00
1be18a9f4c update deprecated CI config 2025-01-05 16:37:54 +00:00
55fc410f80 bump version number 2025-01-05 16:25:42 +00:00
ae40ccb8ca add changelog entry 2025-01-05 16:23:09 +00:00
7ed52838f4 fix test 2025-01-05 16:22:38 +00:00
fe23660a2d Merge pull request 'openbsd: update service file' (#866) from Oneric/akkoma:openbsd-rcd-file into develop
Reviewed-on: AkkomaGang/akkoma#866
2025-01-05 15:44:47 +00:00
177d420ae7 Merge branch 'develop' of akkoma.dev:AkkomaGang/akkoma into develop 2025-01-05 15:44:15 +00:00
b0f2ed6eaa Merge remote-tracking branch 'oneric/changelog-3.13.4' into develop 2025-01-05 15:44:06 +00:00
39cef8b8d2 Merge pull request 'Set customize_hostname_check for Swoosh.Adapters.SMTP' (#861) from norm/akkoma:smtp-defaults-fix into develop
Reviewed-on: AkkomaGang/akkoma#861
2025-01-05 15:43:16 +00:00
3ba743d635 Merge pull request 'Update hashtag prune to account for followed hashtags' (#844) from norm/akkoma:hashtag-prune into develop
Reviewed-on: AkkomaGang/akkoma#844
2025-01-05 15:41:23 +00:00
bd02c3d7de Merge remote-tracking branch 'oneric/custom-source' into develop 2025-01-05 15:40:35 +00:00
8de373fa24 Merge pull request 'Fix various attachment cleanup issues' (#789) from Oneric/akkoma:attachcleanup-overeager into develop
Reviewed-on: AkkomaGang/akkoma#789
2025-01-05 15:39:48 +00:00
d32c4e89bc Merge pull request 'FEP-dc88: Formatting Mathematics' (#642) from pounce/akkoma:formatting-mathematics into develop
Reviewed-on: AkkomaGang/akkoma#642
2025-01-05 15:39:31 +00:00
7c095a6b70 Merge pull request 'do not fetch if :limit_to_local_content is :all or :unauthenticated' (#582) from beerriot/akkoma:develop-no-fetch-with-local-limit into develop
Reviewed-on: AkkomaGang/akkoma#582
2025-01-05 15:39:13 +00:00
e0428a8f14 Merge branch 'develop' of akkoma.dev:AkkomaGang/akkoma into develop 2025-01-05 15:38:32 +00:00
e5619c0895 Merge remote-tracking branch 'norm/improve-asdf-install' into develop 2025-01-05 15:38:25 +00:00
833d7661d6 Merge pull request 'update nsfwCensorImage suggestion in config/description.exs' (#837) from norm/akkoma:update-nsfwCensorImage into develop
Reviewed-on: AkkomaGang/akkoma#837
2025-01-05 15:37:08 +00:00
eviloatmeal
d8c7ed70d0 openbsd: update service file
Changes suggested and tested by eviloatmeal

Fixes: AkkomaGang/akkoma#864
2025-01-03 21:22:38 +01:00
e8bf4422ff Delay attachment deletion
Otherwise attachments have a high chance to disappear with akkoma-fe’s
“delete & redraft” feature when cleanup is enabled in the backend. Since
we don't know whether a deletion was intended to be part of a redraft
process or even if whether the redraft was abandoned we still have to
delete attachments eventually.
A thirty minute delay should provide sufficient time for redrafting.

Fixes: AkkomaGang/akkoma#775
2025-01-03 20:49:11 +01:00
bcfbfbcff5 Don't try to cleanup remote attachments
The cleanup attachment worker was run for every deleted post,
even if it’s a remote post whose attachments we don't even store.
This was especially bad due to attachment cleanup involving a
particularly heavy query wasting a bunch of database perf for nil.

This was uncovered by comparing statistics from
AkkomaGang/akkoma#784 and
AkkomaGang/akkoma#765 (comment)
2025-01-03 20:48:46 +01:00
f2e45d4d4b Teach admin-fe about custom source URLs
Matching  AkkomaGang/akkoma-fe#421
2025-01-03 20:43:52 +01:00
7615a11a1e changelog: fix shuffled and add missing entries 2025-01-03 20:33:41 +01:00
e3c8c4f24f Merge pull request 'mrf/object_age: fix handling of non-public objects' (#851) from Oneric/akkoma:mrf-fix-oage into develop
Reviewed-on: AkkomaGang/akkoma#851
2025-01-03 15:26:11 +00:00
67cdc38296 Merge pull request 'Only proxy HTTP and HTTP urls via Media Proxy' (#860) from nopjmp/akkoma:media-proxy-only-http into develop
Reviewed-on: AkkomaGang/akkoma#860
2025-01-03 15:25:14 +00:00
89d209f486 Merge pull request 'Fix NodeInfo content-type' (#853) from Oneric/akkoma:nodeinfo-contenttype into develop
Reviewed-on: AkkomaGang/akkoma#853
2025-01-03 15:24:25 +00:00
087ada3b2e Merge pull request 'Update supported OTP version to 27 in docs' (#849) from norm/akkoma:docs/otp-27 into develop
Reviewed-on: AkkomaGang/akkoma#849
2025-01-03 15:23:59 +00:00
91bedcfa68 Merge pull request 'Completely omit id for anonymous objects' (#850) from Oneric/akkoma:ap-anonymous-errata into develop
Reviewed-on: AkkomaGang/akkoma#850
2025-01-03 15:23:03 +00:00
f19d5d1380 Set customize_hostname_check for Swoosh.Adapters.SMTP
This should hopefully fix issues with connecting to SMTP servers
with wildcard TLS certificates.

Taken from https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/ssl

Fixes AkkomaGang/akkoma#660
2024-12-18 14:37:27 -05:00
ff5d197341 Update CHANGELOG.md 2024-12-16 20:35:12 -06:00
7632765b43 Only proxy HTTP and HTTP urls via Media Proxy
We make an assumption that we are only proxying HTTP/HTTPS hosted
media through the media proxy endpoint.

Fixes: #859
2024-12-16 20:35:12 -06:00
294de939cb signing_key: refactor nested case into with statement
The error branches were already effectively identical before.
This change is purely cosmetic.
2024-12-08 20:43:57 +00:00
7583eceb38 Make SigningKey data migration future-proof
Bug originally discovered by tudbut
2024-12-08 20:43:10 +00:00
834edfcf96 add changelog 2024-11-26 09:50:04 +00:00
79b282dea6 bump version 2024-11-26 09:36:20 +00:00
d1d82782db add signing key index 2024-11-26 09:35:56 +00:00
Haelwenn (lanodan) Monnier
2b1a252cc7 User: truncate remote user fields instead of rejecting 2024-11-26 09:29:44 +00:00
f048e0cf1b Allow MathML core tags in sanitized content 2024-11-25 23:18:52 +00:00
416aebb76a Fix NodeInfo content-type
Fixes: AkkomaGang/akkoma#852
2024-11-19 19:25:31 +01:00
932810c35e mrf/object_age: fix handling of non-public objects
Current logic unconditionally adds public adressing to "cc"
and follower adressing to "to" after attempting to strip it
from the other one. This creates serious problems:

First the bug prompting this investigation and fix,
unconditional addition creates duplicates when adressing
URIs already were in their intended final field; e.g.
this is prominently the case for all "unlisted" posts.
Since List.delete only removes the first occurence,
this then broke follower-adress stripping later on
making the policy ineffective.

It’s also just not safe in general wrt to non-public adressing:
e.g. pre-existing duplicates didn’t get fully stripped,
bespoke adressing modes with only one of public addressing
or follower addressing are mangled — and most importantly:
any belatedly received DM or follower-only post
also got public adressing added!
Shockingly this last point was actually asserted as "correct" in tests;
it appears to be a mistake from mindless match adjustments
while fixing crashes on nil adressing in
10c792110e.

Clean up this sloppy logic up, making sure no more duplicates are
added by us, all instances of relevant adresses are purged and only
readded when they actually existed to begin with.
2024-11-17 00:44:51 +01:00
4c7ef1e027 Update supported OTP version to 27 in docs
The minor incompatibility should have been fixed with commit
bee10eab5e and PRs #839 and #841.
2024-11-09 18:24:53 -05:00
0f9c9aac38 Completely omit id for anonymous objects
Current AP spec demands anonymous objects to have an id value,
but explicitly set it to JSON null. Howeveras it turns out this is
incompatible with JSON-LD requiring `@id` to be a string and thus AP
spec is incompatible iwth the Ativity Streams spec it is based on.
This is an issue for (the few) AP implementers actually performing
JSON-LD processing, like IceShrimp.NET.
This was uncovered by IceShrimp.NET’s zotan due to our adoption of
anonymous objects for emoj in f101886709.

The issues is being discussed by W3C, and will most likely be resolved
via an errata redefining anonymous objects to completely omit the id
field just like transient objects already do. See:
https://github.com/w3c/activitypub/issues/476

Fixes: AkkomaGang/akkoma#848
2024-11-09 19:29:29 +01:00
6ed5be61ff docs: Note that Elixir 1.17 has been tested as working 2024-10-26 18:51:45 -04:00
88a8086ad3 Use LEFT JOIN instead of UNION for hashtag pruning 2024-10-25 12:26:14 -04:00
40da4e88ea Update hashtag prune to account for followed hashtags
Currently pruning hashtags with the prune_objects task only accounts
for whether that hashtag is associated with an object, but this may
lead to a foreign key constraint violation if that hashtag has no
objects but is followed by a local user.

This adds an additional check to see if that hashtag has any followers
before proceeding to delete it.
2024-10-25 11:55:37 -04:00
a2e397a79d Update asdf install docs in Debian install guide
Instead of trying to update the version of asdf being used, just point
users to the guide on their website.

Ideally we'd do this for Elixir and Erlang as well, but new versions of
those packages may sometimes have compatibility issues with Akkoma. For
now, update those to the latest OTP and Elixir versions known to be
comaptible with Akkoma.
2024-10-22 16:01:58 -04:00
e4332d06b5 update nsfwCensorImage suggestion in config/description.exs
Turns out this is also used to set the default values in adminfe.
However, this URL may break with newer Akkoma-FE versions.
Instead, set this to blank so that it falls back to the default
NSFW cover image set at build time on Akkoma-FE.
2024-10-20 00:59:52 -04:00
97037c0b53 do not fetch if limit_to_local_content is enabled
Prior to this change, anyone, authenticated or not, could submit a search
query for an activity by URL, and cause the fetcher to go fetch it. That
shouldn't happen if `limit_to_local_content` is set to `:all` or if it's
set to `:unauthenticated` and the query came from an unauthenticated
source.
2023-07-07 12:24:04 -05:00
37 changed files with 456 additions and 136 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,7 +4,34 @@ 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

View file

@ -255,6 +255,7 @@
external_user_synchronization: true,
extended_nickname_format: true,
cleanup_attachments: false,
cleanup_attachments_delay: 1800,
multi_factor_authentication: [
totp: [
# digits 6 or 8
@ -302,6 +303,7 @@
allow_headings: false,
allow_tables: false,
allow_fonts: false,
allow_math: true,
scrub_policy: [
Pleroma.HTML.Scrubber.Default,
Pleroma.HTML.Transform.MediaProxy

View file

@ -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"
}
]
},

View file

@ -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: `[]`)

View file

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

View file

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

View file

@ -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() {

View file

@ -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!()

View file

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

View file

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

View file

@ -95,21 +95,29 @@ defp query_with(q, :rum, search_query) do
)
end
def maybe_restrict_local(q, user) do
def should_restrict_local(user) do
limit = Pleroma.Config.get([:instance, :limit_to_local_content], :unauthenticated)
case {limit, user} do
{:all, _} -> restrict_local(q)
{:unauthenticated, %User{}} -> q
{:unauthenticated, _} -> restrict_local(q)
{false, _} -> q
{:all, _} -> true
{:unauthenticated, %User{}} -> false
{:unauthenticated, _} -> true
{false, _} -> false
end
end
def maybe_restrict_local(q, user) do
case should_restrict_local(user) do
true -> restrict_local(q)
false -> 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

View file

@ -443,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
@ -456,6 +457,7 @@ 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()

View file

@ -194,31 +194,24 @@ def get_or_fetch_by_key_id(key_id) do
"""
def fetch_remote_key(key_id) do
Logger.debug("Fetching remote key: #{key_id}")
resp = Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(key_id)
case resp do
{:ok, _body} ->
case handle_signature_response(resp) do
{:ok, ap_id, public_key_pem} ->
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
}
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)
e ->
Logger.debug("Failed to fetch remote key: #{inspect(e)}")
{:error, "Could not fetch key"}
end
_ ->
Logger.debug("Failed to fetch remote key: #{inspect(resp)}")
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

View file

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

View file

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

View file

@ -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" => nil
"updated" => "1970-01-01T00:00:00Z"
}
end

View file

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

View file

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

View file

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

View file

@ -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(),

View file

@ -8,13 +8,14 @@ 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...
query =
from(u in User)
|> where(local: true)
Repo.stream(query, timeout: :infinity)
# 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: user_id, keys: private_key, local: true, ap_id: ap_id} ->
{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

View file

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

View file

@ -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, [])

View file

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

View file

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

View file

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

View file

@ -698,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" => nil,
"name" => ":firefox:",
"type" => "Emoji",
"updated" => "1970-01-01T00:00:00Z"

View file

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

View file

@ -47,7 +47,6 @@ test "Renders with emoji tags" do
"tag" => [
%{
"icon" => %{"type" => "Image", "url" => "/test"},
"id" => nil,
"name" => ":bib:",
"type" => "Emoji",
"updated" => "1970-01-01T00:00:00Z"

View file

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

View file

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

View file

@ -0,0 +1,86 @@
# Akkoma: Magically expressive social media
# Copyright © 2024 Akkoma Authors <https://akkoma.dev/>
# 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