Merge remote-tracking branch 'upstream/develop' into bnakkoma

This commit is contained in:
itepechi 2024-03-04 07:14:18 +09:00
commit 318f18ce1a
Signed by: itepechi
GPG Key ID: D948CE1B5ACD0B25
50 changed files with 1628 additions and 594 deletions

View File

@ -4,21 +4,26 @@ 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
## 2024.02
## Added
- Full compatibility with Erlang OTP26
- handling of GET /api/v1/preferences
- Akkoma API is now documented
- ability to auto-approve follow requests from users you are already following
- The SimplePolicy MRF can now strip user backgrounds from selected remote hosts
## Changed
- OTP builds are now built on erlang OTP26
- The base Phoenix framework is now updated to 1.7
- An `outbox` field has been added to actor profiles to comply with AP spec
- User profile backgrounds do now federate with other Akkoma instances and Sharkey
## Fixed
- Documentation issue in which a non-existing nginx file was referenced
- Issue where a bad inbox URL could break federation
- Issue where hashtag rel values would be scrubbed
- Issue where short domains listed in `transparency_obfuscate_domains` were not actually obfuscated
## 2023.08

View File

@ -291,7 +291,6 @@ config :pleroma, :frontend_configurations,
alwaysShowSubjectInput: true,
background: "",
collapseMessageWithSubject: false,
disableChat: false,
greentext: false,
hideFilteredStatuses: false,
hideMutedPosts: false,
@ -399,6 +398,7 @@ config :pleroma, :mrf_simple,
accept: [],
avatar_removal: [],
banner_removal: [],
background_removal: [],
reject_deletes: [],
handle_threads: true
@ -427,8 +427,6 @@ config :pleroma, :mrf_object_age,
threshold: 604_800,
actions: [:delist, :strip_followers]
config :pleroma, :mrf_follow_bot, follower_nickname: nil
config :pleroma, :mrf_reject_newly_created_account_notes, age: 86_400
config :pleroma, :rich_media,
@ -474,10 +472,6 @@ config :pleroma, :media_preview_proxy,
image_quality: 85,
min_content_length: 100 * 1024
config :pleroma, :shout,
enabled: true,
limit: 5_000
config :phoenix, :format_encoders, json: Jason, "activity+json": Jason
config :phoenix, :json_library, Jason

View File

@ -3,7 +3,7 @@
If you run akkoma, you may be inclined to collect metrics to ensure your instance is running smoothly,
and that there's nothing quietly failing in the background.
To facilitate this, akkoma exposes prometheus metrics to be scraped.
To facilitate this, akkoma exposes a dashboard and prometheus metrics to be scraped.
## Prometheus
@ -31,3 +31,15 @@ Once you have your token of the form `Bearer $ACCESS_TOKEN`, you can use that in
- targets:
- example.com
```
## Dashboard
Administrators can access a live dashboard under `/phoenix/live_dashboard`
giving an overview of uptime, software versions, database stats and more.
The dashboard also includes a variation of the prometheus metrics, however
they do not exactly match due to respective limitations of the dashboard
and the prometheus exporter.
Even more important, the dashboard collects metrics locally in the browser
only while the page is open and cannot give a view on their past history.
For proper monitoring it is recommended to set up prometheus.

View File

@ -104,31 +104,60 @@ To add configuration to your config file, you can copy it from the base config.
## Message rewrite facility
### :mrf
* `policies`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
* `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`: Doesnt modify activities (default).
* `Pleroma.Web.ActivityPub.MRF.DropPolicy`: Drops all activities. It generally doesnt makes sense to use in production.
* `Pleroma.Web.ActivityPub.MRF.SimplePolicy`: Restrict the visibility of activities from certains instances (See [`:mrf_simple`](#mrf_simple)).
* `Pleroma.Web.ActivityPub.MRF.TagPolicy`: Applies policies to individual users based on tags, which can be set using pleroma-fe/admin-fe/any other app that supports Pleroma Admin API. For example it allows marking posts from individual users nsfw (sensitive).
* `Pleroma.Web.ActivityPub.MRF.SubchainPolicy`: Selectively runs other MRF policies when messages match (See [`:mrf_subchain`](#mrf_subchain)).
* `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See [`:mrf_rejectnonpublic`](#mrf_rejectnonpublic)).
* `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:.
* `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.
* `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed.
* `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (See [`:mrf_mention`](#mrf_mention)).
* `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (See [`:mrf_vocabulary`](#mrf_vocabulary)).
* `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)).
* `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.Workers.PurgeExpiredActivity` to be enabled for processing the scheduled delections.
* `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines.
* `Pleroma.Web.ActivityPub.MRF.FollowBotPolicy`: Automatically follows newly discovered users from the specified bot account. Local accounts, locked accounts, and users with "#nobot" in their bio are respected and excluded from being followed.
* `Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy`: Drops follow requests from followbots. Users can still allow bots to follow them by first following the bot.
* `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)).
* `Pleroma.Web.ActivityPub.MRF.NormalizeMarkup`: Pass inbound HTML through a scrubber to make sure it doesn't have anything unusual in it. On by default, cannot be turned off.
* `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Append a link to a post that quotes another post with the link to the quoted post, to ensure that software that does not understand quotes can have full context. On by default, cannot be turned off.
* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
* `transparency_obfuscate_domains`: Show domains with `*` in the middle, to censor them if needed. For example, `ridingho.me` will show as `rid*****.me`
* `policies`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
* `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`: Doesnt modify activities (default).
* `Pleroma.Web.ActivityPub.MRF.DropPolicy`: Drops all activities. It generally doesnt makes sense to use in production.
* `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.Workers.PurgeExpiredActivity` to be enabled for processing the scheduled delections.
(See [`:mrf_activity_expiration`](#mrf_activity_expiration))
* `Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy`: Drops follow requests from followbots. Users can still allow bots to follow them by first following the bot.
* `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.
* `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:.
* `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines.
* `Pleroma.Web.ActivityPub.MRF.HellthreadPolicy`: Blocks messages with too many mentions.
(See [`mrf_hellthread`](#mrf_hellthread))
* `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)).
* `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed.
* `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (See [`:mrf_mention`](#mrf_mention)).
* `Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy`: Drops local activities which have no actual content.
(e.g. no attachments and only consists of mentions)
* `Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy`: Strips content placeholders from posts
(such as the dot from mastodon)
* `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)).
* `Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy`: Rejects posts of users the server only recently learned about for a while. Great to block spam accounts. (See [`:mrf_reject_newly_created_account_notes`](#mrf_reject_newly_created_account_notes))
* `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See [`:mrf_rejectnonpublic`](#mrf_rejectnonpublic)).
* `Pleroma.Web.ActivityPub.MRF.SimplePolicy`: Restrict the visibility of activities from certains instances (See [`:mrf_simple`](#mrf_simple)).
* `Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy`: Steals all eligible emoji encountered in posts from remote instances
(See [`:mrf_steal_emoji`](#mrf_steal_emoji))
* `Pleroma.Web.ActivityPub.MRF.SubchainPolicy`: Selectively runs other MRF policies when messages match (See [`:mrf_subchain`](#mrf_subchain)).
* `Pleroma.Web.ActivityPub.MRF.TagPolicy`: Applies policies to individual users based on tags, which can be set using pleroma-fe/admin-fe/any other app that supports Pleroma Admin API. For example it allows marking posts from individual users nsfw (sensitive).
* `Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy`: Drops all posts except from users specified in a list.
(See [`:mrf_user_allowlist`](#mrf_user_allowlist))
* `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (See [`:mrf_vocabulary`](#mrf_vocabulary)).
Additionally the following MRFs will *always* be aplied and cannot be disabled:
* `Pleroma.Web.ActivityPub.MRF.DirectMessageDisabledPolicy`: Strips users limiting who can send them DMs from the recipients of non-eligible DMs
* `Pleroma.Web.ActivityPub.MRF.HashtagPolicy`: Depending on a posts hashtags it can be rejected, get its sensitive flags force-enabled or removed from the global timeline
(See [`:mrf_hashtag`](#mrf_hashtag))
* `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Append a link to a post that quotes another post with the link to the quoted post, to ensure that software that does not understand quotes can have full context.
(See [`:mrf_inline_quote`](#mrf_inline_quote))
* `Pleroma.Web.ActivityPub.MRF.NormalizeMarkup`: Pass inbound HTML through a scrubber to make sure it doesn't have anything unusual in it.
(See [`:mrf_normalize_markup`](#mrf_normalize_markup))
## Federation
### :activitypub
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
* `outgoing_blocks`: Whether to federate blocks to other instances
* `blockers_visible`: Whether a user can see the posts of users who blocked them
* `deny_follow_blocked`: Whether to disallow following an account that has blocked the user in question
* `sign_object_fetches`: Sign object fetches with HTTP signatures
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches
* `max_collection_objects`: The maximum number of objects to fetch from a remote AP collection.
### MRF policies
!!! note
@ -144,6 +173,7 @@ To add configuration to your config file, you can copy it from the base config.
* `report_removal`: List of instances to reject reports from and the reason for doing so.
* `avatar_removal`: List of instances to strip avatars from and the reason for doing so.
* `banner_removal`: List of instances to strip banners from and the reason for doing so.
* `background_removal`: List of instances to strip user backgrounds from and the reason for doing so.
* `reject_deletes`: List of instances to reject deletions from and the reason for doing so.
#### :mrf_subchain
@ -222,14 +252,24 @@ Notes:
- The hashtags in the configuration do not have a leading `#`.
- This MRF Policy is always enabled, if you want to disable it you have to set empty lists
### :activitypub
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
* `outgoing_blocks`: Whether to federate blocks to other instances
* `blockers_visible`: Whether a user can see the posts of users who blocked them
* `deny_follow_blocked`: Whether to disallow following an account that has blocked the user in question
* `sign_object_fetches`: Sign object fetches with HTTP signatures
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches
* `max_collection_objects`: The maximum number of objects to fetch from a remote AP collection.
#### :mrf_reject_newly_created_account_notes
After initially encountering an user, all their posts
will be rejected for the configured time (in seconds).
Only drops posts. Follows, reposts, etc. are not affected.
* `age`: Time below which to reject (in seconds)
An example: (86400 seconds = 24 hours)
```elixir
config :pleroma, :mrf_reject_newly_created_account_notes, age: 86400
```
#### :mrf_inline_quote
* `prefix`: what prefix to prepend to quoted URLs
#### :mrf_normalize_markup
* `scrub_policy`: the scrubbing module to use (by default a built-in HTML sanitiser)
## Pleroma.User
@ -958,6 +998,15 @@ config :ueberauth, Ueberauth,
]
```
You may also need to set up your frontend to use oauth logins. For example, for `akkoma-fe`:
```elixir
config :pleroma, :frontend_configurations,
pleroma_fe: %{
loginMethod: "token"
}
```
## Link parsing
### :uri_schemes

View File

@ -35,6 +35,7 @@ Once `SimplePolicy` is enabled, you can configure various groups in the `:mrf_si
* `media_removal`: Servers in this group will have media stripped from incoming messages.
* `avatar_removal`: Avatars from these servers will be stripped from incoming messages.
* `banner_removal`: Banner images from these servers will be stripped from incoming messages.
* `background_removal`: User background images from these servers will be stripped from incoming messages.
* `report_removal`: Servers in this group will have their reports (flags) rejected.
* `federated_timeline_removal`: Servers in this group will have their messages unlisted from the public timelines by flipping the `to` and `cc` fields.
* `reject_deletes`: Deletion requests will be rejected from these servers.
@ -61,6 +62,32 @@ config :pleroma, :mrf_simple,
The effects of MRF policies can be very drastic. It is important to use this functionality carefully. Always try to talk to an admin before writing an MRF policy concerning their instance.
## Hiding or Obfuscating Policies
You can opt out of publicly displaying all MRF policies or only hide or obfuscate selected domains.
To just hide everything set:
```elixir
config :pleroma, :mrf,
...
transparency: false,
```
To hide or obfuscate only select entries, use:
```elixir
config :pleroma, :mrf,
...
transparency_obfuscate_domains: ["handholdi.ng", "badword.com"],
transparency_exclusions: [{"ghost.club", "even a fragment is too spoopy for humans"}]
```
## More MRF Policies
See the [documentation cheatsheet](cheatsheet.md)
for all available MRF policies and their options.
## Writing your own MRF Policy
As discussed above, the MRF system is a modular system that supports pluggable policies. This means that an admin may write a custom MRF policy in Elixir or any other language that runs on the Erlang VM, by specifying the module name in the `policies` config setting.

View File

@ -25,11 +25,14 @@ Tuning the BEAM requires you provide a config file normally called [vm.args](htt
`ExecStart=/usr/bin/elixir --erl '-args_file /opt/akkoma/config/vm.args' -S /usr/bin/mix phx.server`
If using an OTP release, set the `RELEASE_VM_ARGS` environment variable to the path to the vm.args file.
Check your OS documentation to adopt a similar strategy on other platforms.
### Virtual Machine and/or few CPU cores
Disable the busy-waiting. This should generally only be done if you're on a platform that does burst scheduling, like AWS.
Disable the busy-waiting. This should generally be done if you're on a platform that does burst scheduling, like AWS, or if you're running other
services on the same machine.
**vm.args:**
@ -39,6 +42,8 @@ Disable the busy-waiting. This should generally only be done if you're on a plat
+sbwtdio none
```
These settings are enabled by default for OTP releases
### Dedicated Hardware
Enable more busy waiting, increase the internal maximum limit of BEAM processes and ports. You can use this if you run on dedicated hardware, but it is not necessary.

View File

@ -0,0 +1,146 @@
# Akkoma API
Request authentication (if required) and parameters work the same as for [Pleroma API](pleroma_api.md).
## `/api/v1/akkoma/preferred_frontend/available`
### Returns the available frontends which can be picked as the preferred choice
* Method: `GET`
* Authentication: not required
* Params: none
* Response: JSON
* Example response:
```json
["pleroma-fe/stable"]
```
!!! note
Theres also a browser UI under `/akkoma/frontend`
for interactively querying and changing this.
## `/api/v1/akkoma/preferred_frontend`
### Configures the preferred frontend of this session
* Method: `PUT`
* Authentication: not required
* Params:
* `frontend_name`: STRING containing one of the available frontends
* Response: JSON
* Example response:
```json
{"frontend_name":"pleroma-fe/stable"}
```
!!! note
Theres also a browser UI under `/akkoma/frontend`
for interactively querying and changing this.
## `/api/v1/akkoma/metrics`
### Provides metrics for Prometheus to scrape
* Method: `GET`
* Authentication: required (admin:metrics)
* Params: none
* Response: text
* Example response:
```
# HELP pleroma_remote_users_total
# TYPE pleroma_remote_users_total gauge
pleroma_remote_users_total 25
# HELP pleroma_local_statuses_total
# TYPE pleroma_local_statuses_total gauge
pleroma_local_statuses_total 17
# HELP pleroma_domains_total
# TYPE pleroma_domains_total gauge
pleroma_domains_total 4
# HELP pleroma_local_users_total
# TYPE pleroma_local_users_total gauge
pleroma_local_users_total 3
...
```
## `/api/v1/akkoma/translation/languages`
### Returns available source and target languages for automated text translation
* Method: `GET`
* Authentication: required
* Params: none
* Response: JSON
* Example response:
```json
{
"source": [
{"code":"LV", "name":"Latvian"},
{"code":"ZH", "name":"Chinese (traditional)"},
{"code":"EN-US", "name":"English (American)"}
],
"target": [
{"code":"EN-GB", "name":"English (British)"},
{"code":"JP", "name":"Japanese"}
]
}
```
## `/api/v1/akkoma/frontend_settings/:frontend_name`
### Lists all configuration profiles of the selected frontend for the current user
* Method: `GET`
* Authentication: required
* Params: none
* Response: JSON
* Example response:
```json
[
{"name":"default","version":31}
]
```
## `/api/v1/akkoma/frontend_settings/:frontend_name/:profile_name`
### Returns the full selected frontend settings profile of the current user
* Method: `GET`
* Authentication: required
* Params: none
* Response: JSON
* Example response:
```json
{
"version": 31,
"settings": {
"streaming": true,
"conversationDisplay": "tree",
...
}
}
```
## `/api/v1/akkoma/frontend_settings/:frontend_name/:profile_name`
### Updates the frontend settings profile
* Method: `PUT`
* Authentication: required
* Params:
* `version`: INTEGER
* `settings`: JSON object containing the entire new settings
* Response: JSON
* Example response:
```json
{
"streaming": false,
"conversationDisplay": "tree",
...
}
```
!!! note
The `version` field must be increased by exactly one on each update
## `/api/v1/akkoma/frontend_settings/:frontend_name/:profile_name`
### Drops the specified frontend settings profile
* Method: `DELETE`
* Authentication: required
* Params: none
* Response: JSON
* Example response:
```json
{"deleted":"ok"}
```
## `/api/v1/timelines/bubble`
### Returns a timeline for the local and closely related instances
Works like all other Mastodon-API timeline queries with the documented
[Akkoma-specific additions and tweaks](./differences_in_mastoapi_responses.md#timelines).

View File

@ -1,6 +1,6 @@
# Differences in Mastodon API responses from vanilla Mastodon
A Akkoma instance can be identified by "<Mastodon version> (compatible; Pleroma <version>)" present in `version` field in response from `/api/v1/instance`
A Akkoma instance can be identified by "<Mastodon version> (compatible; Akkoma <version>)" present in `version` field in response from `/api/v1/instance`
## Flake IDs
@ -8,20 +8,28 @@ Akkoma uses 128-bit ids as opposed to Mastodon's 64 bits. However, just like Mas
## Timelines
In addition to Mastodons timelines, there is also a “bubble timeline” showing
posts from the local instance and a set of closely related instances as chosen
by the administrator. It is available under `/api/v1/timelines/bubble`.
Adding the parameter `with_muted=true` to the timeline queries will also return activities by muted (not by blocked!) users.
Adding the parameter `exclude_visibilities` to the timeline queries will exclude the statuses with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`), e.g., `exclude_visibilities[]=direct&exclude_visibilities[]=private`.
Adding the parameter `reply_visibility` to the public and home timelines queries will filter replies. Possible values: without parameter (default) shows all replies, `following` - replies directed to you or users you follow, `self` - replies directed to you.
Adding the parameter `reply_visibility` to the public, bubble or home timelines queries will filter replies. Possible values: without parameter (default) shows all replies, `following` - replies directed to you or users you follow, `self` - replies directed to you.
Adding the parameter `instance=lain.com` to the public timeline will show only statuses originating from `lain.com` (or any remote instance).
Home, public, hashtag & list timelines accept these parameters:
All but the direct timeline accept these parameters:
- `only_media`: show only statuses with media attached
- `local`: show only local statuses
- `remote`: show only remote statuses
Home, public, hashtag & list timelines further accept:
- `local`: show only local statuses
## Statuses
- `visibility`: has additional possible values `list` and `local` (for local-only statuses)
@ -113,6 +121,12 @@ Has these additional fields under the `pleroma` object:
- `notification_settings`: object, can be absent. See `/api/v1/pleroma/notification_settings` for the parameters/keys returned.
- `favicon`: nullable URL string, Favicon image of the user's instance
Has these additional fields under the `akkoma` object:
- `instance`: nullable object with metadata about the users instance
- `status_ttl_days`: nullable int, default time after which statuses are deleted
- `permit_followback`: boolean, whether follows from followed accounts are auto-approved
### Source
Has these additional fields under the `pleroma` object:

View File

@ -21,6 +21,33 @@ fork of Akkoma - luckily this isn't very hard.
You'll need to update the backend, then possibly the frontend, depending
on your setup.
## Backup diverging features
As time goes on Akkoma and Pleroma added or removed different features
and reorganised the database in a different way. If you want to be able to
migrate back to Pleroma without losing any affected data, youll want to
make a backup before starting the migration.
If you're not interested in migrating back, skip this section
*(although it might be a good idea to temporarily keep a full DB backup
just in case something unexpected happens during migration)*
As of 2024-02 you will want to keep a backup of:
- the entire `chats` and `chat_message_references` tables
The following columns are not deleted by a migration to Akkoma, but a migration
back to Pleroma or future Akkoma upgrades might affect them, so perhaps back them up as well:
- the `birthday` of users and their `show_birthday` setting
- the `expires_at` key of in the `user_relationships` table
*(used by temporary mutes)*
The way cached instance metadata is stored differs, but since those
will be refetched and updated anyway, theres no need for a backup.
Best check all newer migrations unique to Akkoma/Pleroma
to get an up-to-date picture of what needs to be kept.
## From Source
If you're running the source Akkoma install, you'll need to set the
@ -34,16 +61,7 @@ git pull -r
# to run "git merge stable" instead (or develop if you want)
```
### WARNING - Migrating from Pleroma Develop
If you are on pleroma develop, and have updated since 2022-08, you may have issues with database migrations.
Please roll back the given migrations:
```bash
MIX_ENV=prod mix ecto.rollback --migrations-path priv/repo/optional_migrations/pleroma_develop_rollbacks -n5
```
Then compile, migrate and restart as usual.
And compile as usual.
## From OTP
@ -53,15 +71,44 @@ This will just be setting the update URL - find your flavour from the [mapping o
export FLAVOUR=[the flavour you found above]
./bin/pleroma_ctl update --zip-url https://akkoma-updates.s3-website.fr-par.scw.cloud/stable/akkoma-$FLAVOUR.zip
./bin/pleroma_ctl migrate
```
Then restart. When updating in the future, you canjust use
When updating in the future, you can just use
```bash
./bin/pleroma_ctl update --branch stable
```
## Database Migrations
### WARNING - Migrating from Pleroma past 2022-08
If you are on Pleroma stable >= 2.5.0 or Pleroma develop, and
have updated since 2022-08, you may have issues with database migrations.
Please first roll back the given migrations:
=== "OTP"
```bash
./bin/pleroma_ctl rollback --migrations-path priv/repo/optional_migrations/pleroma_develop_rollbacks -n5
```
=== "From Source"
```bash
MIX_ENV=prod mix ecto.rollback --migrations-path priv/repo/optional_migrations/pleroma_develop_rollbacks -n5
```
### Applying Akkoma Database Migrations
Just run
=== "OTP"
```bash
./bin/pleroma_ctl migrate
```
=== "From Source"
```bash
MIX_ENV=prod mix ecto.migrate
```
## Frontend changes
Akkoma comes with a few frontend changes as well as backend ones,
@ -130,3 +177,4 @@ MIX_ENV=prod mix ecto.rollback --to 20210416051708
```
Then switch back to Pleroma for updates (similar to how was done to migrate to Akkoma), and remove the front-ends. The front-ends are installed in the `frontends` folder in the [static directory](../configuration/static_dir.md). Once you are back to Pleroma, you will need to run the database migrations again. See the Pleroma documentation for this.
After this use your previous backups to restore data from diverging features.

View File

@ -26,6 +26,15 @@ defmodule Pleroma.Activity.Pruner do
|> Repo.delete_all(timeout: :infinity)
end
def prune_updates do
before_time = cutoff()
from(a in Activity,
where: fragment("?->>'type' = ?", a.data, "Update") and a.inserted_at < ^before_time
)
|> Repo.delete_all(timeout: :infinity)
end
def prune_removes do
before_time = cutoff()

View File

@ -23,7 +23,7 @@ defmodule Pleroma.Config.ReleaseRuntimeProvider do
with_runtime_config =
if File.exists?(config_path) do
# <https://git.pleroma.social/pleroma/pleroma/-/issues/3135>
%File.Stat{mode: mode} = File.lstat!(config_path)
%File.Stat{mode: mode} = File.stat!(config_path)
if Bitwise.band(mode, 0o007) > 0 do
raise "Configuration at #{config_path} has world-permissions, execute the following: chmod o= #{config_path}"

View File

@ -55,12 +55,61 @@ defmodule Pleroma.Emails.Mailer do
@doc false
def validate_dependency do
parse_config([])
parse_config([], defaults: false)
|> Keyword.get(:adapter)
|> Swoosh.Mailer.validate_dependency()
end
defp parse_config(config) do
Swoosh.Mailer.parse_config(@otp_app, __MODULE__, @mailer_config, config)
defp ensure_charlist(input) do
case input do
i when is_binary(i) -> String.to_charlist(input)
i when is_list(i) -> i
end
end
defp default_config(adapter, conf, opts)
defp default_config(_, _, defaults: false) do
[]
end
defp default_config(Swoosh.Adapters.SMTP, conf, _) do
# gen_smtp and Erlang's tls defaults are very barebones, if nothing is set.
# Add sane defaults for our usecase to make config less painful for admins
relay = ensure_charlist(Keyword.get(conf, :relay))
ssl_disabled = Keyword.get(conf, :ssl) === false
os_cacerts = :public_key.cacerts_get()
common_tls_opts = [
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,
# the default of 10 is too restrictive
depth: 32
]
[
auth: :always,
no_mx_lookups: false,
# Direct SSL/TLS
# (if ssl was explicitly disabled, we must not pass TLS options to the socket)
ssl: true,
sockopts: if(ssl_disabled, do: [], else: common_tls_opts),
# STARTTLS upgrade (can't be set to :always when already using direct TLS)
tls: :if_available,
tls_options: common_tls_opts
]
end
defp default_config(_, _, _), do: []
defp parse_config(config, opts \\ []) do
conf = Swoosh.Mailer.parse_config(@otp_app, __MODULE__, @mailer_config, config)
adapter = Keyword.get(conf, :adapter)
default_config(adapter, conf, opts)
|> Keyword.merge(conf)
end
end

View File

@ -15,8 +15,19 @@ defmodule Pleroma.JobQueueMonitor do
@impl true
def init(state) do
:telemetry.attach("oban-monitor-failure", [:oban, :job, :exception], &handle_event/4, nil)
:telemetry.attach("oban-monitor-success", [:oban, :job, :stop], &handle_event/4, nil)
:telemetry.attach(
"oban-monitor-failure",
[:oban, :job, :exception],
&Pleroma.JobQueueMonitor.handle_event/4,
nil
)
:telemetry.attach(
"oban-monitor-success",
[:oban, :job, :stop],
&Pleroma.JobQueueMonitor.handle_event/4,
nil
)
{:ok, state}
end

View File

@ -160,6 +160,7 @@ defmodule Pleroma.User do
field(:last_status_at, :naive_datetime)
field(:language, :string)
field(:status_ttl_days, :integer, default: nil)
field(:permit_followback, :boolean, default: false)
field(:accepts_direct_messages_from, Ecto.Enum,
values: [:everybody, :people_i_follow, :nobody],
@ -381,6 +382,10 @@ defmodule Pleroma.User do
do_optional_url(user.banner, "#{Endpoint.url()}/images/banner.png", options)
end
def background_url(user) do
do_optional_url(user.background, nil, no_default: true)
end
defp do_optional_url(field, default, options) do
case field do
%{"url" => [%{"href" => href} | _]} when is_binary(href) ->
@ -465,6 +470,7 @@ defmodule Pleroma.User do
:avatar,
:ap_enabled,
:banner,
:background,
:is_locked,
:last_refreshed_at,
:uri,
@ -544,6 +550,7 @@ defmodule Pleroma.User do
:actor_type,
:disclose_client,
:status_ttl_days,
:permit_followback,
:accepts_direct_messages_from
]
)
@ -972,16 +979,21 @@ defmodule Pleroma.User do
def needs_update?(_), do: true
# "Locked" (self-locked) users demand explicit authorization of follow requests
@spec can_direct_follow_local(User.t(), User.t()) :: true | false
def can_direct_follow_local(%User{} = follower, %User{local: true} = followed) do
!followed.is_locked || (followed.permit_followback and is_friend_of(follower, followed))
end
@spec maybe_direct_follow(User.t(), User.t()) ::
{:ok, User.t(), User.t()} | {:error, String.t()}
# "Locked" (self-locked) users demand explicit authorization of follow requests
def maybe_direct_follow(%User{} = follower, %User{local: true, is_locked: true} = followed) do
follow(follower, followed, :follow_pending)
end
def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
follow(follower, followed)
if can_direct_follow_local(follower, followed) do
follow(follower, followed)
else
follow(follower, followed, :follow_pending)
end
end
def maybe_direct_follow(%User{} = follower, %User{} = followed) do
@ -1331,6 +1343,13 @@ defmodule Pleroma.User do
|> Repo.all()
end
def is_friend_of(%User{} = potential_friend, %User{local: true} = user) do
user
|> get_friends_query()
|> where(id: ^potential_friend.id)
|> Repo.exists?()
end
def increase_note_count(%User{} = user) do
User
|> where(id: ^user.id)

View File

@ -1603,6 +1603,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
uri: get_actor_url(data["url"]),
ap_enabled: true,
banner: normalize_image(data["image"]),
background: normalize_image(data["backgroundUrl"]),
fields: fields,
emoji: emojis,
is_locked: is_locked,

View File

@ -178,6 +178,23 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_banner_removal(_actor_info, object), do: {:ok, object}
defp check_background_removal(
%{host: actor_host} = _actor_info,
%{"backgroundUrl" => _bg} = object
) do
background_removal =
instance_list(:background_removal)
|> MRF.subdomains_regex()
if MRF.subdomain_match?(background_removal, actor_host) do
{:ok, Map.delete(object, "backgroundUrl")}
else
{:ok, object}
end
end
defp check_background_removal(_actor_info, object), do: {:ok, object}
defp extract_context_uri(%{"conversation" => "tag:" <> rest}) do
rest
|> String.split(",", parts: 2, trim: true)
@ -283,7 +300,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
with {:ok, _} <- check_accept(actor_info),
{:ok, _} <- check_reject(actor_info),
{:ok, object} <- check_avatar_removal(actor_info, object),
{:ok, object} <- check_banner_removal(actor_info, object) do
{:ok, object} <- check_banner_removal(actor_info, object),
{:ok, object} <- check_background_removal(actor_info, object) do
{:ok, object}
else
{:reject, nil} -> {:reject, "[SimplePolicy]"}
@ -314,6 +332,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
def filter(object), do: {:ok, object}
defp obfuscate(string) when is_binary(string) do
# Want to strip at least two neighbouring chars
# to ensure at least one non-dot char is in the obfuscation area
stripped = String.length(string) - 6
{keepstart, keepend} =
if stripped > 1 do
{3, 3}
else
{
2 - div(1 - stripped, 2),
2 + div(stripped, 2)
}
end
string
|> to_charlist()
|> Enum.with_index()
@ -322,7 +354,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
?.
{char, index} ->
if 3 <= index && index < String.length(string) - 3, do: ?*, else: char
if keepstart <= index && index < String.length(string) - keepend, do: ?*, else: char
end)
|> to_string()
end
@ -433,6 +465,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
key: :banner_removal,
description: "List of instances to strip banners from and the reason for doing so"
},
%{
key: :background_removal,
description:
"List of instances to strip user backgrounds from and the reason for doing so"
},
%{
key: :reject_deletes,
description: "List of instances to reject deletions from and the reason for doing so"

View File

@ -34,6 +34,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
|> Path.basename()
|> Path.extname()
shortcode = Path.basename(shortcode)
file_path = Path.join(emoji_dir_path, shortcode <> (extension || ".png"))
case File.write(file_path, response.body) do
@ -76,6 +77,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
new_emojis =
foreign_emojis
|> Enum.reject(fn {shortcode, _url} -> shortcode in installed_emoji end)
|> Enum.reject(fn {shortcode, _url} ->
String.contains?(shortcode, ["/", "\\", ".", ":"])
end)
|> Enum.filter(fn {shortcode, _url} ->
reject_emoji? =
[:mrf_steal_emoji, :rejected_shortcodes]

View File

@ -109,7 +109,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
%User{} = followed <- User.get_cached_by_ap_id(followed_user),
{_, {:ok, _, _}, _, _} <-
{:following, User.follow(follower, followed, :follow_pending), follower, followed} do
if followed.local && !followed.is_locked do
if followed.local && User.can_direct_follow_local(follower, followed) do
{:ok, accept_data, _} = Builder.accept(followed, object)
{:ok, _activity, _} = Pipeline.common_pipeline(accept_data, local: true)
end

View File

@ -112,6 +112,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
}
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
# Yes, the key is named ...Url eventhough it is a whole 'Image' object
|> Map.merge(maybe_insert_image("backgroundUrl", User.background_url(user)))
|> Map.merge(Utils.make_json_ld_header())
end
@ -287,7 +289,12 @@ defmodule Pleroma.Web.ActivityPub.UserView do
end
defp maybe_make_image(func, key, user) do
if image = func.(user, no_default: true) do
image = func.(user, no_default: true)
maybe_insert_image(key, image)
end
defp maybe_insert_image(key, image) do
if image do
%{
key => %{
"type" => "Image",

View File

@ -723,6 +723,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
description:
"Number of days after which statuses will be deleted. Set to -1 to disable."
},
permit_followback: %Schema{
allOf: [BooleanLike],
nullable: true,
description:
"Whether follow requests from accounts the user is already following are auto-approved (when locked)."
},
accepts_direct_messages_from: %Schema{
type: :string,
enum: [
@ -754,6 +760,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
discoverable: false,
actor_type: "Person",
status_ttl_days: 30,
permit_followback: true,
accepts_direct_messages_from: "everybody"
}
}

View File

@ -111,9 +111,9 @@ defmodule Pleroma.Web.ApiSpec.FrontendSettingsOperation do
def update_preferred_frontend_operation() do
%Operation{
tags: ["Frontends"],
summary: "Frontend Settings Profiles",
description: "List frontend setting profiles",
operationId: "AkkomaAPI.FrontendSettingsController.available_frontends",
summary: "Update preferred frontend setting",
description: "Store preferred frontend in cookies",
operationId: "AkkomaAPI.FrontendSettingsController.update_preferred_frontend",
requestBody:
request_body(
"Frontend",
@ -132,9 +132,11 @@ defmodule Pleroma.Web.ApiSpec.FrontendSettingsOperation do
responses: %{
200 =>
Operation.response("Frontends", "application/json", %Schema{
type: :array,
items: %Schema{
type: :string
type: :object,
properties: %{
frontend_name: %Schema{
type: :string
}
}
})
}

View File

@ -137,7 +137,7 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do
"background_upload_limit" => 4_000_000,
"background_image" => "/static/image.png",
"banner_upload_limit" => 4_000_000,
"description" => "Pleroma: An efficient and flexible fediverse server",
"description" => "Akkoma: The cooler fediverse server",
"email" => "lain@lain.com",
"languages" => ["en"],
"max_toot_chars" => 5000,
@ -160,7 +160,7 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do
"urls" => %{
"streaming_api" => "wss://lain.com"
},
"version" => "2.7.2 (compatible; Pleroma 2.0.50-536-g25eec6d7-develop)"
"version" => "2.7.2 (compatible; Akkoma 3.9.3-232-g6fde75e1-develop)"
}
}
end

View File

@ -112,7 +112,18 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
akkoma: %Schema{
type: :object,
properties: %{
note_ttl_days: %Schema{type: :integer}
instance: %Schema{
type: :object,
nullable: true,
properties: %{
name: %Schema{type: :string},
favicon: %Schema{type: :string, format: :uri, nullable: true},
# XXX: proper nodeinfo schema
nodeinfo: %Schema{type: :object, nullable: true}
}
},
status_ttl_days: %Schema{type: :integer, nullable: true},
permit_followback: %Schema{type: :boolean}
}
},
source: %Schema{
@ -205,6 +216,18 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
"pleroma-fe" => %{}
}
},
"akkoma" => %{
"instance" => %{
"name" => "ihatebeinga.live",
"favicon" => "https://ihatebeinga.live/favicon.png",
"nodeinfo" =>
%{
# XXX: nodeinfo schema
}
},
"status_ttl_days" => nil,
"permit_followback" => true
},
"source" => %{
"fields" => [],
"note" => "foobar",

View File

@ -222,6 +222,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|> Maps.put_if_present(:language, Pleroma.Web.Gettext.normalize_locale(params[:language]))
|> Maps.put_if_present(:status_ttl_days, params[:status_ttl_days], status_ttl_days_value)
|> Maps.put_if_present(:accepts_direct_messages_from, params[:accepts_direct_messages_from])
|> Maps.put_if_present(:permit_followback, params[:permit_followback])
# What happens here:
#

View File

@ -261,6 +261,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|> MediaProxy.url()
end
last_status_at =
if is_nil(user.last_status_at), do: nil, else: NaiveDateTime.to_date(user.last_status_at)
%{
id: to_string(user.id),
username: username_from_nickname(user.nickname),
@ -289,10 +292,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
actor_type: user.actor_type
}
},
last_status_at: user.last_status_at,
last_status_at: last_status_at,
akkoma: %{
instance: render("instance.json", %{instance: instance}),
status_ttl_days: user.status_ttl_days
status_ttl_days: user.status_ttl_days,
permit_followback: user.permit_followback
},
# Pleroma extensions
# Note: it's insecure to output :email but fully-qualified nickname may serve as safe stub

View File

@ -39,6 +39,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
action_fallback(Pleroma.Web.OAuth.FallbackController)
@oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob"
@state_cookie_name "akkoma_oauth_state"
# Note: this definition is only called from error-handling methods with `conn.params` as 2nd arg
def authorize(%Plug.Conn{} = conn, %{"authorization" => _} = params) do
@ -443,13 +444,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|> Map.put("scope", scope)
|> Jason.encode!()
params =
auth_attrs
|> Map.drop(~w(scope scopes client_id redirect_uri))
|> Map.put("state", state)
# Handing the request to Ueberauth
redirect(conn, to: ~p"/oauth/#{provider}?#{params}")
conn
|> put_resp_cookie(@state_cookie_name, state)
|> redirect(to: ~p"/oauth/#{provider}")
end
def request(%Plug.Conn{} = conn, params) do
@ -468,20 +466,26 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
def callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn, params) do
params = callback_params(params)
params = callback_params(conn, params)
messages = for e <- Map.get(failure, :errors, []), do: e.message
message = Enum.join(messages, "; ")
conn
|> put_flash(
:error,
dgettext("errors", "Failed to authenticate: %{message}.", message: message)
)
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
error_message = dgettext("errors", "Failed to authenticate: %{message}.", message: message)
if params["redirect_uri"] do
conn
|> put_flash(
:error,
error_message
)
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
else
send_resp(conn, :bad_request, error_message)
end
end
def callback(%Plug.Conn{} = conn, params) do
params = callback_params(params)
params = callback_params(conn, params)
with {:ok, registration} <- Authenticator.get_registration(conn) do
auth_attrs = Map.take(params, ~w(client_id redirect_uri scope scopes state))
@ -511,8 +515,9 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
end
defp callback_params(%{"state" => state} = params) do
Map.merge(params, Jason.decode!(state))
defp callback_params(%Plug.Conn{} = conn, params) do
fetch_cookies(conn)
Map.merge(params, Jason.decode!(Map.get(conn.req_cookies, @state_cookie_name, "{}")))
end
def registration_details(%Plug.Conn{} = conn, %{"authorization" => auth_attrs}) do

View File

@ -24,7 +24,13 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
true <- Visibility.is_public?(activity.object),
{_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)},
%User{} = user <- User.get_by_ap_id(activity.object.data["actor"]) do
meta = Metadata.build_tags(%{url: activity.data["id"], object: activity.object, user: user})
meta =
Metadata.build_tags(%{
activity_id: notice_id,
url: activity.data["id"],
object: activity.object,
user: user
})
timeline =
activity.object.data["context"]

View File

@ -101,7 +101,8 @@ defmodule Pleroma.Web.Telemetry do
]
end
defp summary_metrics do
# Summary metrics are currently not (yet) supported by the prometheus exporter
defp summary_metrics(byte_unit) do
[
# Phoenix Metrics
summary("phoenix.endpoint.stop.duration",
@ -118,10 +119,98 @@ defmodule Pleroma.Web.Telemetry do
summary("pleroma.repo.query.idle_time", unit: {:native, :millisecond}),
# VM Metrics
summary("vm.memory.total", unit: {:byte, :kilobyte}),
summary("vm.memory.total", unit: {:byte, byte_unit}),
summary("vm.total_run_queue_lengths.total"),
summary("vm.total_run_queue_lengths.cpu"),
summary("vm.total_run_queue_lengths.io"),
summary("vm.total_run_queue_lengths.io")
]
end
defp sum_counter_pair(basename, opts) do
[
sum(basename <> ".psum", opts),
counter(basename <> ".pcount", opts)
]
end
# Prometheus exporter doesn't support summaries, so provide fallbacks
defp summary_fallback_metrics(byte_unit \\ :byte) do
# Summary metrics are not supported by the Prometheus exporter
# https://github.com/beam-telemetry/telemetry_metrics_prometheus_core/issues/11
# and sum metrics currently only work with integers
# https://github.com/beam-telemetry/telemetry_metrics_prometheus_core/issues/35
#
# For VM metrics this is kindof ok as they appear to always be integers
# and we can use sum + counter to get the average between polls from their change
# But for repo query times we need to use a full distribution
simple_buckets = [0, 1, 2, 4, 8, 16]
simple_buckets_quick = for t <- simple_buckets, do: t / 100.0
# Already included in distribution metrics anyway:
# phoenix.router_dispatch.stop.duration
# pleroma.repo.query.total_time
# pleroma.repo.query.queue_time
dist_metrics =
[
distribution("phoenix.endpoint.stop.duration.fdist",
event_name: [:phoenix, :endpoint, :stop],
measurement: :duration,
unit: {:native, :millisecond},
reporter_options: [
buckets: simple_buckets
]
),
distribution("pleroma.repo.query.decode_time.fdist",
event_name: [:pleroma, :repo, :query],
measurement: :decode_time,
unit: {:native, :millisecond},
reporter_options: [
buckets: simple_buckets_quick
]
),
distribution("pleroma.repo.query.query_time.fdist",
event_name: [:pleroma, :repo, :query],
measurement: :query_time,
unit: {:native, :millisecond},
reporter_options: [
buckets: simple_buckets
]
),
distribution("pleroma.repo.query.idle_time.fdist",
event_name: [:pleroma, :repo, :query],
measurement: :idle_time,
unit: {:native, :millisecond},
reporter_options: [
buckets: simple_buckets
]
)
]
vm_metrics =
sum_counter_pair("vm.memory.total",
event_name: [:vm, :memory],
measurement: :total,
unit: {:byte, byte_unit}
) ++
sum_counter_pair("vm.total_run_queue_lengths.total",
event_name: [:vm, :total_run_queue_lengths],
measurement: :total
) ++
sum_counter_pair("vm.total_run_queue_lengths.cpu",
event_name: [:vm, :total_run_queue_lengths],
measurement: :cpu
) ++
sum_counter_pair("vm.total_run_queue_lengths.io.fsum",
event_name: [:vm, :total_run_queue_lengths],
measurement: :io
)
dist_metrics ++ vm_metrics
end
defp common_metrics do
[
last_value("pleroma.local_users.total"),
last_value("pleroma.domains.total"),
last_value("pleroma.local_statuses.total"),
@ -129,8 +218,10 @@ defmodule Pleroma.Web.Telemetry do
]
end
def prometheus_metrics, do: summary_metrics() ++ distribution_metrics()
def live_dashboard_metrics, do: summary_metrics()
def prometheus_metrics,
do: common_metrics() ++ distribution_metrics() ++ summary_fallback_metrics()
def live_dashboard_metrics, do: common_metrics() ++ summary_metrics(:megabyte)
defp periodic_measurements do
[

View File

@ -21,6 +21,9 @@ defmodule Pleroma.Workers.Cron.PruneDatabaseWorker do
Logger.info("Pruning old undos")
ActivityPruner.prune_undos()
Logger.info("Pruning old updates")
ActivityPruner.prune_updates()
Logger.info("Pruning old removes")
ActivityPruner.prune_removes()

View File

@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
def project do
[
app: :pleroma,
version: version("3.10.3"),
version: version("3.11.0"),
elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),
@ -157,7 +157,7 @@ defmodule Pleroma.Mixfile do
{:ex_syslogger, "~> 2.0.0"},
{:floki, "~> 0.34"},
{:timex, "~> 3.7"},
{:ueberauth, "~> 0.10"},
{:ueberauth, "== 0.10.5"},
{:linkify, git: "https://akkoma.dev/AkkomaGang/linkify.git"},
{:http_signatures,
git: "https://akkoma.dev/AkkomaGang/http_signatures.git",

View File

@ -124,7 +124,7 @@
"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"},
"ueberauth": {:hex, :ueberauth, "0.10.6", "8dbefd5aec30c5830af2b6ce6e03f62cc28ae0757f34e2986454f54b8dca3f65", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b0ad1c7508f3cfd5c2c1c668d1a32bafd77de4c56af82c7bfd7e54ed078a7928"},
"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.1", "cb65348ebd1c4002861b65bef36e524c29d9a879c90119b2d0e674e323124277", [:mix], [], "hexpm", "a0f9f3959d127ad6a6a617c3f607ecfb1bc6f3c59f9c3614a901a46d1765bafe"},

File diff suppressed because it is too large Load Diff

View File

@ -8,146 +8,154 @@
### to merge POT files into PO files.
msgid ""
msgstr ""
"PO-Revision-Date: 2023-10-09 18:53+0000\n"
"Last-Translator: subtype <subtype@hollow.capital>\n"
"Language-Team: Polish <http://translate.akkoma.dev/projects/akkoma/"
"akkoma-backend-posix-errors/pl/>\n"
"Language: pl\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10||n%100>=20) ? 1 : 2);\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 4.18.2\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "eperm"
msgstr ""
msgstr "Operacja niedozwolona"
msgid "eacces"
msgstr ""
msgstr "Brak dostępu"
msgid "eagain"
msgstr ""
msgstr "Zasoby chwilowo niedostępne"
msgid "ebadf"
msgstr ""
msgstr "Błędny deskryptor pliku"
msgid "ebadmsg"
msgstr ""
msgstr "Błędny komunikat"
msgid "ebusy"
msgstr ""
msgstr "Urządzenie lub zasoby zajęte"
msgid "edeadlk"
msgstr ""
msgstr "Uniknięto zakleszczenia zasobów"
msgid "edeadlock"
msgstr ""
msgstr "Uniknięto zakleszczenia zasobów"
msgid "edquot"
msgstr ""
msgstr "Przekroczony limit dyskowy"
msgid "eexist"
msgstr ""
msgstr "Plik istnieje"
msgid "efault"
msgstr ""
msgstr "Błędny adres"
msgid "efbig"
msgstr ""
msgstr "Plik zbyt duży"
msgid "eftype"
msgstr ""
msgstr "Niewłaściwy typ pliku"
msgid "eintr"
msgstr ""
msgstr "Przerwane wywołanie systemowe"
msgid "einval"
msgstr ""
msgstr "Zły argument"
msgid "eio"
msgstr ""
msgstr "Błąd wejścia/wyjścia"
msgid "eisdir"
msgstr ""
msgstr "Jest katalogiem"
msgid "eloop"
msgstr ""
msgstr "Za duże zagnieżdżenie dowiązań symbolicznych"
msgid "emfile"
msgstr ""
msgstr "Za dużo otwartych plików"
msgid "emlink"
msgstr ""
msgstr "Za dużo dowiązań"
msgid "emultihop"
msgstr ""
msgstr "Multihop attempted"
msgid "enametoolong"
msgstr ""
msgstr "Za długa nazwa pliku"
msgid "enfile"
msgstr ""
msgstr "Za dużo otwartych plików w systemie"
msgid "enobufs"
msgstr ""
msgstr "Brak miejsca w buforze"
msgid "enodev"
msgstr ""
msgstr "Nie ma takiego urządzenia"
msgid "enolck"
msgstr ""
msgstr "Brak dostępnych blokad"
msgid "enolink"
msgstr ""
msgstr "Połączenie zostało przerwane"
msgid "enoent"
msgstr ""
msgstr "Nie ma takiego pliku ani katalogu"
msgid "enomem"
msgstr ""
msgstr "Nie można przydzielić pamięci"
msgid "enospc"
msgstr ""
msgstr "Brak miejsca na urządzeniu"
msgid "enosr"
msgstr ""
msgstr "Brak dodatkowych strumieni"
msgid "enostr"
msgstr ""
msgstr "Nie jest strumieniem"
msgid "enosys"
msgstr ""
msgstr "Niezaimplementowana funkcja"
msgid "enotblk"
msgstr ""
msgstr "Wymagane urządzenie blokowe"
msgid "enotdir"
msgstr ""
msgstr "Nie jest katalogiem"
msgid "enotsup"
msgstr ""
msgstr "Operacja nieobsługiwana"
msgid "enxio"
msgstr ""
msgstr "Nie ma takiego urządzenia ani adresu"
msgid "eopnotsupp"
msgstr ""
msgstr "Operacja na gnieździe nieobsługiwana"
msgid "eoverflow"
msgstr ""
msgstr "Wartość za duża dla zdefiniowanego typu danych"
msgid "epipe"
msgstr ""
msgstr "Przerwany potok"
msgid "erange"
msgstr ""
msgstr "Za duży wynik"
msgid "erofs"
msgstr ""
msgstr "System plików wyłącznie do odczytu"
msgid "espipe"
msgstr ""
msgstr "Błędne przesunięcie"
msgid "esrch"
msgstr ""
msgstr "Nie ma takiego procesu"
msgid "estale"
msgstr ""
msgstr "Nieaktualny uchwyt pliku"
msgid "etxtbsy"
msgstr ""
msgstr "Plik tekstowy zajęty"
msgid "exdev"
msgstr ""
msgstr "Niepoprawne dowiązanie"

View File

@ -3,16 +3,16 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-06 11:13+0000\n"
"PO-Revision-Date: 2021-09-07 16:42+0000\n"
"Last-Translator: Hồ Nhất Duy <kantcer@gmail.com>\n"
"Language-Team: Vietnamese <https://translate.pleroma.social/projects/pleroma/"
"pleroma/vi/>\n"
"PO-Revision-Date: 2023-09-08 05:53+0000\n"
"Last-Translator: Nguyễn Gia Phong <cnx@loang.net>\n"
"Language-Team: Vietnamese <http://translate.akkoma.dev/projects/akkoma/"
"akkoma-backend-errors/vi/>\n"
"Language: vi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 4.6.2\n"
"X-Generator: Weblate 4.18.2\n"
## This file is a PO Template file.
##
@ -577,37 +577,37 @@ msgstr "Người này không phải quản trị viên."
#, elixir-format
msgid "Last export was less than a day ago"
msgid_plural "Last export was less than %{days} days ago"
msgstr[0] ""
msgstr[0] "Vừa sao lưu lần cuối %{days} ngày trước"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:399
#, elixir-autogen, elixir-format
msgid "Character limit (%{limit} characters) exceeded, contains %{length} characters"
msgstr ""
msgstr "Quá dài: %{length} kí tự (tối đa %{limit})"
#: lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex:33
#: lib/pleroma/web/plugs/user_is_staff_plug.ex:20
#, elixir-autogen, elixir-format, fuzzy
#, elixir-autogen, elixir-format
msgid "User is not a staff member."
msgstr "Người này không phải quản trị viên."
msgstr "Người dùng không phải quản trị viên."
#: lib/pleroma/web/o_auth/o_auth_controller.ex:391
#, elixir-autogen, elixir-format
msgid "Your account is awaiting approval."
msgstr ""
msgstr "Tài khoản của bạn đang chờ được duyệt."
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:256
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:259
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:262
#, elixir-autogen, elixir-format
msgid "File is too large"
msgstr ""
msgstr "Tệp quá lớn"
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:37
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:48
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:59
#, elixir-autogen, elixir-format, fuzzy
#, elixir-autogen, elixir-format
msgid "Hashtag not found"
msgstr "Không tìm thấy danh sách"
msgstr "Không tìm thấy hashtag"
#: lib/pleroma/web/common_api/activity_draft.ex:144
#, elixir-autogen, elixir-format
@ -627,7 +627,7 @@ msgstr ""
#: lib/pleroma/web/common_api/activity_draft.ex:126
#, elixir-autogen, elixir-format
msgid "You can't quote a status that doesn't exist"
msgstr ""
msgstr "Bạn không thể trích dẫn bàn viết không tồn tại"
#: lib/pleroma/web/embed_controller.ex:35
#, elixir-autogen, elixir-format
@ -637,9 +637,9 @@ msgstr ""
#: lib/pleroma/web/embed_controller.ex:38
#, elixir-autogen, elixir-format
msgid "Not authorized to view this post"
msgstr ""
msgstr "Không có quyền xem bài viết này"
#: lib/pleroma/web/embed_controller.ex:32
#, elixir-autogen, elixir-format, fuzzy
#, elixir-autogen, elixir-format
msgid "Post not found"
msgstr "Không tìm thấy danh sách"
msgstr "Không tìm thấy bài viết"

View File

@ -0,0 +1,50 @@
defmodule Pleroma.Repo.Migrations.DropChatTables do
use Ecto.Migration
def up do
# Automatically drops associated indices and constraints
drop table(:chat_message_references)
drop table(:chats)
end
def down do
# Ecto's default primary key is bigserial, thus configure manually
create table(:chats, primary_key: false) do
add(:id, :uuid, primary_key: true, autogenerated: true)
add(
:user_id,
references(:users, type: :uuid, on_delete: :delete_all)
# yes, this was nullable
)
add(
:recipient,
references(:users, column: :ap_id, type: :string, on_delete: :delete_all)
# yes, this was nullable
)
timestamps()
end
create(index(:chats, [:user_id, :recipient], unique: true))
create table(:chat_message_references, primary_key: false) do
add(:id, :uuid, primary_key: true, autogenerated: true)
add(:chat_id, references(:chats, type: :uuid, on_delete: :delete_all), null: false)
add(:object_id, references(:objects, on_delete: :delete_all), null: false)
add(:unread, :boolean, default: true, null: false)
timestamps()
end
create(index(:chat_message_references, [:chat_id, "id desc"]))
create(unique_index(:chat_message_references, [:object_id, :chat_id]))
create(
index(:chat_message_references, [:chat_id],
where: "unread = true",
name: "unread_messages_count_index"
)
)
end
end

View File

@ -0,0 +1,9 @@
defmodule Pleroma.Repo.Migrations.AddPermitFollowback do
use Ecto.Migration
def change do
alter table(:users) do
add(:permit_followback, :boolean, null: false, default: false)
end
end
end

View File

@ -128,6 +128,4 @@ defmodule Pleroma.HTML.Scrubber.Default do
Meta.allow_tag_with_these_attributes(:small, [])
Meta.strip_everything_not_covered()
defp scrub_css(value), do: value
end

View File

@ -9,3 +9,9 @@
## Tweak GC to run more often
##-env ERL_FULLSWEEP_AFTER 10
## Disable busy waits; vastly reduces CPU usage while idle
## See https://docs.akkoma.dev/stable/configuration/optimisation/optimizing_beam/#virtual-machine-andor-few-cpu-cores
+sbwt none
+sbwtdcpu none
+sbwtdio none

View File

@ -371,8 +371,6 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do
["apps"],
["backups"],
["bookmarks"],
["chat_message_references"],
["chats"],
["config"],
["conversation_participation_recipient_ships"],
["conversation_participations"],

View File

@ -6,22 +6,52 @@ defmodule Pleroma.Activity.PrunerTest do
import Pleroma.Factory
describe "prune_deletes" do
test "it prunes old delete objects" do
describe "prune_transient_activities" do
test "it prunes old transient activities" do
user = insert(:user)
old_time = DateTime.utc_now() |> DateTime.add(-31 * 24, :hour)
new_delete = insert(:delete_activity, type: "Delete", user: user)
old_delete =
insert(:delete_activity,
type: "Delete",
user: user,
inserted_at: DateTime.utc_now() |> DateTime.add(-31 * 24, :hour)
inserted_at: old_time
)
new_update = insert(:update_activity, type: "Update", user: user)
old_update =
insert(:update_activity,
type: "Update",
user: user,
inserted_at: old_time
)
new_undo = insert(:undo_activity)
old_undo = insert(:undo_activity, inserted_at: old_time)
new_remove = insert(:remove_activity)
old_remove = insert(:remove_activity, inserted_at: old_time)
Pruner.prune_deletes()
Pruner.prune_updates()
Pruner.prune_undos()
Pruner.prune_removes()
assert Activity.get_by_id(new_delete.id)
refute Activity.get_by_id(old_delete.id)
assert Activity.get_by_id(new_update.id)
refute Activity.get_by_id(old_update.id)
assert Activity.get_by_id(new_undo.id)
refute Activity.get_by_id(old_undo.id)
assert Activity.get_by_id(new_remove.id)
refute Activity.get_by_id(old_remove.id)
end
end

View File

@ -19,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
accept: [],
avatar_removal: [],
banner_removal: [],
background_removal: [],
reject_deletes: []
)
@ -283,7 +284,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert {:ok,
%{
mrf_simple: %{reject: ["rem***.*****nce", "a.b"]},
mrf_simple: %{reject: ["rem***.*****nce", "*.b"]},
mrf_simple_info: %{reject: %{"rem***.*****nce" => %{}}}
}} = SimplePolicy.describe()
end
@ -618,6 +619,42 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
end
end
describe "when :background_removal" do
test "is empty" do
clear_config([:mrf_simple, :background_removal], [])
remote_user = build_remote_user()
assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
end
test "is not empty but it doesn't have a matching host" do
clear_config([:mrf_simple, :background_removal], [{"non.matching.remote", ""}])
remote_user = build_remote_user()
assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
end
test "has a matching host" do
clear_config([:mrf_simple, :background_removal], [{"remote.instance", ""}])
remote_user = build_remote_user()
{:ok, filtered} = SimplePolicy.filter(remote_user)
refute filtered["backgroundUrl"]
end
test "match with wildcard domain" do
clear_config([:mrf_simple, :background_removal], [{"*.remote.instance", ""}])
remote_user = build_remote_user()
{:ok, filtered} = SimplePolicy.filter(remote_user)
refute filtered["backgroundUrl"]
end
end
describe "when :reject_deletes is empty" do
setup do: clear_config([:mrf_simple, :reject_deletes], [])
@ -701,6 +738,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
"url" => "http://example.com/image.jpg",
"type" => "Image"
},
"backgroundUrl" => %{
"url" => "http://example.com/background.jpg",
"type" => "Image"
},
"type" => "Person"
}
end

View File

@ -60,6 +60,32 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicyTest do
|> File.exists?()
end
test "rejects invalid shortcodes", %{path: path} do
message = %{
"type" => "Create",
"object" => %{
"emoji" => [{"fired/fox", "https://example.org/emoji/firedfox"}],
"actor" => "https://example.org/users/admin"
}
}
fullpath = Path.join(path, "fired/fox.png")
Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox"} ->
%Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")}
end)
clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468)
refute "firedfox" in installed()
refute File.exists?(path)
assert {:ok, _message} = StealEmojiPolicy.filter(message)
refute "fired/fox" in installed()
refute File.exists?(fullpath)
end
test "reject regex shortcode", %{message: message} do
refute "firedfox" in installed()

View File

@ -155,7 +155,13 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
user = insert(:user, local: false)
{:ok, update_data, []} =
Builder.update(user, %{"id" => user.ap_id, "type" => "Person", "name" => "new name!"})
Builder.update(user, %{
"id" => user.ap_id,
"type" => "Person",
"name" => "new name!",
"icon" => %{"type" => "Image", "url" => "https://example.org/icon.png"},
"backgroundUrl" => %{"type" => "Image", "url" => "https://example.org/bg.jxl"}
})
{:ok, update, _meta} = ActivityPub.persist(update_data, local: true)
@ -165,7 +171,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
test "it updates the user", %{user: user, update: update} do
{:ok, _, _} = SideEffects.handle(update)
user = User.get_by_id(user.id)
assert user.name == "new name!"
assert [%{"href" => "https://example.org/icon.png"}] = user.avatar["url"]
assert [%{"href" => "https://example.org/bg.jxl"}] = user.background["url"]
end
test "it uses a given changeset to update", %{user: user, update: update} do

View File

@ -58,16 +58,19 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
result = UserView.render("user.json", %{user: user})
refute result["icon"]
refute result["image"]
refute result["backgroundUrl"]
user =
insert(:user,
avatar: %{"url" => [%{"href" => "https://someurl"}]},
banner: %{"url" => [%{"href" => "https://somebanner"}]}
banner: %{"url" => [%{"href" => "https://somebanner"}]},
background: %{"url" => [%{"href" => "https://somebackground"}]}
)
result = UserView.render("user.json", %{user: user})
assert result["icon"]["url"] == "https://someurl"
assert result["image"]["url"] == "https://somebanner"
assert result["backgroundUrl"]["url"] == "https://somebackground"
end
test "renders an invisible user with the invisible property set to true" do

View File

@ -119,4 +119,18 @@ defmodule Pleroma.Web.AkkomaAPI.FrontendSettingsControllerTest do
) == nil
end
end
describe "PUT /api/v1/akkoma/preferred_frontend" do
test "sets a cookie with selected frontend" do
%{conn: conn} = oauth_access(["read"])
response =
conn
|> put_req_header("content-type", "application/json")
|> put("/api/v1/akkoma/preferred_frontend", %{"frontend_name" => "pleroma-fe/stable"})
json_response_and_validate_schema(response, 200)
assert %{"preferred_frontend" => %{value: "pleroma-fe/stable"}} = response.resp_cookies
end
end
end

View File

@ -1061,6 +1061,36 @@ defmodule Pleroma.Web.CommonAPITest do
assert User.following?(follower, followed)
end
test "directly follows back a locked, but followback-allowing local user" do
uopen = insert(:user, is_locked: false)
uselective = insert(:user, is_locked: true, permit_followback: true)
assert {:ok, uselective, uopen, %{data: %{"state" => "accept"}}} =
CommonAPI.follow(uselective, uopen)
assert User.get_follow_state(uselective, uopen) == :follow_accept
assert {:ok, uopen, uselective, %{data: %{"state" => "accept"}}} =
CommonAPI.follow(uopen, uselective)
assert User.get_follow_state(uopen, uselective) == :follow_accept
end
test "creates a pending request for locked, non-followback local user" do
uopen = insert(:user, is_locked: false)
ulocked = insert(:user, is_locked: true, permit_followback: false)
assert {:ok, ulocked, uopen, %{data: %{"state" => "accept"}}} =
CommonAPI.follow(ulocked, uopen)
assert User.get_follow_state(ulocked, uopen) == :follow_accept
assert {:ok, uopen, ulocked, %{data: %{"state" => "pending"}}} =
CommonAPI.follow(uopen, ulocked)
assert User.get_follow_state(uopen, ulocked) == :follow_pending
end
end
describe "unfollow/2" do

View File

@ -40,7 +40,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
emoji: %{"karjalanpiirakka" => "/file.png"},
raw_bio: "valid html. a\nb\nc\nd\nf '&<>\"",
also_known_as: ["https://shitposter.zone/users/shp"],
status_ttl_days: 5
status_ttl_days: 5,
last_status_at: ~N[2023-12-31T15:06:17]
})
insert(:instance, %{host: "example.com", nodeinfo: %{version: "2.1"}})
@ -65,7 +66,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
},
favicon: nil
},
status_ttl_days: 5
status_ttl_days: 5,
permit_followback: false
},
avatar: "http://localhost:4001/images/avi.png",
avatar_static: "http://localhost:4001/images/avi.png",
@ -91,7 +93,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
fields: []
},
fqn: "shp@shitposter.club",
last_status_at: nil,
last_status_at: ~D[2023-12-31],
pleroma: %{
ap_id: user.ap_id,
also_known_as: ["https://shitposter.zone/users/shp"],
@ -248,7 +250,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
favicon: "http://localhost:4001/favicon.png",
nodeinfo: %{version: "2.0"}
},
status_ttl_days: nil
status_ttl_days: nil,
permit_followback: false
},
pleroma: %{
ap_id: user.ap_id,

View File

@ -81,9 +81,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
assert html_response(conn, 302)
redirect_query = URI.parse(redirected_to(conn)).query
assert %{"state" => state_param} = URI.decode_query(redirect_query)
assert {:ok, state_components} = Jason.decode(state_param)
assert {:ok, state_components} = Jason.decode(conn.resp_cookies["akkoma_oauth_state"].value)
expected_client_id = app.client_id
expected_redirect_uri = app.redirect_uris
@ -97,7 +95,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
end
test "with user-bound registration, GET /oauth/<provider>/callback redirects to `redirect_uri` with `code`",
%{app: app, conn: conn} do
%{app: app, conn: _} do
registration = insert(:registration)
redirect_uri = OAuthController.default_redirect_uri(app)
@ -109,15 +107,17 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
}
conn =
conn
build_conn()
|> put_req_cookie("akkoma_oauth_state", Jason.encode!(state_params))
|> Plug.Session.call(Plug.Session.init(@session_opts))
|> fetch_session()
|> assign(:ueberauth_auth, %{provider: registration.provider, uid: registration.uid})
|> get(
"/oauth/twitter/callback",
%{
"oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
"oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
"provider" => "twitter",
"state" => Jason.encode!(state_params)
"provider" => "twitter"
}
)
@ -162,15 +162,42 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
test "on authentication error, GET /oauth/<provider>/callback redirects to `redirect_uri`", %{
app: app,
conn: conn
conn: _
} do
state_params = %{
"scope" => Enum.join(app.scopes, " "),
"client_id" => app.client_id,
"redirect_uri" => OAuthController.default_redirect_uri(app),
"state" => ""
"redirect_uri" => OAuthController.default_redirect_uri(app)
}
conn =
build_conn()
|> put_req_cookie("akkoma_oauth_state", Jason.encode!(state_params))
|> Plug.Session.call(Plug.Session.init(@session_opts))
|> fetch_session()
|> assign(:ueberauth_failure, %{errors: [%{message: "(error description)"}]})
|> get(
"/oauth/twitter/callback",
%{
"oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
"oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
"provider" => "twitter",
"state" => ""
}
)
assert html_response(conn, 302)
assert redirected_to(conn) == app.redirect_uris
assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
"Failed to authenticate: (error description)."
end
test "on authentication error with no prior state, GET /oauth/<provider>/callback returns 400",
%{
app: _,
conn: conn
} do
conn =
conn
|> assign(:ueberauth_failure, %{errors: [%{message: "(error description)"}]})
@ -180,15 +207,11 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
"oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
"oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
"provider" => "twitter",
"state" => Jason.encode!(state_params)
"state" => ""
}
)
assert html_response(conn, 302)
assert redirected_to(conn) == app.redirect_uris
assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
"Failed to authenticate: (error description)."
assert response(conn, 400)
end
test "GET /oauth/registration_details renders registration details form", %{

View File

@ -19,9 +19,26 @@ defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do
setup %{conn: conn} do
conn = put_req_header(conn, "accept", "text/html")
user = insert(:user)
%{conn: conn, user: user}
user_avatar_url = "https://example.org/akko.png"
user =
insert(:user,
local: true,
name: "Akko",
nickname: "atsuko",
bio: "A believing heart is my magic!",
raw_bio: "A believing heart is my magic!",
avatar: %{
"url" => [
%{
"href" => user_avatar_url
}
]
}
)
%{conn: conn, user: user, user_avatar_url: user_avatar_url}
end
describe "user profile html" do
@ -291,4 +308,142 @@ defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do
|> html_response(404)
end
end
defp meta_content(metadata_tag) do
:proplists.get_value("content", metadata_tag)
end
defp meta_find_og(document, name) do
Floki.find(document, "head>meta[property=\"og:" <> name <> "\"]")
end
defp meta_find_twitter(document, name) do
Floki.find(document, "head>meta[name=\"twitter:" <> name <> "\"]")
end
# Detailed metadata tests are already done for each builder individually, so just
# one check per type of content should suffice to ensure we're calling the providers correctly
describe "metadata tags for" do
setup do
clear_config([Pleroma.Web.Metadata, :providers], [
Pleroma.Web.Metadata.Providers.OpenGraph,
Pleroma.Web.Metadata.Providers.TwitterCard
])
end
test "user profile", %{conn: conn, user: user, user_avatar_url: user_avatar_url} do
conn = get(conn, "/users/#{user.nickname}")
html = html_response(conn, 200)
{:ok, document} = Floki.parse_document(html)
[{"meta", og_type, _}] = meta_find_og(document, "type")
[{"meta", og_title, _}] = meta_find_og(document, "title")
[{"meta", og_url, _}] = meta_find_og(document, "url")
[{"meta", og_desc, _}] = meta_find_og(document, "description")
[{"meta", og_img, _}] = meta_find_og(document, "image")
[{"meta", og_imgw, _}] = meta_find_og(document, "image:width")
[{"meta", og_imgh, _}] = meta_find_og(document, "image:height")
[{"meta", tw_card, _}] = meta_find_twitter(document, "card")
[{"meta", tw_title, _}] = meta_find_twitter(document, "title")
[{"meta", tw_desc, _}] = meta_find_twitter(document, "description")
[{"meta", tw_img, _}] = meta_find_twitter(document, "image")
assert meta_content(og_type) == "article"
assert meta_content(og_title) == Pleroma.Web.Metadata.Utils.user_name_string(user)
assert meta_content(og_url) == user.ap_id
assert meta_content(og_desc) == user.bio
assert meta_content(og_img) == user_avatar_url
assert meta_content(og_imgw) == "150"
assert meta_content(og_imgh) == "150"
assert meta_content(tw_card) == "summary"
assert meta_content(tw_title) == meta_content(og_title)
assert meta_content(tw_desc) == meta_content(og_desc)
assert meta_content(tw_img) == meta_content(og_img)
end
test "text-only post", %{conn: conn, user: user, user_avatar_url: user_avatar_url} do
post_text = "How are lessons about magic t h i s boring?!"
{:ok, activity} = CommonAPI.post(user, %{status: post_text})
conn = get(conn, "/notice/#{activity.id}")
html = html_response(conn, 200)
{:ok, document} = Floki.parse_document(html)
[{"meta", og_type, _}] = meta_find_og(document, "type")
[{"meta", og_title, _}] = meta_find_og(document, "title")
[{"meta", og_url, _}] = meta_find_og(document, "url")
[{"meta", og_desc, _}] = meta_find_og(document, "description")
[{"meta", og_img, _}] = meta_find_og(document, "image")
[{"meta", og_imgw, _}] = meta_find_og(document, "image:width")
[{"meta", og_imgh, _}] = meta_find_og(document, "image:height")
[{"meta", tw_card, _}] = meta_find_twitter(document, "card")
[{"meta", tw_title, _}] = meta_find_twitter(document, "title")
[{"meta", tw_desc, _}] = meta_find_twitter(document, "description")
[{"meta", tw_img, _}] = meta_find_twitter(document, "image")
assert meta_content(og_type) == "article"
assert meta_content(og_title) == Pleroma.Web.Metadata.Utils.user_name_string(user)
assert meta_content(og_url) == activity.data["id"]
assert meta_content(og_desc) == post_text
assert meta_content(og_img) == user_avatar_url
assert meta_content(og_imgw) == "150"
assert meta_content(og_imgh) == "150"
assert meta_content(tw_card) == "summary"
assert meta_content(tw_title) == meta_content(og_title)
assert meta_content(tw_desc) == meta_content(og_desc)
assert meta_content(tw_img) == meta_content(og_img)
end
test "post with attachments", %{conn: conn, user: user} do
file = %Plug.Upload{
content_type: "image/jpeg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
alt_text = "The rarest of all Shiny Chariot cards"
{:ok, upload_data} = ActivityPub.upload(file, actor: user.ap_id, description: alt_text)
%{id: media_id, data: %{"url" => [%{"href" => media_url}]}} = upload_data
post_text = "Look!"
{:ok, activity} = CommonAPI.post(user, %{status: post_text, media_ids: [media_id]})
conn = get(conn, "/notice/#{activity.id}")
html = html_response(conn, 200)
{:ok, document} = Floki.parse_document(html)
[{"meta", og_type, _}] = meta_find_og(document, "type")
[{"meta", og_title, _}] = meta_find_og(document, "title")
[{"meta", og_url, _}] = meta_find_og(document, "url")
[{"meta", og_desc, _}] = meta_find_og(document, "description")
[{"meta", og_img, _}] = meta_find_og(document, "image")
[{"meta", og_alt, _}] = meta_find_og(document, "image:alt")
[{"meta", tw_card, _}] = meta_find_twitter(document, "card")
[{"meta", tw_title, _}] = meta_find_twitter(document, "title")
[{"meta", tw_desc, _}] = meta_find_twitter(document, "description")
[{"meta", tw_player, _}] = meta_find_twitter(document, "player")
assert meta_content(og_type) == "article"
assert meta_content(og_title) == Pleroma.Web.Metadata.Utils.user_name_string(user)
assert meta_content(og_url) == activity.data["id"]
assert meta_content(og_desc) == post_text
assert meta_content(og_img) == media_url
assert meta_content(og_alt) == alt_text
# Audio and video attachments use "player" and have some more metadata
assert meta_content(tw_card) == "summary_large_image"
assert meta_content(tw_title) == meta_content(og_title)
assert meta_content(tw_desc) == meta_content(og_desc)
assert meta_content(tw_player) == meta_content(og_img)
end
end
end

View File

@ -9,6 +9,7 @@ defmodule Pleroma.Factory do
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.UserView
@rsa_keys [
"test/fixtures/rsa_keys/key_1.pem",
@ -307,7 +308,7 @@ defmodule Pleroma.Factory do
featured_collection_activity(attrs, "Add")
end
def remove_activity_factor(attrs \\ %{}) do
def remove_activity_factory(attrs \\ %{}) do
featured_collection_activity(attrs, "Remove")
end
@ -328,7 +329,7 @@ defmodule Pleroma.Factory do
"target" => user.featured_address,
"object" => note.data["object"],
"actor" => note.data["actor"],
"type" => "Add",
"type" => type,
"to" => [Pleroma.Constants.as_public()],
"cc" => [user.follower_address]
}
@ -554,6 +555,54 @@ defmodule Pleroma.Factory do
|> Map.merge(attrs)
end
def undo_activity_factory(attrs \\ %{}) do
like_activity = attrs[:like_activity] || insert(:like_activity)
attrs = Map.drop(attrs, [:like_activity])
data =
%{
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
"type" => "Undo",
"actor" => like_activity.data["actor"],
"to" => like_activity.data["to"],
"object" => like_activity.data["id"],
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
"context" => like_activity.data["context"]
}
%Pleroma.Activity{
data: data,
actor: data["actor"],
recipients: data["to"]
}
|> Map.merge(attrs)
end
def update_activity_factory(attrs \\ %{}) do
user = attrs[:user] || insert(:user, nickname: "testuser")
attrs = Map.drop(attrs, [:user])
user_data =
UserView.render("user.json", %{user: user})
|> Map.merge(%{"name" => "new display name"})
data = %{
"type" => "Update",
"to" => [
user_data["followers"],
"https://www.w3.org/ns/activitystreams#Public"
],
"actor" => user_data["id"],
"object" => user_data
}
%Pleroma.Activity{
actor: user_data["id"],
data: data
}
|> Map.merge(attrs)
end
def oauth_app_factory do
%Pleroma.Web.OAuth.App{
client_name: sequence(:client_name, &"Some client #{&1}"),