Merge branch 'develop' into pleroma-database-config-whitelist
This commit is contained in:
commit
c3b779036d
57 changed files with 1378 additions and 611 deletions
25
.forgejo/issue_template/bug.yaml
Normal file
25
.forgejo/issue_template/bug.yaml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
name: 'Bug report'
|
||||
about: 'Report a bug in Pleroma'
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### Precheck
|
||||
|
||||
* For support use https://git.pleroma.social/pleroma/pleroma-support or [community channels](https://git.pleroma.social/pleroma/pleroma#community-channels).
|
||||
* Please do a quick search to ensure no similar bug has been reported before. If the bug has not been addressed after 2 weeks, it's fine to bump it.
|
||||
* Try to ensure that the bug is actually related to the Pleroma backend. For example, if a bug happens in Pleroma-FE but not in Mastodon-FE or mobile clients, it's likely that the bug should be filed in [Pleroma-FE](https://git.pleroma.social/pleroma/pleroma-fe/issues/new) repository.
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
value: |
|
||||
* Installation type (OTP or From Source):
|
||||
* Pleroma version (could be found in the "Version" tab of settings in Pleroma-FE):
|
||||
* Elixir version (`elixir -v` for from source installations, N/A for OTP):
|
||||
* Operating system:
|
||||
* PostgreSQL version (`psql -V`):
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Bug description
|
||||
13
.forgejo/pull_request_template.md
Normal file
13
.forgejo/pull_request_template.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
### Checklist
|
||||
|
||||
- [ ] Adding a changelog: In the `changelog.d` directory, create a file named `<code>.<type>`.
|
||||
|
||||
<!--
|
||||
`<code>` can be anything, but we recommend using a more or less unique identifier to avoid collisions, such as the branch name.
|
||||
|
||||
`<type>` can be `add`, `change`, `remove`, `fix`, `security` or `skip`. `skip` is only used if there is no user-visible change in the PR (for example, only editing comments in the code). Otherwise, choose a type that corresponds to your change.
|
||||
|
||||
In the file, write the changelog entry. For example, if a PR adds group functionality, we can create a file named `group.add` and write `Add group functionality` in it.
|
||||
|
||||
If one changelog entry is not enough, you may add more. But that might mean you can split it into two PRs. Only use more than one changelog entry if you really need to (for example, when one change in the code fix two different bugs, or when refactoring).
|
||||
-->
|
||||
28
.woodpecker.yml
Normal file
28
.woodpecker.yml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
when:
|
||||
- event:
|
||||
- pull_request
|
||||
|
||||
steps:
|
||||
test:
|
||||
image: elixir:1.15-alpine
|
||||
environment:
|
||||
MIX_ENV: test
|
||||
DB_HOST: postgres
|
||||
DB_PORT: 5432
|
||||
commands:
|
||||
- apk add --no-cache build-base cmake exiftool ffmpeg file-dev git openssl
|
||||
- adduser -D -h /home/testuser testuser
|
||||
- mkdir -p /home/testuser/.mix /home/testuser/.hex
|
||||
- chown -R testuser:testuser . /home/testuser
|
||||
- su testuser -c "HOME=/home/testuser mix local.hex --force"
|
||||
- su testuser -c "HOME=/home/testuser mix local.rebar --force"
|
||||
- su testuser -c "HOME=/home/testuser mix deps.get"
|
||||
- su testuser -c "HOME=/home/testuser mix test"
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:13-alpine
|
||||
environment:
|
||||
POSTGRES_DB: pleroma_test
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<img src="https://git.pleroma.social/pleroma/pleroma/uploads/8cec84f5a084d887339f57deeb8a293e/pleroma-banner-vector-nopad-notext.svg" width="300px" />
|
||||
<img src="https://git.pleroma.social/attachments/06a95f5a-7cac-42ad-8b1d-1483f1739f38" width="300px" />
|
||||
|
||||
## About
|
||||
|
||||
|
|
@ -19,8 +19,6 @@ If you are running Linux (glibc or musl) on x86/arm, the recommended way to inst
|
|||
If your platform is not supported, or you just want to be able to edit the source code easily, you may install Pleroma from source.
|
||||
|
||||
- [Alpine Linux](https://docs-develop.pleroma.social/backend/installation/alpine_linux_en/)
|
||||
- [Arch Linux](https://docs-develop.pleroma.social/backend/installation/arch_linux_en/)
|
||||
- [CentOS 7](https://docs-develop.pleroma.social/backend/installation/centos7_en/)
|
||||
- [Debian-based](https://docs-develop.pleroma.social/backend/installation/debian_based_en/)
|
||||
- [Debian-based (jp)](https://docs-develop.pleroma.social/backend/installation/debian_based_jp/)
|
||||
- [FreeBSD](https://docs-develop.pleroma.social/backend/installation/freebsd_en/)
|
||||
|
|
|
|||
1
changelog.d/assign-users.add
Normal file
1
changelog.d/assign-users.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Allow assigning users to reports
|
||||
1
changelog.d/avatar-description-mastodon-api.change
Normal file
1
changelog.d/avatar-description-mastodon-api.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Move avatar_description and header_description fields to the account object
|
||||
1
changelog.d/hackney-mediaproxy.change
Normal file
1
changelog.d/hackney-mediaproxy.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Use a custom redirect handler to ensure MediaProxy redirects are followed with Hackney
|
||||
1
changelog.d/hackney.change
Normal file
1
changelog.d/hackney.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Update Hackney, the default HTTP client, to the latest release which supports Happy Eyeballs for improved IPv6 federation
|
||||
1
changelog.d/inappropriate-docs.remove
Normal file
1
changelog.d/inappropriate-docs.remove
Normal file
|
|
@ -0,0 +1 @@
|
|||
Docs: Removed outdated, incorrect, unmaintained and inappropriate installation documentation (Arch, NetBSD, NixOS)
|
||||
1
changelog.d/instance-profile-fields.add
Normal file
1
changelog.d/instance-profile-fields.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Add /api/v2/instance profile fields limits info used by Mastodon
|
||||
0
changelog.d/map-side-effects.skip
Normal file
0
changelog.d/map-side-effects.skip
Normal file
0
changelog.d/mix-exs-fix.skip
Normal file
0
changelog.d/mix-exs-fix.skip
Normal file
1
changelog.d/prune-hashtag-follow-3376.fix
Normal file
1
changelog.d/prune-hashtag-follow-3376.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
DB prune: Check if user follows hashtag with no objects before deletion
|
||||
1
changelog.d/relationship-expires-at.change
Normal file
1
changelog.d/relationship-expires-at.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Add mute/block expiry to the relationship object
|
||||
|
|
@ -665,6 +665,7 @@ Status: 404
|
|||
- *optional* `limit`: **integer** the number of records to retrieve
|
||||
- *optional* `page`: **integer** page number
|
||||
- *optional* `page_size`: **integer** number of log entries per page (default is `50`)
|
||||
- *optional* `assigned_account`: **string** assigned account ID
|
||||
- Response:
|
||||
- On failure: 403 Forbidden error `{"error": "error_msg"}` when requested by anonymous or non-admin
|
||||
- On success: JSON, returns a list of reports, where:
|
||||
|
|
@ -749,6 +750,7 @@ Status: 404
|
|||
"url": "https://pleroma.example.org/users/lain",
|
||||
"username": "lain"
|
||||
},
|
||||
"assigned_account": null,
|
||||
"content": "Please delete it",
|
||||
"created_at": "2019-04-29T19:48:15.000Z",
|
||||
"id": "9iJGOv1j8hxuw19bcm",
|
||||
|
|
@ -868,6 +870,37 @@ Status: 404
|
|||
]
|
||||
```
|
||||
|
||||
- Response:
|
||||
- On failure:
|
||||
- 400 Bad Request, JSON:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
`id`, // report id
|
||||
`error` // error message
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
- On success: `204`, empty response
|
||||
|
||||
## `POST /api/v1/pleroma/admin/reports/assign_account`
|
||||
|
||||
### Assign account to one or multiple reports
|
||||
|
||||
- Params:
|
||||
|
||||
```json
|
||||
`reports`: [
|
||||
{
|
||||
`id`, // required, report id
|
||||
`nickname` // account nickname, use null to unassign account
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
- Response:
|
||||
- On failure:
|
||||
- 400 Bad Request, JSON:
|
||||
|
|
|
|||
|
|
@ -127,8 +127,6 @@ Has these additional fields under the `pleroma` object:
|
|||
- `notification_settings`: object, can be absent. See `/api/v1/pleroma/notification_settings` for the parameters/keys returned.
|
||||
- `accepts_chat_messages`: boolean, but can be null if we don't have that information about a user
|
||||
- `favicon`: nullable URL string, Favicon image of the user's instance
|
||||
- `avatar_description`: string, image description for user avatar, defaults to empty string
|
||||
- `header_description`: string, image description for user banner, defaults to empty string
|
||||
|
||||
### Source
|
||||
|
||||
|
|
|
|||
|
|
@ -690,6 +690,7 @@ Audio scrobbling in Pleroma is **deprecated**.
|
|||
* `album`: the album of the media playing [optional]
|
||||
* `artist`: the artist of the media playing [optional]
|
||||
* `length`: the length of the media playing [optional]
|
||||
* `external_link`: a URL referencing the media playing [optional]
|
||||
* Response: the newly created media metadata entity representing the Listen activity
|
||||
|
||||
# Emoji Reactions
|
||||
|
|
|
|||
|
|
@ -1,226 +0,0 @@
|
|||
# Installing on Arch Linux
|
||||
|
||||
{! backend/installation/otp_vs_from_source_source.include !}
|
||||
|
||||
## Installation
|
||||
|
||||
This guide will assume that you have administrative rights, either as root or a user with [sudo permissions](https://wiki.archlinux.org/index.php/Sudo). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su <username> -s $SHELL -c 'command'` instead.
|
||||
|
||||
### Required packages
|
||||
|
||||
* `postgresql`
|
||||
* `elixir`
|
||||
* `git`
|
||||
* `base-devel`
|
||||
* `cmake`
|
||||
* `file`
|
||||
|
||||
#### Optional packages used in this guide
|
||||
|
||||
* `nginx` (preferred, example configs for other reverse proxies can be found in the repo)
|
||||
* `certbot` (or any other ACME client for Let’s Encrypt certificates)
|
||||
* `ImageMagick`
|
||||
* `ffmpeg`
|
||||
* `exiftool`
|
||||
|
||||
### Prepare the system
|
||||
|
||||
* First update the system, if not already done:
|
||||
|
||||
```shell
|
||||
sudo pacman -Syu
|
||||
```
|
||||
|
||||
* Install some of the above mentioned programs:
|
||||
|
||||
```shell
|
||||
sudo pacman -S git base-devel elixir cmake file
|
||||
```
|
||||
|
||||
### Install PostgreSQL
|
||||
|
||||
[Arch Wiki article](https://wiki.archlinux.org/index.php/PostgreSQL)
|
||||
|
||||
* Install the `postgresql` package:
|
||||
|
||||
```shell
|
||||
sudo pacman -S postgresql
|
||||
```
|
||||
|
||||
* Initialize the database cluster:
|
||||
|
||||
```shell
|
||||
sudo -iu postgres initdb -D /var/lib/postgres/data
|
||||
```
|
||||
|
||||
* Start and enable the `postgresql.service`
|
||||
|
||||
```shell
|
||||
sudo systemctl enable --now postgresql.service
|
||||
```
|
||||
|
||||
### Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md))
|
||||
|
||||
```shell
|
||||
sudo pacman -S ffmpeg imagemagick perl-image-exiftool
|
||||
```
|
||||
|
||||
### Install PleromaBE
|
||||
|
||||
* Add a new system user for the Pleroma service:
|
||||
|
||||
```shell
|
||||
sudo useradd -r -s /bin/false -m -d /var/lib/pleroma -U pleroma
|
||||
```
|
||||
|
||||
**Note**: To execute a single command as the Pleroma system user, use `sudo -Hu pleroma command`. You can also switch to a shell by using `sudo -Hu pleroma $SHELL`. If you don’t have and want `sudo` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l pleroma -s $SHELL -c 'command'` and `su -l pleroma -s $SHELL` for starting a shell.
|
||||
|
||||
* Git clone the PleromaBE repository and make the Pleroma user the owner of the directory:
|
||||
|
||||
```shell
|
||||
sudo mkdir -p /opt/pleroma
|
||||
sudo chown -R pleroma:pleroma /opt/pleroma
|
||||
sudo -Hu pleroma git clone -b stable https://git.pleroma.social/pleroma/pleroma /opt/pleroma
|
||||
```
|
||||
|
||||
* Change to the new directory:
|
||||
|
||||
```shell
|
||||
cd /opt/pleroma
|
||||
```
|
||||
|
||||
* Install the dependencies for Pleroma and answer with `yes` if it asks you to install `Hex`:
|
||||
|
||||
```shell
|
||||
sudo -Hu pleroma mix deps.get
|
||||
```
|
||||
|
||||
* Generate the configuration: `sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen`
|
||||
* Answer with `yes` if it asks you to install `rebar3`.
|
||||
* This may take some time, because parts of pleroma get compiled first.
|
||||
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
||||
|
||||
* Check the configuration and if all looks right, rename it, so Pleroma will load it (`prod.secret.exs` for productive instance, `dev.secret.exs` for development instances):
|
||||
|
||||
```shell
|
||||
sudo -Hu pleroma mv config/{generated_config.exs,prod.secret.exs}
|
||||
```
|
||||
|
||||
* The previous command creates also the file `config/setup_db.psql`, with which you can create the database:
|
||||
|
||||
```shell
|
||||
sudo -Hu postgres psql -f config/setup_db.psql
|
||||
```
|
||||
|
||||
* Now run the database migration:
|
||||
|
||||
```shell
|
||||
sudo -Hu pleroma MIX_ENV=prod mix ecto.migrate
|
||||
```
|
||||
|
||||
* Now you can start Pleroma already
|
||||
|
||||
```shell
|
||||
sudo -Hu pleroma MIX_ENV=prod mix phx.server
|
||||
```
|
||||
|
||||
### Finalize installation
|
||||
|
||||
If you want to open your newly installed instance to the world, you should run nginx or some other webserver/proxy in front of Pleroma and you should consider to create a systemd service file for Pleroma.
|
||||
|
||||
#### Nginx
|
||||
|
||||
* Install nginx, if not already done:
|
||||
|
||||
```shell
|
||||
sudo pacman -S nginx
|
||||
```
|
||||
|
||||
* Create directories for available and enabled sites:
|
||||
|
||||
```shell
|
||||
sudo mkdir -p /etc/nginx/sites-{available,enabled}
|
||||
```
|
||||
|
||||
* Append the following line at the end of the `http` block in `/etc/nginx/nginx.conf`:
|
||||
|
||||
```Nginx
|
||||
include sites-enabled/*;
|
||||
```
|
||||
|
||||
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
|
||||
|
||||
```shell
|
||||
sudo pacman -S certbot certbot-nginx
|
||||
```
|
||||
|
||||
and then set it up:
|
||||
|
||||
```shell
|
||||
sudo mkdir -p /var/lib/letsencrypt/
|
||||
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
|
||||
```
|
||||
|
||||
If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again).
|
||||
|
||||
---
|
||||
|
||||
* Copy the example nginx configuration and activate it:
|
||||
|
||||
```shell
|
||||
sudo cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/pleroma.nginx
|
||||
sudo ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled/pleroma.nginx
|
||||
```
|
||||
|
||||
* Before starting nginx edit the configuration and change it to your needs (e.g. change servername, change cert paths)
|
||||
|
||||
* (Strongly recommended) serve media on another domain
|
||||
|
||||
Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors.
|
||||
|
||||
* Enable and start nginx:
|
||||
|
||||
```shell
|
||||
sudo systemctl enable --now nginx.service
|
||||
```
|
||||
|
||||
If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run:
|
||||
|
||||
```shell
|
||||
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --webroot -w /var/lib/letsencrypt/
|
||||
```
|
||||
|
||||
#### Other webserver/proxies
|
||||
|
||||
You can find example configurations for them in `/opt/pleroma/installation/`.
|
||||
|
||||
#### Systemd service
|
||||
|
||||
* Copy example service file
|
||||
|
||||
```shell
|
||||
sudo cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
|
||||
```
|
||||
|
||||
* Edit the service file and make sure that all paths fit your installation
|
||||
* Enable and start `pleroma.service`:
|
||||
|
||||
```shell
|
||||
sudo systemctl enable --now pleroma.service
|
||||
```
|
||||
|
||||
#### Create your first user
|
||||
|
||||
If your instance is up and running, you can create your first user with administrative rights with the following task:
|
||||
|
||||
```shell
|
||||
sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
|
||||
```
|
||||
|
||||
#### Further reading
|
||||
|
||||
{! backend/installation/further_reading.include !}
|
||||
|
||||
## Questions
|
||||
|
||||
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
|
||||
|
|
@ -1,294 +0,0 @@
|
|||
# Installing on NetBSD
|
||||
|
||||
{! backend/installation/generic_dependencies.include !}
|
||||
|
||||
# Installation options
|
||||
|
||||
Currently there are two options available for NetBSD: manual installation (from source) or using experimental package from [pkgsrc-wip](https://github.com/NetBSD/pkgsrc-wip/tree/master/pleroma).
|
||||
|
||||
WIP package can be installed via pkgsrc and can be crosscompiled for easier binary distribution. Source installation most probably will be restricted to a single machine.
|
||||
|
||||
## pkgsrc installation
|
||||
|
||||
WIP package creates Mix.Release (similar to how Docker images are built) but doesn't bundle Erlang runtime, listing it as a dependency instead. This allows for easier and more modular installations, especially on weaker machines. Currently this method also does not support all features of `pleroma_ctl` command (like changing installation type or managing frontends) as NetBSD is not yet a supported binary flavour of Pleroma's CI.
|
||||
|
||||
In any case, you can install it the same way as any other `pkgsrc-wip` package:
|
||||
|
||||
```
|
||||
cd /usr/pkgsrc
|
||||
git clone --depth 1 git://wip.pkgsrc.org/pkgsrc-wip.git wip
|
||||
cp -rf wip/pleroma www
|
||||
cp -rf wip/libvips graphics
|
||||
cd /usr/pkgsrc/www/pleroma
|
||||
bmake && bmake install
|
||||
```
|
||||
|
||||
Use `bmake package` to create a binary package. This can come especially handy if you're targeting embedded or low-power systems and are crosscompiling on a more powerful machine.
|
||||
|
||||
> Note: Elixir has [endianness bug](https://github.com/elixir-lang/elixir/issues/2785) which requires it to be compiled on a machine with the same endianness. In other words, package crosscompiled on amd64 (little endian) won't work on powerpc or sparc machines (big endian). While _in theory™_ nothing catastrophic should happen, one can see that for example regexes won't work properly. Some distributions just strip this warning away, so it doesn't bother the users... anyway, you've been warned.
|
||||
|
||||
## Source installation
|
||||
|
||||
pkgin should have been installed by the NetBSD installer if you selected
|
||||
the right options. If it isn't installed, install it using `pkg_add`.
|
||||
|
||||
Note that `postgresql11-contrib` is needed for the Postgres extensions
|
||||
Pleroma uses.
|
||||
|
||||
> Note: you can use modern versions of PostgreSQL. In this case, just use `postgresql16-contrib` and so on.
|
||||
|
||||
The `mksh` shell is needed to run the Elixir `mix` script.
|
||||
|
||||
`# pkgin install acmesh elixir git-base git-docs mksh nginx postgresql11-server postgresql11-client postgresql11-contrib sudo ffmpeg4 ImageMagick`
|
||||
|
||||
You can also build these packages using pkgsrc:
|
||||
```
|
||||
databases/postgresql11-contrib
|
||||
databases/postgresql11-client
|
||||
databases/postgresql11-server
|
||||
devel/git-base
|
||||
devel/git-docs
|
||||
devel/cmake
|
||||
lang/elixir
|
||||
security/acmesh
|
||||
security/sudo
|
||||
shells/mksh
|
||||
www/nginx
|
||||
```
|
||||
|
||||
Create a user for Pleroma:
|
||||
|
||||
```
|
||||
# groupadd pleroma
|
||||
# useradd -d /home/pleroma -m -g pleroma -s /usr/pkg/bin/mksh pleroma
|
||||
# echo 'export LC_ALL="en_GB.UTF-8"' >> /home/pleroma/.profile
|
||||
# su -l pleroma -c $SHELL
|
||||
```
|
||||
|
||||
Clone the repository:
|
||||
|
||||
```
|
||||
$ cd /home/pleroma
|
||||
$ git clone -b stable https://git.pleroma.social/pleroma/pleroma.git
|
||||
```
|
||||
|
||||
Get deps and compile:
|
||||
|
||||
```
|
||||
$ cd /home/pleroma/pleroma
|
||||
$ export MIX_ENV=prod
|
||||
$ mix deps.get
|
||||
$ mix compile
|
||||
```
|
||||
|
||||
## Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md))
|
||||
|
||||
`# pkgin install ImageMagick ffmpeg4 p5-Image-ExifTool`
|
||||
|
||||
or via pkgsrc:
|
||||
|
||||
```
|
||||
graphics/p5-Image-ExifTool
|
||||
graphics/ImageMagick
|
||||
multimedia/ffmpeg4
|
||||
```
|
||||
|
||||
# Configuration
|
||||
|
||||
## Understanding $PREFIX
|
||||
|
||||
From now on, you may encounter `$PREFIX` variable in the paths. This variable indicates your current local pkgsrc prefix. Usually it's `/usr/pkg` unless you configured it otherwise. Translating to pkgsrc's lingo, it's called `LOCALBASE`, which essentially means the same this. You may want to set it up for your local shell session (this uses `mksh` which should already be installed as one of the required dependencies):
|
||||
|
||||
```
|
||||
$ export PREFIX=$(pkg_info -Q LOCALBASE mksh)
|
||||
$ echo $PREFIX
|
||||
/usr/pkg
|
||||
```
|
||||
|
||||
## Setting up your instance
|
||||
|
||||
Now, you need to configure your instance. During this initial configuration, you will be asked some questions about your server. You will need a domain name at this point; it doesn't have to be deployed, but changing it later will be very cumbersome.
|
||||
|
||||
If you've installed via pkgsrc, `pleroma_ctl` should already be in your `PATH`; if you've installed from source, it's located at `/home/pleroma/pleroma/release/bin/pleroma_ctl`.
|
||||
|
||||
```
|
||||
$ su -l pleroma
|
||||
$ pleroma_ctl instance gen --output $PREFIX/etc/pleroma/config.exs --output-psql /tmp/setup_db.psql
|
||||
```
|
||||
|
||||
During installation, you will be asked about static and upload directories. Don't forget to create them and update permissions:
|
||||
|
||||
```
|
||||
mkdir -p /var/lib/pleroma/uploads
|
||||
chown -R pleroma:pleroma /var/lib/pleroma
|
||||
```
|
||||
|
||||
## Setting up the database
|
||||
|
||||
First, run `# /etc/rc.d/pgsql start`. Then, `$ sudo -Hu pgsql -g pgsql createdb`.
|
||||
|
||||
We can now initialize the database. You'll need to edit generated SQL file from the previous step. It's located at `/tmp/setup_db.psql`.
|
||||
|
||||
Edit this file, and *change the password* to a password of your choice. Make sure it is secure, since
|
||||
it'll be protecting your database. Now initialize the database:
|
||||
|
||||
```
|
||||
$ sudo -Hu pgsql -g pgsql psql -f /tmp/setup_db.psql
|
||||
```
|
||||
|
||||
Postgres allows connections from all users without a password by default. To
|
||||
fix this, edit `$PREFIX/pgsql/data/pg_hba.conf`. Change every `trust` to
|
||||
`password`.
|
||||
|
||||
Once this is done, restart Postgres with `# /etc/rc.d/pgsql restart`.
|
||||
|
||||
Run the database migrations.
|
||||
|
||||
### pkgsrc installation
|
||||
|
||||
```
|
||||
pleroma_ctl migrate
|
||||
```
|
||||
|
||||
### Source installation
|
||||
|
||||
You will need to do this whenever you update with `git pull`:
|
||||
|
||||
```
|
||||
$ cd /home/pleroma/pleroma
|
||||
$ MIX_ENV=prod mix ecto.migrate
|
||||
```
|
||||
|
||||
## Configuring nginx
|
||||
|
||||
Install the example configuration file
|
||||
(`$PREFIX/share/examples/pleroma/pleroma.nginx` or `/home/pleroma/pleroma/installation/pleroma.nginx`) to
|
||||
`$PREFIX/etc/nginx.conf`.
|
||||
|
||||
Note that it will need to be wrapped in a `http {}` block. You should add
|
||||
settings for the nginx daemon outside of the http block, for example:
|
||||
|
||||
```
|
||||
user nginx nginx;
|
||||
error_log /var/log/nginx/error.log;
|
||||
worker_processes 4;
|
||||
|
||||
events {
|
||||
}
|
||||
```
|
||||
|
||||
Edit the defaults:
|
||||
|
||||
* Change `ssl_certificate` and `ssl_trusted_certificate` to
|
||||
`/etc/nginx/tls/fullchain`.
|
||||
* Change `ssl_certificate_key` to `/etc/nginx/tls/key`.
|
||||
* Change `example.tld` to your instance's domain name.
|
||||
|
||||
### (Strongly recommended) serve media on another domain
|
||||
|
||||
Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors.
|
||||
|
||||
## Configuring acme.sh
|
||||
|
||||
We'll be using acme.sh in Stateless Mode for TLS certificate renewal.
|
||||
|
||||
First, get your account fingerprint:
|
||||
|
||||
```
|
||||
$ sudo -Hu nginx -g nginx acme.sh --register-account
|
||||
```
|
||||
|
||||
You need to add the following to your nginx configuration for the server
|
||||
running on port 80:
|
||||
|
||||
```
|
||||
location ~ ^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)$ {
|
||||
default_type text/plain;
|
||||
return 200 "$1.6fXAG9VyG0IahirPEU2ZerUtItW2DHzDzD9wZaEKpqd";
|
||||
}
|
||||
```
|
||||
|
||||
Replace the string after after `$1.` with your fingerprint.
|
||||
|
||||
Start nginx:
|
||||
|
||||
```
|
||||
# /etc/rc.d/nginx start
|
||||
```
|
||||
|
||||
It should now be possible to issue a cert (replace `example.com`
|
||||
with your domain name):
|
||||
|
||||
```
|
||||
$ sudo -Hu nginx -g nginx acme.sh --issue -d example.com --stateless
|
||||
```
|
||||
|
||||
Let's add auto-renewal to `/etc/daily.local`
|
||||
(replace `example.com` with your domain):
|
||||
|
||||
```
|
||||
/usr/pkg/bin/sudo -Hu nginx -g nginx \
|
||||
/usr/pkg/sbin/acme.sh -r \
|
||||
-d example.com \
|
||||
--cert-file /etc/nginx/tls/cert \
|
||||
--key-file /etc/nginx/tls/key \
|
||||
--ca-file /etc/nginx/tls/ca \
|
||||
--fullchain-file /etc/nginx/tls/fullchain \
|
||||
--stateless
|
||||
```
|
||||
|
||||
## Autostart
|
||||
|
||||
For properly functioning instance, you will need pleroma (backend service), nginx (reverse proxy) and postgresql (database) services running. There's no requirement for them to reside on the same machine, but you have to provide autostart for each of them.
|
||||
|
||||
### nginx
|
||||
```
|
||||
# cp $PREFIX/share/examples/rc.d/nginx /etc/rc.d
|
||||
# echo "nginx=YES" >> /etc/rc.conf
|
||||
```
|
||||
|
||||
### postgresql
|
||||
|
||||
```
|
||||
# cp $PREFIX/share/examples/rc.d/pgsql /etc/rc.d
|
||||
# echo "pgsql=YES" >> /etc/rc.conf
|
||||
```
|
||||
|
||||
### pleroma
|
||||
|
||||
First, copy the script (pkgsrc variant)
|
||||
```
|
||||
# cp $PREFIX/share/examples/pleroma/pleroma.rc /etc/rc.d/pleroma
|
||||
```
|
||||
|
||||
or source variant
|
||||
```
|
||||
# cp /home/pleroma/pleroma/installation/netbsd/rc.d/pleroma /etc/rc.d/pleroma
|
||||
# chmod +x /etc/rc.d/pleroma
|
||||
```
|
||||
|
||||
Then, add the following to `/etc/rc.conf`:
|
||||
|
||||
```
|
||||
pleroma=YES
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Run `# /etc/rc.d/pleroma start` to start Pleroma.
|
||||
Restart nginx with `# /etc/rc.d/nginx restart` and you should be up and running.
|
||||
|
||||
Make sure your time is in sync, or other instances will receive your posts with
|
||||
incorrect timestamps. You should have ntpd running.
|
||||
|
||||
## Instances running NetBSD
|
||||
|
||||
* <https://catgirl.science>
|
||||
|
||||
#### Further reading
|
||||
|
||||
{! backend/installation/further_reading.include !}
|
||||
|
||||
## Questions
|
||||
|
||||
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
# Installing on NixOS
|
||||
|
||||
NixOS contains a source build package of pleroma and a NixOS module to install it.
|
||||
For installation add this to your configuration.nix and add a config.exs next to it:
|
||||
```nix
|
||||
services.pleroma = {
|
||||
enable = true;
|
||||
configs = [ (lib.fileContents ./config.exs) ];
|
||||
secretConfigFile = "/var/lib/pleroma/secret.exs";
|
||||
};
|
||||
```
|
||||
|
||||
## Questions
|
||||
The nix community uses matrix for communication: [#nix:nixos.org](https://matrix.to/#/#nix:nixos.org)
|
||||
|
||||
|
|
@ -226,7 +226,12 @@ defmodule Mix.Tasks.Pleroma.Database do
|
|||
DELETE FROM hashtags AS ht
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM hashtags_objects hto
|
||||
WHERE ht.id = hto.hashtag_id)
|
||||
WHERE ht.id = hto.hashtag_id
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM user_follows_hashtag ufh
|
||||
WHERE ht.id = ufh.hashtag_id
|
||||
)
|
||||
"""
|
||||
|> Repo.query()
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ defmodule Mix.Tasks.Pleroma.OpenapiSpec do
|
|||
else
|
||||
{_, errors} ->
|
||||
IO.puts(IO.ANSI.format([:red, :bright, "Spec check failed, errors:"]))
|
||||
Enum.map(errors, &IO.puts/1)
|
||||
Enum.each(errors, &IO.puts/1)
|
||||
|
||||
raise "Spec check failed"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ defmodule Pleroma.Activity.HTML do
|
|||
|
||||
def invalidate_cache_for(activity_id) do
|
||||
keys = get_cache_keys_for(activity_id)
|
||||
Enum.map(keys, &@cachex.del(:scrubber_cache, &1))
|
||||
Enum.each(keys, &@cachex.del(:scrubber_cache, &1))
|
||||
@cachex.del(:scrubber_management_cache, activity_id)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ defmodule Pleroma.Constants do
|
|||
"generator",
|
||||
"rules",
|
||||
"language",
|
||||
"voters"
|
||||
"voters",
|
||||
"assigned_account"
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do
|
|||
@behaviour Pleroma.HTTP.AdapterHelper
|
||||
|
||||
@defaults [
|
||||
follow_redirect: true,
|
||||
force_redirect: true
|
||||
follow_redirect: false,
|
||||
force_redirect: false,
|
||||
with_body: true
|
||||
]
|
||||
|
||||
@spec options(keyword(), URI.t()) :: keyword()
|
||||
|
|
|
|||
|
|
@ -132,11 +132,18 @@ defmodule Pleroma.ModerationLog do
|
|||
end
|
||||
|
||||
def insert_log(%{actor: %User{}, action: action, subject: %Activity{} = subject} = attrs)
|
||||
when action in ["report_note_delete", "report_update", "report_note"] do
|
||||
when action in [
|
||||
"report_note_delete",
|
||||
"report_update",
|
||||
"report_note",
|
||||
"report_unassigned",
|
||||
"report_assigned"
|
||||
] do
|
||||
data =
|
||||
attrs
|
||||
|> prepare_log_data
|
||||
|> Pleroma.Maps.put_if_present("text", attrs[:text])
|
||||
|> Pleroma.Maps.put_if_present("assigned_account", attrs[:assigned_account])
|
||||
|> Map.merge(%{"subject" => report_to_map(subject)})
|
||||
|
||||
insert_log_entry_with_message(%ModerationLog{data: data})
|
||||
|
|
@ -441,6 +448,35 @@ defmodule Pleroma.ModerationLog do
|
|||
" with '#{state}' state"
|
||||
end
|
||||
|
||||
def get_log_entry_message(
|
||||
%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
"action" => "report_assigned",
|
||||
"subject" => %{"id" => subject_id, "type" => "report"},
|
||||
"assigned_account" => assigned_account
|
||||
}
|
||||
} = log
|
||||
) do
|
||||
"@#{actor_nickname} assigned report ##{subject_id}" <>
|
||||
subject_actor_nickname(log, " (on user ", ")") <>
|
||||
" to user #{assigned_account}"
|
||||
end
|
||||
|
||||
def get_log_entry_message(
|
||||
%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
"action" => "report_unassigned",
|
||||
"subject" => %{"id" => subject_id, "type" => "report"}
|
||||
}
|
||||
} = log
|
||||
) do
|
||||
"@#{actor_nickname} unassigned report ##{subject_id}" <>
|
||||
subject_actor_nickname(log, " (on user ", ")") <>
|
||||
" from a user"
|
||||
end
|
||||
|
||||
def get_log_entry_message(
|
||||
%ModerationLog{
|
||||
data: %{
|
||||
|
|
|
|||
|
|
@ -5,6 +5,23 @@
|
|||
defmodule Pleroma.ReverseProxy.Client.Hackney do
|
||||
@behaviour Pleroma.ReverseProxy.Client
|
||||
|
||||
# In-app redirect handler to avoid Hackney redirect bugs:
|
||||
# - https://github.com/benoitc/hackney/issues/527 (relative/protocol-less redirects can crash Hackney)
|
||||
# - https://github.com/benoitc/hackney/issues/273 (redirects not followed when using HTTP proxy)
|
||||
#
|
||||
# Based on a redirect handler from Pleb, slightly modified to work with Hackney:
|
||||
# https://declin.eu/objects/d4f38e62-5429-4614-86d1-e8fc16e6bf33
|
||||
@redirect_statuses [301, 302, 303, 307, 308]
|
||||
defp absolute_redirect_url(original_url, resp_headers) do
|
||||
location =
|
||||
Enum.find(resp_headers, fn {header, _location} ->
|
||||
String.downcase(header) == "location"
|
||||
end)
|
||||
|
||||
URI.merge(original_url, elem(location, 1))
|
||||
|> URI.to_string()
|
||||
end
|
||||
|
||||
@impl true
|
||||
def request(method, url, headers, body, opts \\ []) do
|
||||
opts =
|
||||
|
|
@ -12,7 +29,35 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do
|
|||
path
|
||||
end)
|
||||
|
||||
:hackney.request(method, url, headers, body, opts)
|
||||
if opts[:follow_redirect] != false do
|
||||
{_state, req_opts} = Access.get_and_update(opts, :follow_redirect, fn a -> {a, false} end)
|
||||
res = :hackney.request(method, url, headers, body, req_opts)
|
||||
|
||||
case res do
|
||||
{:ok, code, resp_headers, _client} when code in @redirect_statuses ->
|
||||
:hackney.request(
|
||||
method,
|
||||
absolute_redirect_url(url, resp_headers),
|
||||
headers,
|
||||
body,
|
||||
req_opts
|
||||
)
|
||||
|
||||
{:ok, code, resp_headers} when code in @redirect_statuses ->
|
||||
:hackney.request(
|
||||
method,
|
||||
absolute_redirect_url(url, resp_headers),
|
||||
headers,
|
||||
body,
|
||||
req_opts
|
||||
)
|
||||
|
||||
_ ->
|
||||
res
|
||||
end
|
||||
else
|
||||
:hackney.request(method, url, headers, body, opts)
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ defmodule Pleroma.UserRelationship do
|
|||
do: exists?(unquote(relationship_type), source, target)
|
||||
|
||||
# `def get_block_expire_date/2`, `def get_mute_expire_date/2`,
|
||||
# `def get_reblog_mute_expire_date/2`, `def get_notification_mute_exists?/2`,
|
||||
# `def get_reblog_mute_expire_date/2`, `def get_notification_mute_expire_date/2`,
|
||||
# `def get_inverse_subscription_expire_date/2`, `def get_inverse_endorsement_expire_date/2`
|
||||
def unquote(:"get_#{relationship_type}_expire_date")(source, target),
|
||||
do: get_expire_date(unquote(relationship_type), source, target)
|
||||
|
|
|
|||
|
|
@ -1003,6 +1003,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
defp restrict_state(query, _), do: query
|
||||
|
||||
defp restrict_assigned_account(query, %{assigned_account: assigned_account}) do
|
||||
from(activity in query,
|
||||
where: fragment("?->>'assigned_account' = ?", activity.data, ^assigned_account)
|
||||
)
|
||||
end
|
||||
|
||||
defp restrict_assigned_account(query, _), do: query
|
||||
|
||||
defp restrict_favorited_by(query, %{favorited_by: ap_id}) do
|
||||
from(
|
||||
[_activity, object] in query,
|
||||
|
|
@ -1471,6 +1479,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> restrict_actor(opts)
|
||||
|> restrict_type(opts)
|
||||
|> restrict_state(opts)
|
||||
|> restrict_assigned_account(opts)
|
||||
|> restrict_favorited_by(opts)
|
||||
|> restrict_blocked(restrict_blocked_opts)
|
||||
|> restrict_blockers_visibility(opts)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
|
|||
end
|
||||
|
||||
defp fetch(url) do
|
||||
http_client_opts = Pleroma.Config.get([:media_proxy, :proxy_opts, :http], pool: :media)
|
||||
# This module uses Tesla (Pleroma.HTTP) to fetch the MediaProxy URL.
|
||||
# Redirect following is handled by Tesla middleware, so we must not enable
|
||||
# adapter-level redirect logic (Hackney can crash on relative redirects when proxied).
|
||||
http_client_opts =
|
||||
[:media_proxy, :proxy_opts, :http]
|
||||
|> Pleroma.Config.get(pool: :media)
|
||||
|> Keyword.drop([:follow_redirect, :force_redirect])
|
||||
|
||||
HTTP.get(url, [], http_client_opts)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -863,6 +863,34 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
|
||||
def update_report_state(_, _), do: {:error, "Unsupported state"}
|
||||
|
||||
def assign_report_to_account(%Activity{} = activity, nil = _account) do
|
||||
new_data = Map.delete(activity.data, "assigned_account")
|
||||
|
||||
activity
|
||||
|> Changeset.change(data: new_data)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def assign_report_to_account(%Activity{} = activity, account) do
|
||||
new_data = Map.put(activity.data, "assigned_account", account)
|
||||
|
||||
activity
|
||||
|> Changeset.change(data: new_data)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def assign_report_to_account(activity_ids, account) do
|
||||
activities_num = length(activity_ids)
|
||||
|
||||
from(a in Activity, where: a.id in ^activity_ids)
|
||||
|> update(set: [data: fragment("jsonb_set(data, '{assigned_account}', ?)", ^account)])
|
||||
|> Repo.update_all([])
|
||||
|> case do
|
||||
{^activities_num, _} -> :ok
|
||||
_ -> {:error, activity_ids}
|
||||
end
|
||||
end
|
||||
|
||||
def strip_report_status_data(%Activity{} = activity) do
|
||||
with {:ok, new_data} <- strip_report_status_data(activity.data) do
|
||||
{:ok, %{activity | data: new_data}}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
|
|||
alias Pleroma.Activity
|
||||
alias Pleroma.ModerationLog
|
||||
alias Pleroma.ReportNote
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.AdminAPI
|
||||
alias Pleroma.Web.AdminAPI.Report
|
||||
|
|
@ -24,7 +25,7 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
|
|||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["admin:write:reports"]}
|
||||
when action in [:update, :notes_create, :notes_delete]
|
||||
when action in [:update, :assign_account, :notes_create, :notes_delete]
|
||||
)
|
||||
|
||||
action_fallback(AdminAPI.FallbackController)
|
||||
|
|
@ -79,6 +80,22 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
|
|||
end
|
||||
end
|
||||
|
||||
def assign_account(
|
||||
%{
|
||||
assigns: %{user: admin},
|
||||
private: %{open_api_spex: %{body_params: %{reports: reports}}}
|
||||
} = conn,
|
||||
_
|
||||
) do
|
||||
result = Enum.map(reports, &do_assign_account(&1, admin))
|
||||
|
||||
if Enum.any?(result, &Map.has_key?(&1, :error)) do
|
||||
json_response(conn, :bad_request, result)
|
||||
else
|
||||
json_response(conn, :no_content, "")
|
||||
end
|
||||
end
|
||||
|
||||
def notes_create(
|
||||
%{
|
||||
assigns: %{user: user},
|
||||
|
|
@ -131,4 +148,40 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
|
|||
_ -> json_response(conn, :bad_request, "")
|
||||
end
|
||||
end
|
||||
|
||||
defp do_assign_account(%{assigned_account: nil, id: id}, admin) do
|
||||
with {:ok, activity} <- CommonAPI.assign_report_to_account(id, nil),
|
||||
report <- Activity.get_by_id_with_user_actor(activity.id) do
|
||||
ModerationLog.insert_log(%{
|
||||
action: "report_unassigned",
|
||||
actor: admin,
|
||||
subject: activity,
|
||||
subject_actor: report.user_actor
|
||||
})
|
||||
|
||||
activity
|
||||
else
|
||||
{:error, message} ->
|
||||
%{id: id, error: message}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_assign_account(%{assigned_account: assigned_account, id: id}, admin) do
|
||||
with %User{id: account} = user <- User.get_cached_by_nickname(assigned_account),
|
||||
{:ok, activity} <- CommonAPI.assign_report_to_account(id, account),
|
||||
report <- Activity.get_by_id_with_user_actor(activity.id) do
|
||||
ModerationLog.insert_log(%{
|
||||
action: "report_assigned",
|
||||
actor: admin,
|
||||
subject: activity,
|
||||
subject_actor: report.user_actor,
|
||||
assigned_account: user.nickname
|
||||
})
|
||||
|
||||
activity
|
||||
else
|
||||
{:error, message} ->
|
||||
%{id: id, error: message}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@ defmodule Pleroma.Web.AdminAPI.Report do
|
|||
user = User.get_cached_by_ap_id(actor)
|
||||
account = User.get_cached_by_ap_id(account_ap_id)
|
||||
|
||||
assigned_account =
|
||||
if Map.has_key?(report.data, "assigned_account") do
|
||||
User.get_cached_by_id(report.data["assigned_account"])
|
||||
end
|
||||
|
||||
statuses =
|
||||
status_ap_ids
|
||||
|> Enum.reject(&is_nil(&1))
|
||||
|
|
@ -26,7 +31,13 @@ defmodule Pleroma.Web.AdminAPI.Report do
|
|||
Activity.get_by_ap_id_with_object(act)
|
||||
end)
|
||||
|
||||
%{report: report, user: user, account: account, statuses: statuses}
|
||||
%{
|
||||
report: report,
|
||||
user: user,
|
||||
account: account,
|
||||
statuses: statuses,
|
||||
assigned_account: assigned_account
|
||||
}
|
||||
end
|
||||
|
||||
defp make_fake_activity(act, user) do
|
||||
|
|
|
|||
|
|
@ -26,7 +26,13 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
|
|||
}
|
||||
end
|
||||
|
||||
def render("show.json", %{report: report, user: user, account: account, statuses: statuses}) do
|
||||
def render("show.json", %{
|
||||
report: report,
|
||||
user: user,
|
||||
account: account,
|
||||
statuses: statuses,
|
||||
assigned_account: assigned_account
|
||||
}) do
|
||||
created_at = Utils.to_masto_date(report.data["published"])
|
||||
|
||||
content =
|
||||
|
|
@ -36,6 +42,11 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
|
|||
nil
|
||||
end
|
||||
|
||||
assigned_account =
|
||||
if assigned_account do
|
||||
merge_account_views(assigned_account)
|
||||
end
|
||||
|
||||
%{
|
||||
id: report.id,
|
||||
account: merge_account_views(account),
|
||||
|
|
@ -49,7 +60,8 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
|
|||
}),
|
||||
state: report.data["state"],
|
||||
notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes}),
|
||||
rules: rules(Map.get(report.data, "rules", nil))
|
||||
rules: rules(Map.get(report.data, "rules", nil)),
|
||||
assigned_account: assigned_account
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,12 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
|
|||
:query,
|
||||
%Schema{type: :integer, default: 50},
|
||||
"Number number of log entries per page"
|
||||
),
|
||||
Operation.parameter(
|
||||
:assigned_account,
|
||||
:query,
|
||||
%Schema{type: :string},
|
||||
"Filter by assigned account ID"
|
||||
)
|
||||
| admin_api_params()
|
||||
],
|
||||
|
|
@ -103,6 +109,22 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
|
|||
}
|
||||
end
|
||||
|
||||
def assign_account_operation do
|
||||
%Operation{
|
||||
tags: ["Report management"],
|
||||
summary: "Assign account to specified reports",
|
||||
operationId: "AdminAPI.ReportController.assign_account",
|
||||
security: [%{"oAuth" => ["admin:write:reports"]}],
|
||||
parameters: admin_api_params(),
|
||||
requestBody: request_body("Parameters", assign_account_request(), required: true),
|
||||
responses: %{
|
||||
204 => no_content_response(),
|
||||
400 => Operation.response("Bad Request", "application/json", update_400_response()),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def notes_create_operation do
|
||||
%Operation{
|
||||
tags: ["Report management"],
|
||||
|
|
@ -186,7 +208,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
|
|||
hint: %Schema{type: :string, nullable: true}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
assigned_account:
|
||||
account_admin()
|
||||
|> Map.put(:nullable, true)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
@ -242,6 +267,34 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
|
|||
}
|
||||
end
|
||||
|
||||
defp assign_account_request do
|
||||
%Schema{
|
||||
type: :object,
|
||||
required: [:reports],
|
||||
properties: %{
|
||||
reports: %Schema{
|
||||
type: :array,
|
||||
items: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{allOf: [FlakeID], description: "Required, report ID"},
|
||||
assigned_account: %Schema{
|
||||
type: :string,
|
||||
description: "User nickname",
|
||||
nullable: true
|
||||
}
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"reports" => [
|
||||
%{"id" => "123", "assigned_account" => "pleroma"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp update_400_response do
|
||||
%Schema{
|
||||
type: :array,
|
||||
|
|
|
|||
|
|
@ -342,6 +342,18 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do
|
|||
max_pinned_statuses: %Schema{
|
||||
type: :integer,
|
||||
description: "The maximum number of pinned statuses for each account."
|
||||
},
|
||||
max_profile_fields: %Schema{
|
||||
type: :integer,
|
||||
description: "The maximum number of custom profile fields allowed to be set."
|
||||
},
|
||||
profile_field_name_limit: %Schema{
|
||||
type: :integer,
|
||||
description: "The maximum size of a profile field name, in characters."
|
||||
},
|
||||
profile_field_value_limit: %Schema{
|
||||
type: :integer,
|
||||
description: "The maximum size of a profile field value, in characters."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
|||
acct: %Schema{type: :string},
|
||||
avatar_static: %Schema{type: :string, format: :uri},
|
||||
avatar: %Schema{type: :string, format: :uri},
|
||||
avatar_description: %Schema{type: :string},
|
||||
bot: %Schema{type: :boolean},
|
||||
created_at: %Schema{type: :string, format: "date-time"},
|
||||
display_name: %Schema{type: :string},
|
||||
|
|
@ -31,6 +32,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
|||
following_count: %Schema{type: :integer},
|
||||
header_static: %Schema{type: :string, format: :uri},
|
||||
header: %Schema{type: :string, format: :uri},
|
||||
header_description: %Schema{type: :string},
|
||||
id: FlakeID,
|
||||
locked: %Schema{type: :boolean},
|
||||
note: %Schema{type: :string, format: :html},
|
||||
|
|
@ -111,8 +113,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
|||
nullable: true,
|
||||
description: "Favicon image of the user's instance"
|
||||
},
|
||||
avatar_description: %Schema{type: :string},
|
||||
header_description: %Schema{type: :string}
|
||||
avatar_description: %Schema{type: :string, deprecated: true},
|
||||
header_description: %Schema{type: :string, deprecated: true}
|
||||
}
|
||||
},
|
||||
source: %Schema{
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
|
|||
requested: %Schema{type: :boolean},
|
||||
showing_reblogs: %Schema{type: :boolean},
|
||||
subscribing: %Schema{type: :boolean},
|
||||
notifying: %Schema{type: :boolean}
|
||||
notifying: %Schema{type: :boolean},
|
||||
mute_expires_at: %Schema{type: :string, format: "date-time", nullable: true},
|
||||
block_expires_at: %Schema{type: :string, format: "date-time", nullable: true}
|
||||
},
|
||||
example: %{
|
||||
"blocked_by" => false,
|
||||
|
|
|
|||
|
|
@ -709,6 +709,22 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
def assign_report_to_account(activity_ids, user) when is_list(activity_ids) do
|
||||
case Utils.assign_report_to_account(activity_ids, user) do
|
||||
:ok -> {:ok, activity_ids}
|
||||
_ -> {:error, dgettext("errors", "Could not assign account")}
|
||||
end
|
||||
end
|
||||
|
||||
def assign_report_to_account(activity_id, user) do
|
||||
with %Activity{} = activity <- Activity.get_by_id(activity_id) do
|
||||
Utils.assign_report_to_account(activity, user)
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
_ -> {:error, dgettext("errors", "Could not assign account")}
|
||||
end
|
||||
end
|
||||
|
||||
@spec update_activity_scope(String.t(), map()) :: {:ok, any()} | {:error, any()}
|
||||
def update_activity_scope(activity_id, opts \\ %{}) do
|
||||
with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
|
||||
|
|
|
|||
|
|
@ -96,6 +96,24 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
followed_by = FollowingRelationship.following?(target, reading_user)
|
||||
following = FollowingRelationship.following?(reading_user, target)
|
||||
|
||||
blocking =
|
||||
UserRelationship.exists?(
|
||||
user_relationships,
|
||||
:block,
|
||||
reading_user,
|
||||
target,
|
||||
&User.blocks_user?(&1, &2)
|
||||
)
|
||||
|
||||
muting =
|
||||
UserRelationship.exists?(
|
||||
user_relationships,
|
||||
:mute,
|
||||
reading_user,
|
||||
target,
|
||||
&User.mutes?(&1, &2)
|
||||
)
|
||||
|
||||
requested =
|
||||
cond do
|
||||
following -> false
|
||||
|
|
@ -116,14 +134,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
id: to_string(target.id),
|
||||
following: following,
|
||||
followed_by: followed_by,
|
||||
blocking:
|
||||
UserRelationship.exists?(
|
||||
user_relationships,
|
||||
:block,
|
||||
reading_user,
|
||||
target,
|
||||
&User.blocks_user?(&1, &2)
|
||||
),
|
||||
blocking: blocking,
|
||||
blocked_by:
|
||||
UserRelationship.exists?(
|
||||
user_relationships,
|
||||
|
|
@ -132,14 +143,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
reading_user,
|
||||
&User.blocks_user?(&1, &2)
|
||||
),
|
||||
muting:
|
||||
UserRelationship.exists?(
|
||||
user_relationships,
|
||||
:mute,
|
||||
reading_user,
|
||||
target,
|
||||
&User.mutes?(&1, &2)
|
||||
),
|
||||
block_expires_at: nil,
|
||||
muting: muting,
|
||||
muting_notifications:
|
||||
UserRelationship.exists?(
|
||||
user_relationships,
|
||||
|
|
@ -148,6 +153,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
target,
|
||||
&User.muted_notifications?(&1, &2)
|
||||
),
|
||||
mute_expires_at: nil,
|
||||
subscribing: subscribing,
|
||||
notifying: subscribing,
|
||||
requested: requested,
|
||||
|
|
@ -174,6 +180,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
&User.endorses?(&1, &2)
|
||||
)
|
||||
}
|
||||
|> maybe_put_mute_expires_at(target, reading_user, %{mutes: muting})
|
||||
|> maybe_put_block_expires_at(target, reading_user, %{blocks: blocking})
|
||||
end
|
||||
|
||||
def render("relationships.json", %{user: user, targets: targets} = opts) do
|
||||
|
|
@ -292,8 +300,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
note: user.bio,
|
||||
url: user.uri || user.ap_id,
|
||||
avatar: avatar,
|
||||
avatar_description: avatar_description,
|
||||
avatar_static: avatar_static,
|
||||
header: header,
|
||||
header_description: header_description,
|
||||
header_static: header_static,
|
||||
emojis: emojis,
|
||||
fields: user.fields,
|
||||
|
|
@ -343,8 +353,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
|> maybe_put_unread_conversation_count(user, opts[:for])
|
||||
|> maybe_put_unread_notification_count(user, opts[:for])
|
||||
|> maybe_put_email_address(user, opts[:for])
|
||||
|> maybe_put_mute_expires_at(user, opts[:for], opts)
|
||||
|> maybe_put_block_expires_at(user, opts[:for], opts)
|
||||
|> maybe_put_mute_expires_at(user, opts[:for], opts, relationship)
|
||||
|> maybe_put_block_expires_at(user, opts[:for], opts, relationship)
|
||||
|> maybe_show_birthday(user, opts[:for])
|
||||
end
|
||||
|
||||
|
|
@ -472,25 +482,47 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
|
||||
defp maybe_put_email_address(data, _, _), do: data
|
||||
|
||||
defp maybe_put_mute_expires_at(data, %User{} = user, target, %{mutes: true}) do
|
||||
defp maybe_put_mute_expires_at(data, target, user, opts, relationship \\ nil)
|
||||
|
||||
defp maybe_put_mute_expires_at(data, _target, _user, %{mutes: true}, %{
|
||||
mute_expires_at: mute_expires_at
|
||||
}) do
|
||||
Map.put(data, :mute_expires_at, mute_expires_at)
|
||||
end
|
||||
|
||||
defp maybe_put_mute_expires_at(data, %User{} = target, user, %{mutes: true}, _relationship) do
|
||||
Map.put(
|
||||
data,
|
||||
:mute_expires_at,
|
||||
UserRelationship.get_mute_expire_date(target, user)
|
||||
UserRelationship.get_mute_expire_date(user, target)
|
||||
)
|
||||
end
|
||||
|
||||
defp maybe_put_mute_expires_at(data, _, _, _), do: data
|
||||
defp maybe_put_mute_expires_at(data, _, _, _, _), do: data
|
||||
|
||||
defp maybe_put_block_expires_at(data, %User{} = user, target, %{blocks: true}) do
|
||||
defp maybe_put_block_expires_at(data, target, user, opts, relationship \\ nil)
|
||||
|
||||
defp maybe_put_block_expires_at(data, _target, _user, %{blocks: true}, %{
|
||||
block_expires_at: block_expires_at
|
||||
}) do
|
||||
Map.put(data, :block_expires_at, block_expires_at)
|
||||
end
|
||||
|
||||
defp maybe_put_block_expires_at(
|
||||
data,
|
||||
%User{} = target,
|
||||
%User{} = user,
|
||||
%{blocks: true},
|
||||
_relationship
|
||||
) do
|
||||
Map.put(
|
||||
data,
|
||||
:block_expires_at,
|
||||
UserRelationship.get_block_expire_date(target, user)
|
||||
UserRelationship.get_block_expire_date(user, target)
|
||||
)
|
||||
end
|
||||
|
||||
defp maybe_put_block_expires_at(data, _, _, _), do: data
|
||||
defp maybe_put_block_expires_at(data, _, _, _, _), do: data
|
||||
|
||||
defp maybe_show_birthday(data, %User{id: user_id} = user, %User{id: user_id}) do
|
||||
data
|
||||
|
|
|
|||
|
|
@ -303,6 +303,15 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
|||
defp configuration2 do
|
||||
configuration()
|
||||
|> put_in([:accounts, :max_pinned_statuses], Config.get([:instance, :max_pinned_statuses], 0))
|
||||
|> put_in([:accounts, :max_profile_fields], Config.get([:instance, :max_account_fields]))
|
||||
|> put_in(
|
||||
[:accounts, :profile_field_name_limit],
|
||||
Config.get([:instance, :account_field_name_length])
|
||||
)
|
||||
|> put_in(
|
||||
[:accounts, :profile_field_value_limit],
|
||||
Config.get([:instance, :account_field_value_length])
|
||||
)
|
||||
|> put_in([:statuses, :characters_reserved_per_url], 0)
|
||||
|> Map.merge(%{
|
||||
urls: %{
|
||||
|
|
|
|||
|
|
@ -395,6 +395,7 @@ defmodule Pleroma.Web.Router do
|
|||
get("/reports", ReportController, :index)
|
||||
get("/reports/:id", ReportController, :show)
|
||||
patch("/reports", ReportController, :update)
|
||||
post("/reports/assign_account", ReportController, :assign_account)
|
||||
post("/reports/:id/notes", ReportController, :notes_create)
|
||||
delete("/reports/:report_id/notes/:id", ReportController, :notes_delete)
|
||||
end
|
||||
|
|
|
|||
4
mix.exs
4
mix.exs
|
|
@ -154,7 +154,7 @@ defmodule Pleroma.Mixfile do
|
|||
{:gun, "~> 2.2"},
|
||||
{:finch, "~> 0.15"},
|
||||
{:jason, "~> 1.2"},
|
||||
{:mogrify, "~> 0.9.0", override: "true"},
|
||||
{:mogrify, "~> 0.9.0", override: true},
|
||||
{:ex_aws, "~> 2.1.6"},
|
||||
{:ex_aws_s3, "~> 2.0"},
|
||||
{:sweet_xml, "~> 0.7.2"},
|
||||
|
|
@ -211,7 +211,7 @@ defmodule Pleroma.Mixfile do
|
|||
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
|
||||
{:mock, "~> 0.3.5", only: :test},
|
||||
{:covertool, "~> 2.0", only: :test},
|
||||
{:hackney, "~> 1.18.0", override: true},
|
||||
{:hackney, "~> 1.25.0", override: true},
|
||||
{:mox, "~> 1.0", only: :test},
|
||||
{:websockex, "~> 0.4.3", only: :test},
|
||||
{:benchee, "~> 1.0", only: :benchmark},
|
||||
|
|
|
|||
4
mix.lock
4
mix.lock
|
|
@ -13,7 +13,7 @@
|
|||
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e7b7cc34cc16b383461b966484c297e4ec9aeef6", [ref: "e7b7cc34cc16b383461b966484c297e4ec9aeef6"]},
|
||||
"castore": {:hex, :castore, "1.0.15", "8aa930c890fe18b6fe0a0cff27b27d0d4d231867897bd23ea772dee561f032a3", [:mix], [], "hexpm", "96ce4c69d7d5d7a0761420ef743e2f4096253931a3ba69e5ff8ef1844fe446d3"},
|
||||
"cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"},
|
||||
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
|
||||
"certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"},
|
||||
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
|
||||
"comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"},
|
||||
"concurrent_limiter": {:hex, :concurrent_limiter, "0.1.1", "43ae1dc23edda1ab03dd66febc739c4ff710d047bb4d735754909f9a474ae01c", [:mix], [{:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "53968ff238c0fbb4d7ed76ddb1af0be6f3b2f77909f6796e249e737c505a16eb"},
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
"gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
|
||||
"gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"},
|
||||
"gun": {:hex, :gun, "2.2.0", "b8f6b7d417e277d4c2b0dc3c07dfdf892447b087f1cc1caff9c0f556b884e33d", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "76022700c64287feb4df93a1795cff6741b83fb37415c40c34c38d2a4645261a"},
|
||||
"hackney": {:hex, :hackney, "1.18.2", "d7ff544ddae5e1cb49e9cf7fa4e356d7f41b283989a1c304bfc47a8cc1cf966f", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "af94d5c9f97857db257090a4a10e5426ecb6f4918aa5cc666798566ae14b65fd"},
|
||||
"hackney": {:hex, :hackney, "1.25.0", "390e9b83f31e5b325b9f43b76e1a785cbdb69b5b6cd4e079aa67835ded046867", [:rebar3], [{:certifi, "~> 2.15.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.4", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "7209bfd75fd1f42467211ff8f59ea74d6f2a9e81cbcee95a56711ee79fd6b1d4"},
|
||||
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
|
||||
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
|
||||
"http_signatures": {:hex, :http_signatures, "0.1.2", "ed1cc7043abcf5bb4f30d68fb7bad9d618ec1a45c4ff6c023664e78b67d9c406", [:mix], [], "hexpm", "f08aa9ac121829dae109d608d83c84b940ef2f183ae50f2dd1e9a8bc619d8be7"},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
defmodule Pleroma.Repo.Migrations.AddActivityAssignedAccountIndex do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create_if_not_exists(
|
||||
index(:activities, ["(data->>'assigned_account')"],
|
||||
name: :activities_assigned_account_index
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
18
test/fixtures/server.pem
vendored
Normal file
18
test/fixtures/server.pem
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICpDCCAYwCCQC0vCQAnSoGdzANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls
|
||||
b2NhbGhvc3QwHhcNMjYwMTE2MTY1ODE5WhcNMzYwMTE0MTY1ODE5WjAUMRIwEAYD
|
||||
VQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCq
|
||||
dZ4O2upZqwIo1eK5KrW1IIsjkfsFK8hE7Llh+4axcesiUKot0ib1CUhRSYiL1DLO
|
||||
CIYQOw8IKQDVSC4JWAX9SsnX4W8dwexMQuSQG7/IKX2auC1bNNySFvoqM6Gq3GL9
|
||||
MqBFonZGXDPZu8fmxsI/2p9+2GK13F+HXgoLlXSCoO3XELJaBmjv29tgxxWRxCiH
|
||||
m4u0briSxgUEx+CctpKPvGDmLaoIOIhjtuoG6OjkeWUOp6jDcteazO23VxPyF5cS
|
||||
NbRJgm8AckrTQ6wbWSnhyqF8rPEsIc0ZAlUdDEs5fL3sjugc566FvE+GOkZIEyDD
|
||||
tgWbc4Ne+Kp/nnt6oVxpAgMBAAEwDQYJKoZIhvcNAQELBQADggEBADv+J1DTok8V
|
||||
MKVKo0hsRnHTeJQ2+EIgOspuYlEzez3PysOZH6diAQxO2lzuo9LKxP3hnmw17XO/
|
||||
P2oCzYyb9/P58VY/gr4UDIfuhgcE0cVfdsRhVId/I2FW6VP2f5q1TGbDUxSsVIlG
|
||||
6hufn1aLBu90LtEbDkHqbnD05yYPwdqzWg4TrOXbX+jBhQrXJJdB3W7KTgozjRQw
|
||||
F7+/2IyXoxXuxcwQBQlYhUbvGlsFqFpP/6cz2al5i5pNUkiNaSYwlRmuwa7zoTft
|
||||
tHf57dhfXIpXET2BaJM6DSjDOOG/QleRXkvkTI5J21q+Bo+XnOzo19p4cZKJpTFC
|
||||
SNgrftyNh3k=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
|
|
@ -8,6 +8,7 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do
|
|||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Bookmark
|
||||
alias Pleroma.Hashtag
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
|
@ -550,6 +551,39 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do
|
|||
|
||||
assert length(activities) == 3
|
||||
end
|
||||
|
||||
test "it prunes hashtags with no objects associated", %{old_insert_date: old_insert_date} do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, hashtag_post_activity} =
|
||||
CommonAPI.post(user, %{status: "morning #cofe", local: true})
|
||||
|
||||
hashtag_post_object = Object.normalize(hashtag_post_activity)
|
||||
|
||||
{:ok, hashtag_post2_activity} =
|
||||
CommonAPI.post(user, %{status: "morning #cawfee", local: true})
|
||||
|
||||
hashtag_post2_object = Object.normalize(hashtag_post2_activity)
|
||||
|
||||
hashtag_post_object
|
||||
|> Ecto.Changeset.change(%{updated_at: old_insert_date})
|
||||
|> Repo.update!()
|
||||
|
||||
hashtag_post2_object
|
||||
|> Ecto.Changeset.change(%{updated_at: old_insert_date})
|
||||
|> Repo.update!()
|
||||
|
||||
# Test whether hashtags with follow relationships are kept
|
||||
User.follow_hashtag(user, Hashtag.get_by_name("cofe"))
|
||||
|
||||
assert length(Repo.all(Hashtag)) == 2
|
||||
assert length(Repo.all(Object)) == 2
|
||||
|
||||
Mix.Tasks.Pleroma.Database.run(["prune_objects"])
|
||||
assert length(Repo.all(Hashtag)) == 1
|
||||
assert length(Repo.all(Object)) == 0
|
||||
assert Repo.one(Hashtag) |> Map.fetch!(:name) == "cofe"
|
||||
end
|
||||
end
|
||||
|
||||
describe "running update_users_following_followers_counts" do
|
||||
|
|
|
|||
|
|
@ -16,6 +16,14 @@ defmodule Pleroma.HTTP.AdapterHelper.HackneyTest do
|
|||
describe "options/2" do
|
||||
setup do: clear_config([:http, :adapter], a: 1, b: 2)
|
||||
|
||||
test "uses redirect-safe defaults", %{uri: uri} do
|
||||
opts = Hackney.options([], uri)
|
||||
|
||||
assert opts[:follow_redirect] == false
|
||||
assert opts[:force_redirect] == false
|
||||
assert opts[:with_body] == true
|
||||
end
|
||||
|
||||
test "add proxy and opts from config", %{uri: uri} do
|
||||
opts = Hackney.options([proxy: "localhost:8123"], uri)
|
||||
|
||||
|
|
|
|||
355
test/pleroma/http/hackney_follow_redirect_regression_test.exs
Normal file
355
test/pleroma/http/hackney_follow_redirect_regression_test.exs
Normal file
|
|
@ -0,0 +1,355 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.HTTP.HackneyFollowRedirectRegressionTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
setup do
|
||||
{:ok, _} = Application.ensure_all_started(:hackney)
|
||||
|
||||
{:ok, tls_server} = start_tls_redirect_server()
|
||||
{:ok, proxy} = start_connect_proxy()
|
||||
|
||||
on_exit(fn ->
|
||||
stop_connect_proxy(proxy)
|
||||
stop_tls_redirect_server(tls_server)
|
||||
end)
|
||||
|
||||
{:ok, tls_server: tls_server, proxy: proxy}
|
||||
end
|
||||
|
||||
test "hackney follow_redirect crashes behind CONNECT proxy on relative redirects", %{
|
||||
tls_server: tls_server,
|
||||
proxy: proxy
|
||||
} do
|
||||
url = "#{tls_server.base_url}/redirect"
|
||||
|
||||
opts = [
|
||||
pool: :media,
|
||||
proxy: proxy.proxy_url,
|
||||
insecure: true,
|
||||
connect_timeout: 1_000,
|
||||
recv_timeout: 1_000,
|
||||
follow_redirect: true,
|
||||
force_redirect: true
|
||||
]
|
||||
|
||||
{pid, ref} = spawn_monitor(fn -> :hackney.request(:get, url, [], <<>>, opts) end)
|
||||
|
||||
assert_receive {:DOWN, ^ref, :process, ^pid, reason}, 5_000
|
||||
|
||||
assert match?({%FunctionClauseError{}, _}, reason) or match?(%FunctionClauseError{}, reason) or
|
||||
match?({:function_clause, _}, reason)
|
||||
end
|
||||
|
||||
test "redirects work via proxy when hackney follow_redirect is disabled", %{
|
||||
tls_server: tls_server,
|
||||
proxy: proxy
|
||||
} do
|
||||
url = "#{tls_server.base_url}/redirect"
|
||||
|
||||
adapter_opts = [
|
||||
pool: :media,
|
||||
proxy: proxy.proxy_url,
|
||||
insecure: true,
|
||||
connect_timeout: 1_000,
|
||||
recv_timeout: 1_000,
|
||||
follow_redirect: false,
|
||||
force_redirect: false,
|
||||
with_body: true
|
||||
]
|
||||
|
||||
client = Tesla.client([Tesla.Middleware.FollowRedirects], Tesla.Adapter.Hackney)
|
||||
|
||||
assert {:ok, %Tesla.Env{status: 200, body: "ok"}} =
|
||||
Tesla.request(client, method: :get, url: url, opts: [adapter: adapter_opts])
|
||||
end
|
||||
|
||||
test "reverse proxy hackney client follows redirects via proxy without crashing", %{
|
||||
tls_server: tls_server,
|
||||
proxy: proxy
|
||||
} do
|
||||
url = "#{tls_server.base_url}/redirect"
|
||||
|
||||
opts = [
|
||||
pool: :media,
|
||||
proxy: proxy.proxy_url,
|
||||
insecure: true,
|
||||
connect_timeout: 1_000,
|
||||
recv_timeout: 1_000,
|
||||
follow_redirect: true
|
||||
]
|
||||
|
||||
assert {:ok, 200, _headers, ref} =
|
||||
Pleroma.ReverseProxy.Client.Hackney.request(:get, url, [], "", opts)
|
||||
|
||||
assert collect_body(ref) == "ok"
|
||||
Pleroma.ReverseProxy.Client.Hackney.close(ref)
|
||||
end
|
||||
|
||||
defp collect_body(ref, acc \\ "") do
|
||||
case Pleroma.ReverseProxy.Client.Hackney.stream_body(ref) do
|
||||
:done -> acc
|
||||
{:ok, data, _ref} -> collect_body(ref, acc <> data)
|
||||
{:error, error} -> flunk("stream_body failed: #{inspect(error)}")
|
||||
end
|
||||
end
|
||||
|
||||
defp start_tls_redirect_server do
|
||||
certfile = Path.expand("../../fixtures/server.pem", __DIR__)
|
||||
keyfile = Path.expand("../../fixtures/private_key.pem", __DIR__)
|
||||
|
||||
{:ok, listener} =
|
||||
:ssl.listen(0, [
|
||||
:binary,
|
||||
certfile: certfile,
|
||||
keyfile: keyfile,
|
||||
reuseaddr: true,
|
||||
active: false,
|
||||
packet: :raw,
|
||||
ip: {127, 0, 0, 1}
|
||||
])
|
||||
|
||||
{:ok, {{127, 0, 0, 1}, port}} = :ssl.sockname(listener)
|
||||
|
||||
{:ok, acceptor} =
|
||||
Task.start_link(fn ->
|
||||
accept_tls_loop(listener)
|
||||
end)
|
||||
|
||||
{:ok, %{listener: listener, acceptor: acceptor, base_url: "https://127.0.0.1:#{port}"}}
|
||||
end
|
||||
|
||||
defp stop_tls_redirect_server(%{listener: listener, acceptor: acceptor}) do
|
||||
:ok = :ssl.close(listener)
|
||||
|
||||
if Process.alive?(acceptor) do
|
||||
Process.exit(acceptor, :normal)
|
||||
end
|
||||
end
|
||||
|
||||
defp accept_tls_loop(listener) do
|
||||
case :ssl.transport_accept(listener) do
|
||||
{:ok, socket} ->
|
||||
_ = Task.start(fn -> serve_tls(socket) end)
|
||||
accept_tls_loop(listener)
|
||||
|
||||
{:error, :closed} ->
|
||||
:ok
|
||||
|
||||
{:error, _reason} ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp serve_tls(tcp_socket) do
|
||||
with {:ok, ssl_socket} <- :ssl.handshake(tcp_socket, 2_000),
|
||||
{:ok, data} <- recv_ssl_headers(ssl_socket),
|
||||
{:ok, path} <- parse_path(data) do
|
||||
case path do
|
||||
"/redirect" ->
|
||||
send_ssl_response(ssl_socket, 302, "Found", [{"Location", "/final"}], "")
|
||||
|
||||
"/final" ->
|
||||
send_ssl_response(ssl_socket, 200, "OK", [], "ok")
|
||||
|
||||
_ ->
|
||||
send_ssl_response(ssl_socket, 404, "Not Found", [], "not found")
|
||||
end
|
||||
|
||||
:ssl.close(ssl_socket)
|
||||
else
|
||||
_ ->
|
||||
_ = :gen_tcp.close(tcp_socket)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp recv_ssl_headers(socket, acc \\ <<>>) do
|
||||
case :ssl.recv(socket, 0, 1_000) do
|
||||
{:ok, data} ->
|
||||
acc = acc <> data
|
||||
|
||||
if :binary.match(acc, "\r\n\r\n") != :nomatch do
|
||||
{:ok, acc}
|
||||
else
|
||||
if byte_size(acc) > 8_192 do
|
||||
{:error, :too_large}
|
||||
else
|
||||
recv_ssl_headers(socket, acc)
|
||||
end
|
||||
end
|
||||
|
||||
{:error, _} = error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
defp send_ssl_response(socket, status, reason, headers, body) do
|
||||
base_headers =
|
||||
[
|
||||
{"Content-Length", Integer.to_string(byte_size(body))},
|
||||
{"Connection", "close"}
|
||||
] ++ headers
|
||||
|
||||
iodata =
|
||||
[
|
||||
"HTTP/1.1 ",
|
||||
Integer.to_string(status),
|
||||
" ",
|
||||
reason,
|
||||
"\r\n",
|
||||
Enum.map(base_headers, fn {k, v} -> [k, ": ", v, "\r\n"] end),
|
||||
"\r\n",
|
||||
body
|
||||
]
|
||||
|
||||
:ssl.send(socket, iodata)
|
||||
end
|
||||
|
||||
defp start_connect_proxy do
|
||||
{:ok, listener} =
|
||||
:gen_tcp.listen(0, [
|
||||
:binary,
|
||||
active: false,
|
||||
packet: :raw,
|
||||
reuseaddr: true,
|
||||
ip: {127, 0, 0, 1}
|
||||
])
|
||||
|
||||
{:ok, {{127, 0, 0, 1}, port}} = :inet.sockname(listener)
|
||||
|
||||
{:ok, acceptor} =
|
||||
Task.start_link(fn ->
|
||||
accept_proxy_loop(listener)
|
||||
end)
|
||||
|
||||
{:ok, %{listener: listener, acceptor: acceptor, proxy_url: "127.0.0.1:#{port}"}}
|
||||
end
|
||||
|
||||
defp stop_connect_proxy(%{listener: listener, acceptor: acceptor}) do
|
||||
:ok = :gen_tcp.close(listener)
|
||||
|
||||
if Process.alive?(acceptor) do
|
||||
Process.exit(acceptor, :normal)
|
||||
end
|
||||
end
|
||||
|
||||
defp accept_proxy_loop(listener) do
|
||||
case :gen_tcp.accept(listener) do
|
||||
{:ok, socket} ->
|
||||
_ = Task.start(fn -> serve_proxy(socket) end)
|
||||
accept_proxy_loop(listener)
|
||||
|
||||
{:error, :closed} ->
|
||||
:ok
|
||||
|
||||
{:error, _reason} ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp serve_proxy(client_socket) do
|
||||
with {:ok, {headers, rest}} <- recv_tcp_headers(client_socket),
|
||||
{:ok, {host, port}} <- parse_connect(headers),
|
||||
{:ok, upstream_socket} <- connect_upstream(host, port) do
|
||||
:gen_tcp.send(client_socket, "HTTP/1.1 200 Connection established\r\n\r\n")
|
||||
|
||||
if rest != <<>> do
|
||||
:gen_tcp.send(upstream_socket, rest)
|
||||
end
|
||||
|
||||
tunnel(client_socket, upstream_socket)
|
||||
else
|
||||
_ ->
|
||||
:gen_tcp.close(client_socket)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp tunnel(client_socket, upstream_socket) do
|
||||
parent = self()
|
||||
_ = spawn_link(fn -> forward(client_socket, upstream_socket, parent) end)
|
||||
_ = spawn_link(fn -> forward(upstream_socket, client_socket, parent) end)
|
||||
|
||||
receive do
|
||||
:tunnel_closed -> :ok
|
||||
after
|
||||
10_000 -> :ok
|
||||
end
|
||||
|
||||
:gen_tcp.close(client_socket)
|
||||
:gen_tcp.close(upstream_socket)
|
||||
end
|
||||
|
||||
defp forward(from_socket, to_socket, parent) do
|
||||
case :gen_tcp.recv(from_socket, 0, 10_000) do
|
||||
{:ok, data} ->
|
||||
_ = :gen_tcp.send(to_socket, data)
|
||||
forward(from_socket, to_socket, parent)
|
||||
|
||||
{:error, _reason} ->
|
||||
send(parent, :tunnel_closed)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp recv_tcp_headers(socket, acc \\ <<>>) do
|
||||
case :gen_tcp.recv(socket, 0, 1_000) do
|
||||
{:ok, data} ->
|
||||
acc = acc <> data
|
||||
|
||||
case :binary.match(acc, "\r\n\r\n") do
|
||||
:nomatch ->
|
||||
if byte_size(acc) > 8_192 do
|
||||
{:error, :too_large}
|
||||
else
|
||||
recv_tcp_headers(socket, acc)
|
||||
end
|
||||
|
||||
{idx, _len} ->
|
||||
split_at = idx + 4
|
||||
<<headers::binary-size(split_at), rest::binary>> = acc
|
||||
{:ok, {headers, rest}}
|
||||
end
|
||||
|
||||
{:error, _} = error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_connect(data) do
|
||||
with [request_line | _] <- String.split(data, "\r\n", trim: true),
|
||||
["CONNECT", hostport | _] <- String.split(request_line, " ", parts: 3),
|
||||
[host, port_str] <- String.split(hostport, ":", parts: 2),
|
||||
{port, ""} <- Integer.parse(port_str) do
|
||||
{:ok, {host, port}}
|
||||
else
|
||||
_ -> {:error, :invalid_connect}
|
||||
end
|
||||
end
|
||||
|
||||
defp connect_upstream(host, port) do
|
||||
address =
|
||||
case :inet.parse_address(String.to_charlist(host)) do
|
||||
{:ok, ip} -> ip
|
||||
{:error, _} -> String.to_charlist(host)
|
||||
end
|
||||
|
||||
:gen_tcp.connect(address, port, [:binary, active: false, packet: :raw], 1_000)
|
||||
end
|
||||
|
||||
defp parse_path(data) do
|
||||
case String.split(data, "\r\n", parts: 2) do
|
||||
[request_line | _] ->
|
||||
case String.split(request_line, " ") do
|
||||
[_method, path, _protocol] -> {:ok, path}
|
||||
_ -> {:error, :invalid_request}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, :invalid_request}
|
||||
end
|
||||
end
|
||||
end
|
||||
151
test/pleroma/http/hackney_redirect_regression_test.exs
Normal file
151
test/pleroma/http/hackney_redirect_regression_test.exs
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.HTTP.HackneyRedirectRegressionTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
alias Pleroma.HTTP.AdapterHelper.Hackney, as: HackneyAdapterHelper
|
||||
|
||||
setup do
|
||||
{:ok, _} = Application.ensure_all_started(:hackney)
|
||||
|
||||
{:ok, server} = start_server()
|
||||
on_exit(fn -> stop_server(server) end)
|
||||
|
||||
{:ok, server: server}
|
||||
end
|
||||
|
||||
test "pooled redirects work with follow_redirect disabled", %{server: server} do
|
||||
url = "#{server.base_url}/redirect"
|
||||
uri = URI.parse(url)
|
||||
|
||||
adapter_opts =
|
||||
HackneyAdapterHelper.options(
|
||||
[pool: :media, follow_redirect: false, no_proxy_env: true],
|
||||
uri
|
||||
)
|
||||
|
||||
client = Tesla.client([Tesla.Middleware.FollowRedirects], Tesla.Adapter.Hackney)
|
||||
|
||||
assert {:ok, %Tesla.Env{status: 200, body: "ok"}} =
|
||||
Tesla.request(client, method: :get, url: url, opts: [adapter: adapter_opts])
|
||||
end
|
||||
|
||||
defp start_server do
|
||||
{:ok, listener} =
|
||||
:gen_tcp.listen(0, [
|
||||
:binary,
|
||||
active: false,
|
||||
packet: :raw,
|
||||
reuseaddr: true,
|
||||
ip: {127, 0, 0, 1}
|
||||
])
|
||||
|
||||
{:ok, {{127, 0, 0, 1}, port}} = :inet.sockname(listener)
|
||||
|
||||
{:ok, acceptor} =
|
||||
Task.start_link(fn ->
|
||||
accept_loop(listener)
|
||||
end)
|
||||
|
||||
{:ok, %{listener: listener, acceptor: acceptor, base_url: "http://127.0.0.1:#{port}"}}
|
||||
end
|
||||
|
||||
defp stop_server(%{listener: listener, acceptor: acceptor}) do
|
||||
:ok = :gen_tcp.close(listener)
|
||||
|
||||
if Process.alive?(acceptor) do
|
||||
Process.exit(acceptor, :normal)
|
||||
end
|
||||
end
|
||||
|
||||
defp accept_loop(listener) do
|
||||
case :gen_tcp.accept(listener) do
|
||||
{:ok, socket} ->
|
||||
serve(socket)
|
||||
accept_loop(listener)
|
||||
|
||||
{:error, :closed} ->
|
||||
:ok
|
||||
|
||||
{:error, _reason} ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp serve(socket) do
|
||||
with {:ok, data} <- recv_headers(socket),
|
||||
{:ok, path} <- parse_path(data) do
|
||||
case path do
|
||||
"/redirect" ->
|
||||
send_response(socket, 302, "Found", [{"Location", "/final"}], "")
|
||||
|
||||
"/final" ->
|
||||
send_response(socket, 200, "OK", [], "ok")
|
||||
|
||||
_ ->
|
||||
send_response(socket, 404, "Not Found", [], "not found")
|
||||
end
|
||||
else
|
||||
_ -> :ok
|
||||
end
|
||||
|
||||
:gen_tcp.close(socket)
|
||||
end
|
||||
|
||||
defp recv_headers(socket, acc \\ <<>>) do
|
||||
case :gen_tcp.recv(socket, 0, 1_000) do
|
||||
{:ok, data} ->
|
||||
acc = acc <> data
|
||||
|
||||
if :binary.match(acc, "\r\n\r\n") != :nomatch do
|
||||
{:ok, acc}
|
||||
else
|
||||
if byte_size(acc) > 8_192 do
|
||||
{:error, :too_large}
|
||||
else
|
||||
recv_headers(socket, acc)
|
||||
end
|
||||
end
|
||||
|
||||
{:error, _} = error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_path(data) do
|
||||
case String.split(data, "\r\n", parts: 2) do
|
||||
[request_line | _] ->
|
||||
case String.split(request_line, " ") do
|
||||
[_method, path, _protocol] -> {:ok, path}
|
||||
_ -> {:error, :invalid_request}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, :invalid_request}
|
||||
end
|
||||
end
|
||||
|
||||
defp send_response(socket, status, reason, headers, body) do
|
||||
base_headers =
|
||||
[
|
||||
{"Content-Length", Integer.to_string(byte_size(body))},
|
||||
{"Connection", "close"}
|
||||
] ++ headers
|
||||
|
||||
iodata =
|
||||
[
|
||||
"HTTP/1.1 ",
|
||||
Integer.to_string(status),
|
||||
" ",
|
||||
reason,
|
||||
"\r\n",
|
||||
Enum.map(base_headers, fn {k, v} -> [k, ": ", v, "\r\n"] end),
|
||||
"\r\n",
|
||||
body
|
||||
]
|
||||
|
||||
:gen_tcp.send(socket, iodata)
|
||||
end
|
||||
end
|
||||
|
|
@ -54,14 +54,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do
|
|||
setup do: clear_config([:media_proxy, :enabled], true)
|
||||
|
||||
test "it prefetches media proxy URIs" do
|
||||
Tesla.Mock.mock(fn %{method: :get, url: "http://example.com/image.jpg"} ->
|
||||
{:ok, %Tesla.Env{status: 200, body: ""}}
|
||||
end)
|
||||
|
||||
with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do
|
||||
with_mock HTTP,
|
||||
get: fn _, _, opts ->
|
||||
send(self(), {:prefetch_opts, opts})
|
||||
{:ok, []}
|
||||
end do
|
||||
MediaProxyWarmingPolicy.filter(@message)
|
||||
|
||||
assert called(HTTP.get(:_, :_, :_))
|
||||
assert_receive {:prefetch_opts, opts}
|
||||
refute Keyword.has_key?(opts, :follow_redirect)
|
||||
refute Keyword.has_key?(opts, :force_redirect)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -81,10 +84,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do
|
|||
end
|
||||
|
||||
test "history-aware" do
|
||||
Tesla.Mock.mock(fn %{method: :get, url: "http://example.com/image.jpg"} ->
|
||||
{:ok, %Tesla.Env{status: 200, body: ""}}
|
||||
end)
|
||||
|
||||
with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do
|
||||
MRF.filter_one(MediaProxyWarmingPolicy, @message_with_history)
|
||||
|
||||
|
|
@ -93,10 +92,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do
|
|||
end
|
||||
|
||||
test "works with Updates" do
|
||||
Tesla.Mock.mock(fn %{method: :get, url: "http://example.com/image.jpg"} ->
|
||||
{:ok, %Tesla.Env{status: 200, body: ""}}
|
||||
end)
|
||||
|
||||
with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do
|
||||
MRF.filter_one(MediaProxyWarmingPolicy, @message_with_history |> Map.put("type", "Update"))
|
||||
|
||||
|
|
|
|||
|
|
@ -671,6 +671,19 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "assign_report_to_account/2" do
|
||||
test "assigns report to an account" do
|
||||
reporter = insert(:user)
|
||||
target_account = insert(:user)
|
||||
%{id: assigned_id} = insert(:user)
|
||||
|
||||
{:ok, report} = CommonAPI.report(reporter, %{account_id: target_account.id})
|
||||
{:ok, report} = Utils.assign_report_to_account(report, assigned_id)
|
||||
|
||||
assert %{data: %{"assigned_account" => ^assigned_id}} = report
|
||||
end
|
||||
end
|
||||
|
||||
describe "maybe_anonymize_reporter/1" do
|
||||
setup do
|
||||
reporter = insert(:user)
|
||||
|
|
|
|||
|
|
@ -388,6 +388,38 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
|
|||
|> json_response_and_validate_schema(:ok)
|
||||
end
|
||||
|
||||
test "returns reports with specified assigned user", %{conn: conn, admin: admin} do
|
||||
[reporter, target_user] = insert_pair(:user)
|
||||
activity = insert(:note_activity, user: target_user)
|
||||
|
||||
{:ok, _report} =
|
||||
CommonAPI.report(reporter, %{
|
||||
account_id: target_user.id,
|
||||
comment: "I feel offended",
|
||||
status_ids: [activity.id]
|
||||
})
|
||||
|
||||
{:ok, %{id: second_report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
account_id: target_user.id,
|
||||
comment: "I don't like this user"
|
||||
})
|
||||
|
||||
CommonAPI.assign_report_to_account(second_report_id, admin.id)
|
||||
|
||||
response =
|
||||
conn
|
||||
|> get(report_path(conn, :index, %{assigned_account: admin.id}))
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert [open_report] = response["reports"]
|
||||
|
||||
assert length(response["reports"]) == 1
|
||||
assert open_report["id"] == second_report_id
|
||||
|
||||
assert response["total"] == 1
|
||||
end
|
||||
|
||||
test "renders content correctly", %{conn: conn} do
|
||||
[reporter, target_user] = insert_pair(:user)
|
||||
note = insert(:note, user: target_user, data: %{"content" => "mew 1"})
|
||||
|
|
@ -467,6 +499,66 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "POST /api/pleroma/admin/reports/assign_account" do
|
||||
test "assigns account to report", %{conn: conn, admin: admin} do
|
||||
[reporter, target_user] = insert_pair(:user)
|
||||
activity = insert(:note_activity, user: target_user)
|
||||
|
||||
{:ok, %{id: report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
account_id: target_user.id,
|
||||
status_ids: [activity.id]
|
||||
})
|
||||
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/pleroma/admin/reports/assign_account", %{
|
||||
"reports" => [
|
||||
%{"assigned_account" => admin.nickname, "id" => report_id}
|
||||
]
|
||||
})
|
||||
|> json_response_and_validate_schema(:no_content)
|
||||
|
||||
activity = Activity.get_by_id_with_user_actor(report_id)
|
||||
assert activity.data["assigned_account"] == admin.id
|
||||
|
||||
log_entry = Repo.one(ModerationLog)
|
||||
|
||||
assert ModerationLog.get_log_entry_message(log_entry) ==
|
||||
"@#{admin.nickname} assigned report ##{report_id} (on user @#{activity.user_actor.nickname}) to user #{admin.nickname}"
|
||||
end
|
||||
|
||||
test "unassigns account from report", %{conn: conn, admin: admin} do
|
||||
[reporter, target_user] = insert_pair(:user)
|
||||
activity = insert(:note_activity, user: target_user)
|
||||
|
||||
{:ok, %{id: report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
account_id: target_user.id,
|
||||
status_ids: [activity.id]
|
||||
})
|
||||
|
||||
CommonAPI.assign_report_to_account(report_id, admin.id)
|
||||
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/pleroma/admin/reports/assign_account", %{
|
||||
"reports" => [
|
||||
%{"assigned_account" => nil, "id" => report_id}
|
||||
]
|
||||
})
|
||||
|> json_response_and_validate_schema(:no_content)
|
||||
|
||||
activity = Activity.get_by_id_with_user_actor(report_id)
|
||||
assert activity.data["assigned_account"] == nil
|
||||
|
||||
log_entry = Repo.one(ModerationLog)
|
||||
|
||||
assert ModerationLog.get_log_entry_message(log_entry) ==
|
||||
"@#{admin.nickname} unassigned report ##{report_id} (on user @#{activity.user_actor.nickname}) from a user"
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /api/pleroma/admin/reports/:id/notes" do
|
||||
setup %{conn: conn, admin: admin} do
|
||||
clear_config([:instance, :admin_privileges], [:reports_manage_reports])
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do
|
|||
}),
|
||||
AdminAPI.AccountView.render("show.json", %{user: other_user})
|
||||
),
|
||||
assigned_account: nil,
|
||||
statuses: [],
|
||||
notes: [],
|
||||
state: "open",
|
||||
|
|
@ -75,6 +76,7 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do
|
|||
}),
|
||||
AdminAPI.AccountView.render("show.json", %{user: other_user})
|
||||
),
|
||||
assigned_account: nil,
|
||||
statuses: [StatusView.render("show.json", %{activity: activity})],
|
||||
state: "open",
|
||||
notes: [],
|
||||
|
|
|
|||
|
|
@ -1458,6 +1458,29 @@ defmodule Pleroma.Web.CommonAPITest do
|
|||
}
|
||||
} = flag_activity
|
||||
end
|
||||
|
||||
test "assigns report to an account" do
|
||||
[reporter, target_user] = insert_pair(:user)
|
||||
%{id: assigned} = insert(:user)
|
||||
|
||||
{:ok, %Activity{id: report_id}} = CommonAPI.report(reporter, %{account_id: target_user.id})
|
||||
|
||||
{:ok, activity} = CommonAPI.assign_report_to_account(report_id, assigned)
|
||||
|
||||
assert %{data: %{"assigned_account" => ^assigned}} = activity
|
||||
end
|
||||
|
||||
test "unassigns report from account" do
|
||||
[reporter, target_user] = insert_pair(:user)
|
||||
%{id: assigned} = insert(:user)
|
||||
|
||||
{:ok, %Activity{id: report_id}} = CommonAPI.report(reporter, %{account_id: target_user.id})
|
||||
|
||||
CommonAPI.assign_report_to_account(report_id, assigned)
|
||||
{:ok, activity} = CommonAPI.assign_report_to_account(report_id, nil)
|
||||
|
||||
refute Map.has_key?(activity.data, "assigned_account")
|
||||
end
|
||||
end
|
||||
|
||||
describe "reblog muting" do
|
||||
|
|
|
|||
|
|
@ -1901,7 +1901,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
|||
|
||||
{:ok, _user_relationships} = User.mute(user, other_user1)
|
||||
{:ok, _user_relationships} = User.mute(user, other_user2)
|
||||
{:ok, _user_relationships} = User.mute(user, other_user3)
|
||||
{:ok, _user_relationships} = User.mute(user, other_user3, %{duration: 24 * 60 * 60})
|
||||
|
||||
date =
|
||||
DateTime.utc_now()
|
||||
|> DateTime.add(24 * 60 * 60)
|
||||
|> DateTime.truncate(:second)
|
||||
|> DateTime.to_iso8601()
|
||||
|
||||
result =
|
||||
conn
|
||||
|
|
@ -1937,6 +1943,17 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
|||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [%{"id" => ^id3}] = result
|
||||
|
||||
result =
|
||||
conn
|
||||
|> get("/api/v1/mutes")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [
|
||||
%{"id" => ^id3, "mute_expires_at" => ^date},
|
||||
%{"id" => ^id2, "mute_expires_at" => nil},
|
||||
%{"id" => ^id1, "mute_expires_at" => nil}
|
||||
] = result
|
||||
end
|
||||
|
||||
test "list of mutes with with_relationships parameter" do
|
||||
|
|
@ -1951,20 +1968,44 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
|||
|
||||
{:ok, _} = User.mute(user, other_user1)
|
||||
{:ok, _} = User.mute(user, other_user2)
|
||||
{:ok, _} = User.mute(user, other_user3)
|
||||
{:ok, _} = User.mute(user, other_user3, %{duration: 24 * 60 * 60})
|
||||
|
||||
date =
|
||||
DateTime.utc_now()
|
||||
|> DateTime.add(24 * 60 * 60)
|
||||
|> DateTime.truncate(:second)
|
||||
|> DateTime.to_iso8601()
|
||||
|
||||
assert [
|
||||
%{
|
||||
"id" => ^id3,
|
||||
"pleroma" => %{"relationship" => %{"muting" => true, "followed_by" => true}}
|
||||
"pleroma" => %{
|
||||
"relationship" => %{
|
||||
"muting" => true,
|
||||
"mute_expires_at" => ^date,
|
||||
"followed_by" => true
|
||||
}
|
||||
}
|
||||
},
|
||||
%{
|
||||
"id" => ^id2,
|
||||
"pleroma" => %{"relationship" => %{"muting" => true, "followed_by" => true}}
|
||||
"pleroma" => %{
|
||||
"relationship" => %{
|
||||
"muting" => true,
|
||||
"mute_expires_at" => nil,
|
||||
"followed_by" => true
|
||||
}
|
||||
}
|
||||
},
|
||||
%{
|
||||
"id" => ^id1,
|
||||
"pleroma" => %{"relationship" => %{"muting" => true, "followed_by" => true}}
|
||||
"pleroma" => %{
|
||||
"relationship" => %{
|
||||
"muting" => true,
|
||||
"mute_expires_at" => nil,
|
||||
"followed_by" => true
|
||||
}
|
||||
}
|
||||
}
|
||||
] =
|
||||
conn
|
||||
|
|
@ -1980,7 +2021,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
|||
|
||||
{:ok, _user_relationship} = User.block(user, other_user1)
|
||||
{:ok, _user_relationship} = User.block(user, other_user3)
|
||||
{:ok, _user_relationship} = User.block(user, other_user2)
|
||||
{:ok, _user_relationship} = User.block(user, other_user2, %{duration: 24 * 60 * 60})
|
||||
|
||||
date =
|
||||
DateTime.utc_now()
|
||||
|> DateTime.add(24 * 60 * 60)
|
||||
|> DateTime.truncate(:second)
|
||||
|> DateTime.to_iso8601()
|
||||
|
||||
result =
|
||||
conn
|
||||
|
|
@ -2045,6 +2092,18 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
|||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [%{"id" => ^id1}] = result
|
||||
|
||||
result =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("api/v1/blocks")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [
|
||||
%{"id" => ^id3, "block_expires_at" => nil},
|
||||
%{"id" => ^id2, "block_expires_at" => ^date},
|
||||
%{"id" => ^id1, "block_expires_at" => nil}
|
||||
] = result
|
||||
end
|
||||
|
||||
test "list of blocks with with_relationships parameter" do
|
||||
|
|
@ -2059,20 +2118,44 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
|||
|
||||
{:ok, _} = User.block(user, other_user1)
|
||||
{:ok, _} = User.block(user, other_user2)
|
||||
{:ok, _} = User.block(user, other_user3)
|
||||
{:ok, _} = User.block(user, other_user3, %{duration: 24 * 60 * 60})
|
||||
|
||||
date =
|
||||
DateTime.utc_now()
|
||||
|> DateTime.add(24 * 60 * 60)
|
||||
|> DateTime.truncate(:second)
|
||||
|> DateTime.to_iso8601()
|
||||
|
||||
assert [
|
||||
%{
|
||||
"id" => ^id3,
|
||||
"pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}}
|
||||
"pleroma" => %{
|
||||
"relationship" => %{
|
||||
"blocking" => true,
|
||||
"block_expires_at" => ^date,
|
||||
"followed_by" => false
|
||||
}
|
||||
}
|
||||
},
|
||||
%{
|
||||
"id" => ^id2,
|
||||
"pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}}
|
||||
"pleroma" => %{
|
||||
"relationship" => %{
|
||||
"blocking" => true,
|
||||
"block_expires_at" => nil,
|
||||
"followed_by" => false
|
||||
}
|
||||
}
|
||||
},
|
||||
%{
|
||||
"id" => ^id1,
|
||||
"pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}}
|
||||
"pleroma" => %{
|
||||
"relationship" => %{
|
||||
"blocking" => true,
|
||||
"block_expires_at" => nil,
|
||||
"followed_by" => false
|
||||
}
|
||||
}
|
||||
}
|
||||
] =
|
||||
conn
|
||||
|
|
|
|||
|
|
@ -54,8 +54,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
|
|||
note: "<span>valid html</span>. a<br/>b<br/>c<br/>d<br/>f '&<>"",
|
||||
url: user.ap_id,
|
||||
avatar: "http://localhost:4001/images/avi.png",
|
||||
avatar_description: "",
|
||||
avatar_static: "http://localhost:4001/images/avi.png",
|
||||
header: "http://localhost:4001/images/banner.png",
|
||||
header_description: "",
|
||||
header_static: "http://localhost:4001/images/banner.png",
|
||||
emojis: [
|
||||
%{
|
||||
|
|
@ -326,8 +328,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
|
|||
note: user.bio,
|
||||
url: user.ap_id,
|
||||
avatar: "http://localhost:4001/images/avi.png",
|
||||
avatar_description: "",
|
||||
avatar_static: "http://localhost:4001/images/avi.png",
|
||||
header: "http://localhost:4001/images/banner.png",
|
||||
header_description: "",
|
||||
header_static: "http://localhost:4001/images/banner.png",
|
||||
emojis: [],
|
||||
fields: [],
|
||||
|
|
@ -439,8 +443,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
|
|||
following: false,
|
||||
followed_by: false,
|
||||
blocking: false,
|
||||
block_expires_at: nil,
|
||||
blocked_by: false,
|
||||
muting: false,
|
||||
mute_expires_at: nil,
|
||||
muting_notifications: false,
|
||||
subscribing: false,
|
||||
notifying: false,
|
||||
|
|
@ -536,6 +542,53 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
|
|||
test_relationship_rendering(user, other_user, expected)
|
||||
end
|
||||
|
||||
test "represent a relationship for the blocking and blocked user with expiry" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
date = DateTime.utc_now() |> DateTime.add(24 * 60 * 60) |> DateTime.truncate(:second)
|
||||
|
||||
{:ok, user, other_user} = User.follow(user, other_user)
|
||||
{:ok, _subscription} = User.subscribe(user, other_user)
|
||||
{:ok, _user_relationship} = User.block(user, other_user, %{duration: 24 * 60 * 60})
|
||||
{:ok, _user_relationship} = User.block(other_user, user)
|
||||
|
||||
expected =
|
||||
Map.merge(
|
||||
@blank_response,
|
||||
%{
|
||||
following: false,
|
||||
blocking: true,
|
||||
block_expires_at: date,
|
||||
blocked_by: true,
|
||||
id: to_string(other_user.id)
|
||||
}
|
||||
)
|
||||
|
||||
test_relationship_rendering(user, other_user, expected)
|
||||
end
|
||||
|
||||
test "represent a relationship for the muting user with expiry" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
date = DateTime.utc_now() |> DateTime.add(24 * 60 * 60) |> DateTime.truncate(:second)
|
||||
|
||||
{:ok, _user_relationship} =
|
||||
User.mute(user, other_user, %{notifications: true, duration: 24 * 60 * 60})
|
||||
|
||||
expected =
|
||||
Map.merge(
|
||||
@blank_response,
|
||||
%{
|
||||
muting: true,
|
||||
mute_expires_at: date,
|
||||
muting_notifications: true,
|
||||
id: to_string(other_user.id)
|
||||
}
|
||||
)
|
||||
|
||||
test_relationship_rendering(user, other_user, expected)
|
||||
end
|
||||
|
||||
test "represent a relationship for the user blocking a domain" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user, ap_id: "https://bad.site/users/other_user")
|
||||
|
|
@ -856,12 +909,37 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
|
|||
User.mute(user, other_user, %{notifications: true, duration: 24 * 60 * 60})
|
||||
|
||||
%{
|
||||
mute_expires_at: mute_expires_at
|
||||
} = AccountView.render("show.json", %{user: other_user, for: user, mutes: true})
|
||||
pleroma: %{
|
||||
relationship: %{
|
||||
mute_expires_at: mute_expires_at
|
||||
}
|
||||
}
|
||||
} = AccountView.render("show.json", %{user: other_user, for: user, embed_relationships: true})
|
||||
|
||||
assert DateTime.diff(
|
||||
mute_expires_at,
|
||||
DateTime.utc_now() |> DateTime.add(24 * 60 * 60)
|
||||
) in -3..3
|
||||
end
|
||||
|
||||
test "renders block expiration date" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, _user_relationships} =
|
||||
User.block(user, other_user, %{duration: 24 * 60 * 60})
|
||||
|
||||
%{
|
||||
pleroma: %{
|
||||
relationship: %{
|
||||
block_expires_at: block_expires_at
|
||||
}
|
||||
}
|
||||
} = AccountView.render("show.json", %{user: other_user, for: user, embed_relationships: true})
|
||||
|
||||
assert DateTime.diff(
|
||||
block_expires_at,
|
||||
DateTime.utc_now() |> DateTime.add(24 * 60 * 60)
|
||||
) in -3..3
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue