Merge remote-tracking branch 'remotes/origin/develop' into 2168-media-preview-proxy
# Conflicts: # config/config.exs # mix.lock
This commit is contained in:
commit
2def3cbf41
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -27,8 +27,6 @@ erl_crash.dump
|
||||||
# variables.
|
# variables.
|
||||||
/config/*.secret.exs
|
/config/*.secret.exs
|
||||||
/config/generated_config.exs
|
/config/generated_config.exs
|
||||||
/config/*.env
|
|
||||||
|
|
||||||
|
|
||||||
# Database setup file, some may forget to delete it
|
# Database setup file, some may forget to delete it
|
||||||
/config/setup_db.psql
|
/config/setup_db.psql
|
||||||
|
|
|
@ -24,6 +24,7 @@ stages:
|
||||||
- docker
|
- docker
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
|
- apt-get update && apt-get install -y cmake
|
||||||
- mix local.hex --force
|
- mix local.hex --force
|
||||||
- mix local.rebar --force
|
- mix local.rebar --force
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
## [unreleased]
|
## [unreleased]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- **Breaking:** Added the ObjectAgePolicy to the default set of MRFs. This will delist and strip the follower collection of any message received that is older than 7 days. This will stop users from seeing very old messages in the timelines. The messages can still be viewed on the user's page and in conversations. They also still trigger notifications.
|
||||||
- **Breaking:** Elixir >=1.9 is now required (was >= 1.8)
|
- **Breaking:** Elixir >=1.9 is now required (was >= 1.8)
|
||||||
- **Breaking:** Configuration: `:auto_linker, :opts` moved to `:pleroma, Pleroma.Formatter`. Old config namespace is deprecated.
|
- **Breaking:** Configuration: `:auto_linker, :opts` moved to `:pleroma, Pleroma.Formatter`. Old config namespace is deprecated.
|
||||||
- In Conversations, return only direct messages as `last_status`
|
- In Conversations, return only direct messages as `last_status`
|
||||||
|
@ -15,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Configuration: `:instance, rewrite_policy` moved to `:mrf, policies`, `:instance, :mrf_transparency` moved to `:mrf, :transparency`, `:instance, :mrf_transparency_exclusions` moved to `:mrf, :transparency_exclusions`. Old config namespace is deprecated.
|
- Configuration: `:instance, rewrite_policy` moved to `:mrf, policies`, `:instance, :mrf_transparency` moved to `:mrf, :transparency`, `:instance, :mrf_transparency_exclusions` moved to `:mrf, :transparency_exclusions`. Old config namespace is deprecated.
|
||||||
- Configuration: `:media_proxy, whitelist` format changed to host with scheme (e.g. `http://example.com` instead of `example.com`). Domain format is deprecated.
|
- Configuration: `:media_proxy, whitelist` format changed to host with scheme (e.g. `http://example.com` instead of `example.com`). Domain format is deprecated.
|
||||||
- **Breaking:** Configuration: `:instance, welcome_user_nickname` moved to `:welcome, :direct_message, :sender_nickname`, `:instance, :welcome_message` moved to `:welcome, :direct_message, :message`. Old config namespace is deprecated.
|
- **Breaking:** Configuration: `:instance, welcome_user_nickname` moved to `:welcome, :direct_message, :sender_nickname`, `:instance, :welcome_message` moved to `:welcome, :direct_message, :message`. Old config namespace is deprecated.
|
||||||
|
- **Breaking:** LDAP: Fallback to local database authentication has been removed for security reasons and lack of a mechanism to ensure the passwords are synchronized when LDAP passwords are updated.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>API Changes</summary>
|
<summary>API Changes</summary>
|
||||||
|
|
16
SECURITY.md
Normal file
16
SECURITY.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Pleroma backend security policy
|
||||||
|
|
||||||
|
## Supported versions
|
||||||
|
|
||||||
|
Currently, Pleroma offers bugfixes and security patches only for the latest minor release.
|
||||||
|
|
||||||
|
| Version | Support
|
||||||
|
|---------| --------
|
||||||
|
| 2.0 | Bugfixes and security patches
|
||||||
|
|
||||||
|
## Reporting a vulnerability
|
||||||
|
|
||||||
|
Please use confidential issues (tick the "This issue is confidential and should only be visible to team members with at least Reporter access." box when submitting) at our [bugtracker](https://git.pleroma.social/pleroma/pleroma/-/issues/new) for reporting vulnerabilities.
|
||||||
|
## Announcements
|
||||||
|
|
||||||
|
New releases are announced at [pleroma.social](https://pleroma.social/announcements/). All security releases are tagged with ["Security"](https://pleroma.social/announcements/tags/security/). You can be notified of them by subscribing to an Atom feed at <https://pleroma.social/announcements/tags/security/feed.xml>.
|
|
@ -527,7 +527,13 @@
|
||||||
"user-search",
|
"user-search",
|
||||||
"user_exists",
|
"user_exists",
|
||||||
"users",
|
"users",
|
||||||
"web"
|
"web",
|
||||||
|
"verify_credentials",
|
||||||
|
"update_credentials",
|
||||||
|
"relationships",
|
||||||
|
"search",
|
||||||
|
"confirmation_resend",
|
||||||
|
"mfa"
|
||||||
],
|
],
|
||||||
email_blacklist: []
|
email_blacklist: []
|
||||||
|
|
||||||
|
@ -749,6 +755,10 @@
|
||||||
|
|
||||||
config :pleroma, :instances_favicons, enabled: false
|
config :pleroma, :instances_favicons, enabled: false
|
||||||
|
|
||||||
|
config :floki, :html_parser, Floki.HTMLParser.FastHtml
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.PleromaAuthenticator
|
||||||
|
|
||||||
config :pleroma, :exexec,
|
config :pleroma, :exexec,
|
||||||
root_mode: false,
|
root_mode: false,
|
||||||
options: %{}
|
options: %{}
|
||||||
|
|
|
@ -98,3 +98,13 @@ but should only be run if necessary. **It is safe to cancel this.**
|
||||||
```sh tab="From Source"
|
```sh tab="From Source"
|
||||||
mix pleroma.database vacuum full
|
mix pleroma.database vacuum full
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Add expiration to all local statuses
|
||||||
|
|
||||||
|
```sh tab="OTP"
|
||||||
|
./bin/pleroma_ctl database ensure_expiration
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.database ensure_expiration
|
||||||
|
```
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
# Generate release environment file
|
|
||||||
|
|
||||||
```sh tab="OTP"
|
|
||||||
./bin/pleroma_ctl release_env gen
|
|
||||||
```
|
|
||||||
|
|
||||||
```sh tab="From Source"
|
|
||||||
mix pleroma.release_env gen
|
|
||||||
```
|
|
|
@ -858,9 +858,6 @@ Warning: it's discouraged to use this feature because of the associated security
|
||||||
|
|
||||||
### :auth
|
### :auth
|
||||||
|
|
||||||
* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator.
|
|
||||||
* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication.
|
|
||||||
|
|
||||||
Authentication / authorization settings.
|
Authentication / authorization settings.
|
||||||
|
|
||||||
* `auth_template`: authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.eex`.
|
* `auth_template`: authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.eex`.
|
||||||
|
@ -890,6 +887,9 @@ Pleroma account will be created with the same name as the LDAP user name.
|
||||||
* `base`: LDAP base, e.g. "dc=example,dc=com"
|
* `base`: LDAP base, e.g. "dc=example,dc=com"
|
||||||
* `uid`: LDAP attribute name to authenticate the user, e.g. when "cn", the filter will be "cn=username,base"
|
* `uid`: LDAP attribute name to authenticate the user, e.g. when "cn", the filter will be "cn=username,base"
|
||||||
|
|
||||||
|
Note, if your LDAP server is an Active Directory server the correct value is commonly `uid: "cn"`, but if you use an
|
||||||
|
OpenLDAP server the value may be `uid: "uid"`.
|
||||||
|
|
||||||
### OAuth consumer mode
|
### OAuth consumer mode
|
||||||
|
|
||||||
OAuth consumer mode allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).
|
OAuth consumer mode allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).
|
||||||
|
|
|
@ -14,6 +14,7 @@ It assumes that you have administrative rights, either as root or a user with [s
|
||||||
* `erlang-xmerl`
|
* `erlang-xmerl`
|
||||||
* `git`
|
* `git`
|
||||||
* Development Tools
|
* Development Tools
|
||||||
|
* `cmake`
|
||||||
|
|
||||||
#### Optional packages used in this guide
|
#### Optional packages used in this guide
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ sudo apk upgrade
|
||||||
* Install some tools, which are needed later:
|
* Install some tools, which are needed later:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo apk add git build-base
|
sudo apk add git build-base cmake
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install Elixir and Erlang
|
### Install Elixir and Erlang
|
||||||
|
|
|
@ -9,6 +9,7 @@ This guide will assume that you have administrative rights, either as root or a
|
||||||
* `elixir`
|
* `elixir`
|
||||||
* `git`
|
* `git`
|
||||||
* `base-devel`
|
* `base-devel`
|
||||||
|
* `cmake`
|
||||||
|
|
||||||
#### Optional packages used in this guide
|
#### Optional packages used in this guide
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ sudo pacman -Syu
|
||||||
* Install some of the above mentioned programs:
|
* Install some of the above mentioned programs:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo pacman -S git base-devel elixir
|
sudo pacman -S git base-devel elixir cmake
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install PostgreSQL
|
### Install PostgreSQL
|
||||||
|
|
|
@ -12,6 +12,7 @@ This guide will assume you are on Debian Stretch. This guide should also work wi
|
||||||
* `erlang-nox`
|
* `erlang-nox`
|
||||||
* `git`
|
* `git`
|
||||||
* `build-essential`
|
* `build-essential`
|
||||||
|
* `cmake`
|
||||||
|
|
||||||
#### Optional packages used in this guide
|
#### Optional packages used in this guide
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ sudo apt full-upgrade
|
||||||
* Install some of the above mentioned programs:
|
* Install some of the above mentioned programs:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo apt install git build-essential postgresql postgresql-contrib
|
sudo apt install git build-essential postgresql postgresql-contrib cmake
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install Elixir and Erlang
|
### Install Elixir and Erlang
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
- `erlang-nox`
|
- `erlang-nox`
|
||||||
- `git`
|
- `git`
|
||||||
- `build-essential`
|
- `build-essential`
|
||||||
|
- `cmake`
|
||||||
|
|
||||||
#### このガイドで利用している追加パッケージ
|
#### このガイドで利用している追加パッケージ
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ sudo apt full-upgrade
|
||||||
|
|
||||||
* 上記に挙げたパッケージをインストールしておきます。
|
* 上記に挙げたパッケージをインストールしておきます。
|
||||||
```
|
```
|
||||||
sudo apt install git build-essential postgresql postgresql-contrib
|
sudo apt install git build-essential postgresql postgresql-contrib cmake
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ Gentoo quite pointedly does not come with a cron daemon installed, and as such i
|
||||||
* `dev-db/postgresql`
|
* `dev-db/postgresql`
|
||||||
* `dev-lang/elixir`
|
* `dev-lang/elixir`
|
||||||
* `dev-vcs/git`
|
* `dev-vcs/git`
|
||||||
|
* `dev-util/cmake`
|
||||||
|
|
||||||
#### Optional ebuilds used in this guide
|
#### Optional ebuilds used in this guide
|
||||||
|
|
||||||
|
@ -46,7 +47,7 @@ Gentoo quite pointedly does not come with a cron daemon installed, and as such i
|
||||||
* Emerge all required the required and suggested software in one go:
|
* Emerge all required the required and suggested software in one go:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# emerge --ask dev-db/postgresql dev-lang/elixir dev-vcs/git www-servers/nginx app-crypt/certbot app-crypt/certbot-nginx
|
# emerge --ask dev-db/postgresql dev-lang/elixir dev-vcs/git www-servers/nginx app-crypt/certbot app-crypt/certbot-nginx dev-util/cmake
|
||||||
```
|
```
|
||||||
|
|
||||||
If you would not like to install the optional packages, remove them from this line.
|
If you would not like to install the optional packages, remove them from this line.
|
||||||
|
|
|
@ -19,6 +19,7 @@ databases/postgresql11-client
|
||||||
databases/postgresql11-server
|
databases/postgresql11-server
|
||||||
devel/git-base
|
devel/git-base
|
||||||
devel/git-docs
|
devel/git-docs
|
||||||
|
devel/cmake
|
||||||
lang/elixir
|
lang/elixir
|
||||||
security/acmesh
|
security/acmesh
|
||||||
security/sudo
|
security/sudo
|
||||||
|
|
|
@ -14,11 +14,12 @@ The following packages need to be installed:
|
||||||
* git
|
* git
|
||||||
* postgresql-server
|
* postgresql-server
|
||||||
* postgresql-contrib
|
* postgresql-contrib
|
||||||
|
* cmake
|
||||||
|
|
||||||
To install them, run the following command (with doas or as root):
|
To install them, run the following command (with doas or as root):
|
||||||
|
|
||||||
```
|
```
|
||||||
pkg_add elixir gmake ImageMagick git postgresql-server postgresql-contrib
|
pkg_add elixir gmake ImageMagick git postgresql-server postgresql-contrib cmake
|
||||||
```
|
```
|
||||||
|
|
||||||
Pleroma requires a reverse proxy, OpenBSD has relayd in base (and is used in this guide) and packages/ports are available for nginx (www/nginx) and apache (www/apache-httpd). Independently of the reverse proxy, [acme-client(1)](https://man.openbsd.org/acme-client) can be used to get a certificate from Let's Encrypt.
|
Pleroma requires a reverse proxy, OpenBSD has relayd in base (and is used in this guide) and packages/ports are available for nginx (www/nginx) and apache (www/apache-httpd). Independently of the reverse proxy, [acme-client(1)](https://man.openbsd.org/acme-client) can be used to get a certificate from Let's Encrypt.
|
||||||
|
|
|
@ -16,7 +16,7 @@ Matrix-kanava #freenode_#pleroma:matrix.org ovat hyviä paikkoja löytää apua
|
||||||
|
|
||||||
Asenna tarvittava ohjelmisto:
|
Asenna tarvittava ohjelmisto:
|
||||||
|
|
||||||
`# pkg_add git elixir gmake postgresql-server-10.3 postgresql-contrib-10.3`
|
`# pkg_add git elixir gmake postgresql-server-10.3 postgresql-contrib-10.3 cmake`
|
||||||
|
|
||||||
Luo postgresql-tietokanta:
|
Luo postgresql-tietokanta:
|
||||||
|
|
||||||
|
|
|
@ -121,9 +121,6 @@ chown -R pleroma /etc/pleroma
|
||||||
# Run the config generator
|
# Run the config generator
|
||||||
su pleroma -s $SHELL -lc "./bin/pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql"
|
su pleroma -s $SHELL -lc "./bin/pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql"
|
||||||
|
|
||||||
# Run the environment file generator.
|
|
||||||
su pleroma -s $SHELL -lc "./bin/pleroma_ctl release_env gen"
|
|
||||||
|
|
||||||
# Create the postgres database
|
# Create the postgres database
|
||||||
su postgres -s $SHELL -lc "psql -f /tmp/setup_db.psql"
|
su postgres -s $SHELL -lc "psql -f /tmp/setup_db.psql"
|
||||||
|
|
||||||
|
@ -134,7 +131,7 @@ su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate"
|
||||||
# su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
|
# su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
|
||||||
|
|
||||||
# Start the instance to verify that everything is working as expected
|
# Start the instance to verify that everything is working as expected
|
||||||
su pleroma -s $SHELL -lc "export $(cat /opt/pleroma/config/pleroma.env); ./bin/pleroma daemon"
|
su pleroma -s $SHELL -lc "./bin/pleroma daemon"
|
||||||
|
|
||||||
# Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly
|
# Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly
|
||||||
sleep 20 && curl http://localhost:4000/api/v1/instance
|
sleep 20 && curl http://localhost:4000/api/v1/instance
|
||||||
|
@ -203,7 +200,6 @@ rc-update add pleroma
|
||||||
# Copy the service into a proper directory
|
# Copy the service into a proper directory
|
||||||
cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
|
cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
|
||||||
|
|
||||||
|
|
||||||
# Start pleroma and enable it on boot
|
# Start pleroma and enable it on boot
|
||||||
systemctl start pleroma
|
systemctl start pleroma
|
||||||
systemctl enable pleroma
|
systemctl enable pleroma
|
||||||
|
@ -279,3 +275,4 @@ This will create an account withe the username of 'joeuser' with the email addre
|
||||||
## Questions
|
## Questions
|
||||||
|
|
||||||
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
|
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ pidfile="/var/run/pleroma.pid"
|
||||||
directory=/opt/pleroma
|
directory=/opt/pleroma
|
||||||
healthcheck_delay=60
|
healthcheck_delay=60
|
||||||
healthcheck_timer=30
|
healthcheck_timer=30
|
||||||
export $(cat /opt/pleroma/config/pleroma.env)
|
|
||||||
|
|
||||||
: ${pleroma_port:-4000}
|
: ${pleroma_port:-4000}
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,6 @@ Environment="MIX_ENV=prod"
|
||||||
Environment="HOME=/var/lib/pleroma"
|
Environment="HOME=/var/lib/pleroma"
|
||||||
; Path to the folder containing the Pleroma installation.
|
; Path to the folder containing the Pleroma installation.
|
||||||
WorkingDirectory=/opt/pleroma
|
WorkingDirectory=/opt/pleroma
|
||||||
; Path to the environment file. the file contains RELEASE_COOKIE and etc
|
|
||||||
EnvironmentFile=/opt/pleroma/config/pleroma.env
|
|
||||||
; Path to the Mix binary.
|
; Path to the Mix binary.
|
||||||
ExecStart=/usr/bin/mix phx.server
|
ExecStart=/usr/bin/mix phx.server
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ defmodule Mix.Tasks.Pleroma.Database do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
require Logger
|
require Logger
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
import Ecto.Query
|
||||||
import Mix.Pleroma
|
import Mix.Pleroma
|
||||||
use Mix.Task
|
use Mix.Task
|
||||||
|
|
||||||
|
@ -53,8 +54,6 @@ def run(["update_users_following_followers_counts"]) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(["prune_objects" | args]) do
|
def run(["prune_objects" | args]) do
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
{options, [], []} =
|
{options, [], []} =
|
||||||
OptionParser.parse(
|
OptionParser.parse(
|
||||||
args,
|
args,
|
||||||
|
@ -94,8 +93,6 @@ def run(["prune_objects" | args]) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(["fix_likes_collections"]) do
|
def run(["fix_likes_collections"]) do
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
|
||||||
from(object in Object,
|
from(object in Object,
|
||||||
|
@ -130,4 +127,23 @@ def run(["vacuum", args]) do
|
||||||
|
|
||||||
Maintenance.vacuum(args)
|
Maintenance.vacuum(args)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def run(["ensure_expiration"]) do
|
||||||
|
start_pleroma()
|
||||||
|
days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365)
|
||||||
|
|
||||||
|
Pleroma.Activity
|
||||||
|
|> join(:left, [a], u in assoc(a, :expiration))
|
||||||
|
|> where(local: true)
|
||||||
|
|> where([a, u], is_nil(u))
|
||||||
|
|> Pleroma.RepoStreamer.chunk_stream(100)
|
||||||
|
|> Stream.each(fn activities ->
|
||||||
|
Enum.each(activities, fn activity ->
|
||||||
|
expires_at = Timex.shift(activity.inserted_at, days: days)
|
||||||
|
|
||||||
|
Pleroma.ActivityExpiration.create(activity, expires_at, false)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|> Stream.run()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Mix.Tasks.Pleroma.ReleaseEnv do
|
|
||||||
use Mix.Task
|
|
||||||
import Mix.Pleroma
|
|
||||||
|
|
||||||
@shortdoc "Generate Pleroma environment file."
|
|
||||||
@moduledoc File.read!("docs/administration/CLI_tasks/release_environments.md")
|
|
||||||
|
|
||||||
def run(["gen" | rest]) do
|
|
||||||
{options, [], []} =
|
|
||||||
OptionParser.parse(
|
|
||||||
rest,
|
|
||||||
strict: [
|
|
||||||
force: :boolean,
|
|
||||||
path: :string
|
|
||||||
],
|
|
||||||
aliases: [
|
|
||||||
p: :path,
|
|
||||||
f: :force
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
file_path =
|
|
||||||
get_option(
|
|
||||||
options,
|
|
||||||
:path,
|
|
||||||
"Environment file path",
|
|
||||||
"./config/pleroma.env"
|
|
||||||
)
|
|
||||||
|
|
||||||
env_path = Path.expand(file_path)
|
|
||||||
|
|
||||||
proceed? =
|
|
||||||
if File.exists?(env_path) do
|
|
||||||
get_option(
|
|
||||||
options,
|
|
||||||
:force,
|
|
||||||
"Environment file already exists. Do you want to overwrite the #{env_path} file? (y/n)",
|
|
||||||
"n"
|
|
||||||
) === "y"
|
|
||||||
else
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
if proceed? do
|
|
||||||
case do_generate(env_path) do
|
|
||||||
{:error, reason} ->
|
|
||||||
shell_error(
|
|
||||||
File.Error.message(%{action: "write to file", reason: reason, path: env_path})
|
|
||||||
)
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
shell_info("\nThe file generated: #{env_path}.\n")
|
|
||||||
|
|
||||||
shell_info("""
|
|
||||||
WARNING: before start pleroma app please make sure to make the file read-only and non-modifiable.
|
|
||||||
Example:
|
|
||||||
chmod 0444 #{file_path}
|
|
||||||
chattr +i #{file_path}
|
|
||||||
""")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
shell_info("\nThe file is exist. #{env_path}.\n")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_generate(path) do
|
|
||||||
content = "RELEASE_COOKIE=#{Base.encode32(:crypto.strong_rand_bytes(32))}"
|
|
||||||
|
|
||||||
File.mkdir_p!(Path.dirname(path))
|
|
||||||
File.write(path, content)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -20,11 +20,11 @@ defmodule Pleroma.ActivityExpiration do
|
||||||
field(:scheduled_at, :naive_datetime)
|
field(:scheduled_at, :naive_datetime)
|
||||||
end
|
end
|
||||||
|
|
||||||
def changeset(%ActivityExpiration{} = expiration, attrs) do
|
def changeset(%ActivityExpiration{} = expiration, attrs, validate_scheduled_at) do
|
||||||
expiration
|
expiration
|
||||||
|> cast(attrs, [:scheduled_at])
|
|> cast(attrs, [:scheduled_at])
|
||||||
|> validate_required([:scheduled_at])
|
|> validate_required([:scheduled_at])
|
||||||
|> validate_scheduled_at()
|
|> validate_scheduled_at(validate_scheduled_at)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_by_activity_id(activity_id) do
|
def get_by_activity_id(activity_id) do
|
||||||
|
@ -33,9 +33,9 @@ def get_by_activity_id(activity_id) do
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
end
|
end
|
||||||
|
|
||||||
def create(%Activity{} = activity, scheduled_at) do
|
def create(%Activity{} = activity, scheduled_at, validate_scheduled_at \\ true) do
|
||||||
%ActivityExpiration{activity_id: activity.id}
|
%ActivityExpiration{activity_id: activity.id}
|
||||||
|> changeset(%{scheduled_at: scheduled_at})
|
|> changeset(%{scheduled_at: scheduled_at}, validate_scheduled_at)
|
||||||
|> Repo.insert()
|
|> Repo.insert()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -49,7 +49,9 @@ def due_expirations(offset \\ 0) do
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_scheduled_at(changeset) do
|
def validate_scheduled_at(changeset, false), do: changeset
|
||||||
|
|
||||||
|
def validate_scheduled_at(changeset, true) do
|
||||||
validate_change(changeset, :scheduled_at, fn _, scheduled_at ->
|
validate_change(changeset, :scheduled_at, fn _, scheduled_at ->
|
||||||
if not expires_late_enough?(scheduled_at) do
|
if not expires_late_enough?(scheduled_at) do
|
||||||
[scheduled_at: "an ephemeral activity must live for at least one hour"]
|
[scheduled_at: "an ephemeral activity must live for at least one hour"]
|
||||||
|
|
|
@ -255,6 +255,10 @@ def increase_replies_count(ap_id) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp poll_is_multiple?(%Object{data: %{"anyOf" => [_ | _]}}), do: true
|
||||||
|
|
||||||
|
defp poll_is_multiple?(_), do: false
|
||||||
|
|
||||||
def decrease_replies_count(ap_id) do
|
def decrease_replies_count(ap_id) do
|
||||||
Object
|
Object
|
||||||
|> where([o], fragment("?->>'id' = ?::text", o.data, ^to_string(ap_id)))
|
|> where([o], fragment("?->>'id' = ?::text", o.data, ^to_string(ap_id)))
|
||||||
|
@ -281,10 +285,10 @@ def decrease_replies_count(ap_id) do
|
||||||
def increase_vote_count(ap_id, name, actor) do
|
def increase_vote_count(ap_id, name, actor) do
|
||||||
with %Object{} = object <- Object.normalize(ap_id),
|
with %Object{} = object <- Object.normalize(ap_id),
|
||||||
"Question" <- object.data["type"] do
|
"Question" <- object.data["type"] do
|
||||||
multiple = Map.has_key?(object.data, "anyOf")
|
key = if poll_is_multiple?(object), do: "anyOf", else: "oneOf"
|
||||||
|
|
||||||
options =
|
options =
|
||||||
(object.data["anyOf"] || object.data["oneOf"] || [])
|
object.data[key]
|
||||||
|> Enum.map(fn
|
|> Enum.map(fn
|
||||||
%{"name" => ^name} = option ->
|
%{"name" => ^name} = option ->
|
||||||
Kernel.update_in(option["replies"]["totalItems"], &(&1 + 1))
|
Kernel.update_in(option["replies"]["totalItems"], &(&1 + 1))
|
||||||
|
@ -296,11 +300,8 @@ def increase_vote_count(ap_id, name, actor) do
|
||||||
voters = [actor | object.data["voters"] || []] |> Enum.uniq()
|
voters = [actor | object.data["voters"] || []] |> Enum.uniq()
|
||||||
|
|
||||||
data =
|
data =
|
||||||
if multiple do
|
object.data
|
||||||
Map.put(object.data, "anyOf", options)
|
|> Map.put(key, options)
|
||||||
else
|
|
||||||
Map.put(object.data, "oneOf", options)
|
|
||||||
end
|
|
||||||
|> Map.put("voters", voters)
|
|> Map.put("voters", voters)
|
||||||
|
|
||||||
object
|
object
|
||||||
|
|
|
@ -55,7 +55,7 @@ defp compare_uris(%URI{host: host} = _id_uri, %URI{host: host} = _other_uri), do
|
||||||
defp compare_uris(_id_uri, _other_uri), do: :error
|
defp compare_uris(_id_uri, _other_uri), do: :error
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Checks that an imported AP object's actor matches the domain it came from.
|
Checks that an imported AP object's actor matches the host it came from.
|
||||||
"""
|
"""
|
||||||
def contain_origin(_id, %{"actor" => nil}), do: :error
|
def contain_origin(_id, %{"actor" => nil}), do: :error
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Object.Fetcher do
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.Signature
|
alias Pleroma.Signature
|
||||||
alias Pleroma.Web.ActivityPub.InternalFetchActor
|
alias Pleroma.Web.ActivityPub.InternalFetchActor
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
alias Pleroma.Web.Federator
|
alias Pleroma.Web.Federator
|
||||||
|
|
||||||
|
@ -23,21 +24,39 @@ defp touch_changeset(changeset) do
|
||||||
Ecto.Changeset.put_change(changeset, :updated_at, updated_at)
|
Ecto.Changeset.put_change(changeset, :updated_at, updated_at)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_reinject_internal_fields(data, %{data: %{} = old_data}) do
|
defp maybe_reinject_internal_fields(%{data: %{} = old_data}, new_data) do
|
||||||
internal_fields = Map.take(old_data, Pleroma.Constants.object_internal_fields())
|
internal_fields = Map.take(old_data, Pleroma.Constants.object_internal_fields())
|
||||||
|
|
||||||
Map.merge(data, internal_fields)
|
Map.merge(new_data, internal_fields)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_reinject_internal_fields(data, _), do: data
|
defp maybe_reinject_internal_fields(_, new_data), do: new_data
|
||||||
|
|
||||||
@spec reinject_object(struct(), map()) :: {:ok, Object.t()} | {:error, any()}
|
@spec reinject_object(struct(), map()) :: {:ok, Object.t()} | {:error, any()}
|
||||||
defp reinject_object(struct, data) do
|
defp reinject_object(%Object{data: %{"type" => "Question"}} = object, new_data) do
|
||||||
Logger.debug("Reinjecting object #{data["id"]}")
|
Logger.debug("Reinjecting object #{new_data["id"]}")
|
||||||
|
|
||||||
with data <- Transmogrifier.fix_object(data),
|
with new_data <- Transmogrifier.fix_object(new_data),
|
||||||
data <- maybe_reinject_internal_fields(data, struct),
|
data <- maybe_reinject_internal_fields(object, new_data),
|
||||||
changeset <- Object.change(struct, %{data: data}),
|
{:ok, data, _} <- ObjectValidator.validate(data, %{}),
|
||||||
|
changeset <- Object.change(object, %{data: data}),
|
||||||
|
changeset <- touch_changeset(changeset),
|
||||||
|
{:ok, object} <- Repo.insert_or_update(changeset),
|
||||||
|
{:ok, object} <- Object.set_cache(object) do
|
||||||
|
{:ok, object}
|
||||||
|
else
|
||||||
|
e ->
|
||||||
|
Logger.error("Error while processing object: #{inspect(e)}")
|
||||||
|
{:error, e}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp reinject_object(%Object{} = object, new_data) do
|
||||||
|
Logger.debug("Reinjecting object #{new_data["id"]}")
|
||||||
|
|
||||||
|
with new_data <- Transmogrifier.fix_object(new_data),
|
||||||
|
data <- maybe_reinject_internal_fields(object, new_data),
|
||||||
|
changeset <- Object.change(object, %{data: data}),
|
||||||
changeset <- touch_changeset(changeset),
|
changeset <- touch_changeset(changeset),
|
||||||
{:ok, object} <- Repo.insert_or_update(changeset),
|
{:ok, object} <- Repo.insert_or_update(changeset),
|
||||||
{:ok, object} <- Object.set_cache(object) do
|
{:ok, object} <- Object.set_cache(object) do
|
||||||
|
@ -51,8 +70,8 @@ defp reinject_object(struct, data) do
|
||||||
|
|
||||||
def refetch_object(%Object{data: %{"id" => id}} = object) do
|
def refetch_object(%Object{data: %{"id" => id}} = object) do
|
||||||
with {:local, false} <- {:local, Object.local?(object)},
|
with {:local, false} <- {:local, Object.local?(object)},
|
||||||
{:ok, data} <- fetch_and_contain_remote_object_from_id(id),
|
{:ok, new_data} <- fetch_and_contain_remote_object_from_id(id),
|
||||||
{:ok, object} <- reinject_object(object, data) do
|
{:ok, object} <- reinject_object(object, new_data) do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
else
|
else
|
||||||
{:local, true} -> {:ok, object}
|
{:local, true} -> {:ok, object}
|
||||||
|
|
|
@ -638,6 +638,34 @@ def force_password_reset_async(user) do
|
||||||
@spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
|
@spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
|
||||||
def force_password_reset(user), do: update_password_reset_pending(user, true)
|
def force_password_reset(user), do: update_password_reset_pending(user, true)
|
||||||
|
|
||||||
|
# Used to auto-register LDAP accounts which won't have a password hash stored locally
|
||||||
|
def register_changeset_ldap(struct, params = %{password: password})
|
||||||
|
when is_nil(password) do
|
||||||
|
params = Map.put_new(params, :accepts_chat_messages, true)
|
||||||
|
|
||||||
|
params =
|
||||||
|
if Map.has_key?(params, :email) do
|
||||||
|
Map.put_new(params, :email, params[:email])
|
||||||
|
else
|
||||||
|
params
|
||||||
|
end
|
||||||
|
|
||||||
|
struct
|
||||||
|
|> cast(params, [
|
||||||
|
:name,
|
||||||
|
:nickname,
|
||||||
|
:email,
|
||||||
|
:accepts_chat_messages
|
||||||
|
])
|
||||||
|
|> validate_required([:name, :nickname])
|
||||||
|
|> unique_constraint(:nickname)
|
||||||
|
|> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
|
||||||
|
|> validate_format(:nickname, local_nickname_regex())
|
||||||
|
|> put_ap_id()
|
||||||
|
|> unique_constraint(:ap_id)
|
||||||
|
|> put_following_and_follower_address()
|
||||||
|
end
|
||||||
|
|
||||||
def register_changeset(struct, params \\ %{}, opts \\ []) do
|
def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||||
bio_limit = Config.get([:instance, :user_bio_length], 5000)
|
bio_limit = Config.get([:instance, :user_bio_length], 5000)
|
||||||
name_limit = Config.get([:instance, :user_name_length], 100)
|
name_limit = Config.get([:instance, :user_name_length], 100)
|
||||||
|
|
|
@ -130,6 +130,7 @@ defp compose_query({:external, _}, query), do: location_query(query, false)
|
||||||
defp compose_query({:active, _}, query) do
|
defp compose_query({:active, _}, query) do
|
||||||
User.restrict_deactivated(query)
|
User.restrict_deactivated(query)
|
||||||
|> where([u], not is_nil(u.nickname))
|
|> where([u], not is_nil(u.nickname))
|
||||||
|
|> where([u], u.approval_pending == false)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp compose_query({:legacy_active, _}, query) do
|
defp compose_query({:legacy_active, _}, query) do
|
||||||
|
|
|
@ -66,7 +66,7 @@ defp check_remote_limit(%{"object" => %{"content" => content}}) when not is_nil(
|
||||||
|
|
||||||
defp check_remote_limit(_), do: true
|
defp check_remote_limit(_), do: true
|
||||||
|
|
||||||
defp increase_note_count_if_public(actor, object) do
|
def increase_note_count_if_public(actor, object) do
|
||||||
if is_public?(object), do: User.increase_note_count(actor), else: {:ok, actor}
|
if is_public?(object), do: User.increase_note_count(actor), else: {:ok, actor}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -85,17 +85,7 @@ defp increase_replies_count_if_reply(%{
|
||||||
|
|
||||||
defp increase_replies_count_if_reply(_create_data), do: :noop
|
defp increase_replies_count_if_reply(_create_data), do: :noop
|
||||||
|
|
||||||
defp increase_poll_votes_if_vote(%{
|
@object_types ["ChatMessage", "Question", "Answer"]
|
||||||
"object" => %{"inReplyTo" => reply_ap_id, "name" => name},
|
|
||||||
"type" => "Create",
|
|
||||||
"actor" => actor
|
|
||||||
}) do
|
|
||||||
Object.increase_vote_count(reply_ap_id, name, actor)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp increase_poll_votes_if_vote(_create_data), do: :noop
|
|
||||||
|
|
||||||
@object_types ["ChatMessage"]
|
|
||||||
@spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
|
@spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
|
||||||
def persist(%{"type" => type} = object, meta) when type in @object_types do
|
def persist(%{"type" => type} = object, meta) when type in @object_types do
|
||||||
with {:ok, object} <- Object.create(object) do
|
with {:ok, object} <- Object.create(object) do
|
||||||
|
@ -258,7 +248,6 @@ defp do_create(%{to: to, actor: actor, context: context, object: object} = param
|
||||||
with {:ok, activity} <- insert(create_data, local, fake),
|
with {:ok, activity} <- insert(create_data, local, fake),
|
||||||
{:fake, false, activity} <- {:fake, fake, activity},
|
{:fake, false, activity} <- {:fake, fake, activity},
|
||||||
_ <- increase_replies_count_if_reply(create_data),
|
_ <- increase_replies_count_if_reply(create_data),
|
||||||
_ <- increase_poll_votes_if_vote(create_data),
|
|
||||||
{:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
|
{:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
|
||||||
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
|
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
|
||||||
_ <- notify_and_stream(activity),
|
_ <- notify_and_stream(activity),
|
||||||
|
|
|
@ -80,6 +80,13 @@ def delete(actor, object_id) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def create(actor, object, recipients) do
|
def create(actor, object, recipients) do
|
||||||
|
context =
|
||||||
|
if is_map(object) do
|
||||||
|
object["context"]
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
{:ok,
|
{:ok,
|
||||||
%{
|
%{
|
||||||
"id" => Utils.generate_activity_id(),
|
"id" => Utils.generate_activity_id(),
|
||||||
|
@ -88,7 +95,8 @@ def create(actor, object, recipients) do
|
||||||
"object" => object,
|
"object" => object,
|
||||||
"type" => "Create",
|
"type" => "Create",
|
||||||
"published" => DateTime.utc_now() |> DateTime.to_iso8601()
|
"published" => DateTime.utc_now() |> DateTime.to_iso8601()
|
||||||
}, []}
|
}
|
||||||
|
|> Pleroma.Maps.put_if_present("context", context), []}
|
||||||
end
|
end
|
||||||
|
|
||||||
def chat_message(actor, recipient, content, opts \\ []) do
|
def chat_message(actor, recipient, content, opts \\ []) do
|
||||||
|
@ -115,6 +123,22 @@ def chat_message(actor, recipient, content, opts \\ []) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def answer(user, object, name) do
|
||||||
|
{:ok,
|
||||||
|
%{
|
||||||
|
"type" => "Answer",
|
||||||
|
"actor" => user.ap_id,
|
||||||
|
"attributedTo" => user.ap_id,
|
||||||
|
"cc" => [object.data["actor"]],
|
||||||
|
"to" => [],
|
||||||
|
"name" => name,
|
||||||
|
"inReplyTo" => object.data["id"],
|
||||||
|
"context" => object.data["context"],
|
||||||
|
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||||
|
"id" => Utils.generate_object_id()
|
||||||
|
}, []}
|
||||||
|
end
|
||||||
|
|
||||||
@spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
|
@spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
|
||||||
def tombstone(actor, id) do
|
def tombstone(actor, id) do
|
||||||
{:ok,
|
{:ok,
|
||||||
|
|
|
@ -14,13 +14,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
|
||||||
|
|
||||||
|
@ -112,17 +115,40 @@ def validate(%{"type" => "ChatMessage"} = object, meta) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate(%{"type" => "Question"} = object, meta) do
|
||||||
|
with {:ok, object} <-
|
||||||
|
object
|
||||||
|
|> QuestionValidator.cast_and_validate()
|
||||||
|
|> Ecto.Changeset.apply_action(:insert) do
|
||||||
|
object = stringify_keys(object)
|
||||||
|
{:ok, object, meta}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate(%{"type" => "Answer"} = object, meta) do
|
||||||
|
with {:ok, object} <-
|
||||||
|
object
|
||||||
|
|> AnswerValidator.cast_and_validate()
|
||||||
|
|> Ecto.Changeset.apply_action(:insert) do
|
||||||
|
object = stringify_keys(object)
|
||||||
|
{:ok, object, meta}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def validate(%{"type" => "EmojiReact"} = object, meta) do
|
def validate(%{"type" => "EmojiReact"} = object, meta) do
|
||||||
with {:ok, object} <-
|
with {:ok, object} <-
|
||||||
object
|
object
|
||||||
|> EmojiReactValidator.cast_and_validate()
|
|> EmojiReactValidator.cast_and_validate()
|
||||||
|> Ecto.Changeset.apply_action(:insert) do
|
|> Ecto.Changeset.apply_action(:insert) do
|
||||||
object = stringify_keys(object |> Map.from_struct())
|
object = stringify_keys(object)
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate(%{"type" => "Create", "object" => object} = create_activity, meta) do
|
def validate(
|
||||||
|
%{"type" => "Create", "object" => %{"type" => "ChatMessage"} = object} = create_activity,
|
||||||
|
meta
|
||||||
|
) do
|
||||||
with {:ok, object_data} <- cast_and_apply(object),
|
with {:ok, object_data} <- cast_and_apply(object),
|
||||||
meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
|
meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
|
||||||
{:ok, create_activity} <-
|
{:ok, create_activity} <-
|
||||||
|
@ -134,12 +160,28 @@ def validate(%{"type" => "Create", "object" => object} = create_activity, meta)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate(
|
||||||
|
%{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
|
||||||
|
meta
|
||||||
|
)
|
||||||
|
when objtype in ["Question", "Answer"] do
|
||||||
|
with {:ok, object_data} <- cast_and_apply(object),
|
||||||
|
meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
|
||||||
|
{:ok, create_activity} <-
|
||||||
|
create_activity
|
||||||
|
|> CreateGenericValidator.cast_and_validate(meta)
|
||||||
|
|> Ecto.Changeset.apply_action(:insert) do
|
||||||
|
create_activity = stringify_keys(create_activity)
|
||||||
|
{:ok, create_activity, meta}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def validate(%{"type" => "Announce"} = object, meta) do
|
def validate(%{"type" => "Announce"} = object, meta) do
|
||||||
with {:ok, object} <-
|
with {:ok, object} <-
|
||||||
object
|
object
|
||||||
|> AnnounceValidator.cast_and_validate()
|
|> AnnounceValidator.cast_and_validate()
|
||||||
|> Ecto.Changeset.apply_action(:insert) do
|
|> Ecto.Changeset.apply_action(:insert) do
|
||||||
object = stringify_keys(object |> Map.from_struct())
|
object = stringify_keys(object)
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -148,8 +190,17 @@ def cast_and_apply(%{"type" => "ChatMessage"} = object) do
|
||||||
ChatMessageValidator.cast_and_apply(object)
|
ChatMessageValidator.cast_and_apply(object)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cast_and_apply(%{"type" => "Question"} = object) do
|
||||||
|
QuestionValidator.cast_and_apply(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_apply(%{"type" => "Answer"} = object) do
|
||||||
|
AnswerValidator.cast_and_apply(object)
|
||||||
|
end
|
||||||
|
|
||||||
def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
|
def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
|
||||||
|
|
||||||
|
# is_struct/1 isn't present in Elixir 1.8.x
|
||||||
def stringify_keys(%{__struct__: _} = object) do
|
def stringify_keys(%{__struct__: _} = object) do
|
||||||
object
|
object
|
||||||
|> Map.from_struct()
|
|> Map.from_struct()
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
@derive Jason.Encoder
|
||||||
|
|
||||||
|
embedded_schema do
|
||||||
|
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||||
|
field(:to, {:array, :string}, default: [])
|
||||||
|
field(:cc, {:array, :string}, default: [])
|
||||||
|
|
||||||
|
# is this actually needed?
|
||||||
|
field(:bto, {:array, :string}, default: [])
|
||||||
|
field(:bcc, {:array, :string}, default: [])
|
||||||
|
|
||||||
|
field(:type, :string)
|
||||||
|
field(:name, :string)
|
||||||
|
field(:inReplyTo, :string)
|
||||||
|
field(:attributedTo, ObjectValidators.ObjectID)
|
||||||
|
|
||||||
|
# TODO: Remove actor on objects
|
||||||
|
field(:actor, ObjectValidators.ObjectID)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_apply(data) do
|
||||||
|
data
|
||||||
|
|> cast_data()
|
||||||
|
|> apply_action(:insert)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_validate(data) do
|
||||||
|
data
|
||||||
|
|> cast_data()
|
||||||
|
|> validate_data()
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_data(data) do
|
||||||
|
%__MODULE__{}
|
||||||
|
|> changeset(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(struct, data) do
|
||||||
|
struct
|
||||||
|
|> cast(data, __schema__(:fields))
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_data(data_cng) do
|
||||||
|
data_cng
|
||||||
|
|> validate_inclusion(:type, ["Answer"])
|
||||||
|
|> validate_required([:id, :inReplyTo, :name, :attributedTo, :actor])
|
||||||
|
|> CommonValidations.validate_any_presence([:cc, :to])
|
||||||
|
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|
||||||
|
|> CommonValidations.validate_actor_presence()
|
||||||
|
|> CommonValidations.validate_host_match()
|
||||||
|
end
|
||||||
|
end
|
|
@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
def validate_recipients_presence(cng, fields \\ [:to, :cc]) do
|
def validate_any_presence(cng, fields) do
|
||||||
non_empty =
|
non_empty =
|
||||||
fields
|
fields
|
||||||
|> Enum.map(fn field -> get_field(cng, field) end)
|
|> Enum.map(fn field -> get_field(cng, field) end)
|
||||||
|
@ -24,7 +24,7 @@ def validate_recipients_presence(cng, fields \\ [:to, :cc]) do
|
||||||
fields
|
fields
|
||||||
|> Enum.reduce(cng, fn field, cng ->
|
|> Enum.reduce(cng, fn field, cng ->
|
||||||
cng
|
cng
|
||||||
|> add_error(field, "no recipients in any field")
|
|> add_error(field, "none of #{inspect(fields)} present")
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -82,4 +82,60 @@ def validate_object_or_user_presence(cng, options \\ []) do
|
||||||
|
|
||||||
if actor_cng.valid?, do: actor_cng, else: object_cng
|
if actor_cng.valid?, do: actor_cng, else: object_cng
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_host_match(cng, fields \\ [:id, :actor]) do
|
||||||
|
if same_domain?(cng, fields) do
|
||||||
|
cng
|
||||||
|
else
|
||||||
|
fields
|
||||||
|
|> Enum.reduce(cng, fn field, cng ->
|
||||||
|
cng
|
||||||
|
|> add_error(field, "hosts of #{inspect(fields)} aren't matching")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_fields_match(cng, fields) do
|
||||||
|
if map_unique?(cng, fields) do
|
||||||
|
cng
|
||||||
|
else
|
||||||
|
fields
|
||||||
|
|> Enum.reduce(cng, fn field, cng ->
|
||||||
|
cng
|
||||||
|
|> add_error(field, "Fields #{inspect(fields)} aren't matching")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp map_unique?(cng, fields, func \\ & &1) do
|
||||||
|
Enum.reduce_while(fields, nil, fn field, acc ->
|
||||||
|
value =
|
||||||
|
cng
|
||||||
|
|> get_field(field)
|
||||||
|
|> func.()
|
||||||
|
|
||||||
|
case {value, acc} do
|
||||||
|
{value, nil} -> {:cont, value}
|
||||||
|
{value, value} -> {:cont, value}
|
||||||
|
_ -> {:halt, false}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def same_domain?(cng, fields \\ [:actor, :object]) do
|
||||||
|
map_unique?(cng, fields, fn value -> URI.parse(value).host end)
|
||||||
|
end
|
||||||
|
|
||||||
|
# This figures out if a user is able to create, delete or modify something
|
||||||
|
# based on the domain and superuser status
|
||||||
|
def validate_modification_rights(cng) do
|
||||||
|
actor = User.get_cached_by_ap_id(get_field(cng, :actor))
|
||||||
|
|
||||||
|
if User.superuser?(actor) || same_domain?(cng) do
|
||||||
|
cng
|
||||||
|
else
|
||||||
|
cng
|
||||||
|
|> add_error(:actor, "is not allowed to modify object")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
# Code based on CreateChatMessageValidator
|
||||||
|
# NOTES
|
||||||
|
# - doesn't embed, will only get the object id
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
|
alias Pleroma.Object
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
|
||||||
|
embedded_schema do
|
||||||
|
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||||
|
field(:actor, ObjectValidators.ObjectID)
|
||||||
|
field(:type, :string)
|
||||||
|
field(:to, ObjectValidators.Recipients, default: [])
|
||||||
|
field(:cc, ObjectValidators.Recipients, default: [])
|
||||||
|
field(:object, ObjectValidators.ObjectID)
|
||||||
|
field(:expires_at, ObjectValidators.DateTime)
|
||||||
|
|
||||||
|
# Should be moved to object, done for CommonAPI.Utils.make_context
|
||||||
|
field(:context, :string)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_data(data, meta \\ []) do
|
||||||
|
data = fix(data, meta)
|
||||||
|
|
||||||
|
%__MODULE__{}
|
||||||
|
|> changeset(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_apply(data) do
|
||||||
|
data
|
||||||
|
|> cast_data
|
||||||
|
|> apply_action(:insert)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_validate(data, meta \\ []) do
|
||||||
|
data
|
||||||
|
|> cast_data(meta)
|
||||||
|
|> validate_data(meta)
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(struct, data) do
|
||||||
|
struct
|
||||||
|
|> cast(data, __schema__(:fields))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fix_context(data, meta) do
|
||||||
|
if object = meta[:object_data] do
|
||||||
|
Map.put_new(data, "context", object["context"])
|
||||||
|
else
|
||||||
|
data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fix(data, meta) do
|
||||||
|
data
|
||||||
|
|> fix_context(meta)
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_data(cng, meta \\ []) do
|
||||||
|
cng
|
||||||
|
|> validate_required([:actor, :type, :object])
|
||||||
|
|> validate_inclusion(:type, ["Create"])
|
||||||
|
|> validate_actor_presence()
|
||||||
|
|> validate_any_presence([:to, :cc])
|
||||||
|
|> validate_actors_match(meta)
|
||||||
|
|> validate_context_match(meta)
|
||||||
|
|> validate_object_nonexistence()
|
||||||
|
|> validate_object_containment()
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_object_containment(cng) do
|
||||||
|
actor = get_field(cng, :actor)
|
||||||
|
|
||||||
|
cng
|
||||||
|
|> validate_change(:object, fn :object, object_id ->
|
||||||
|
%URI{host: object_id_host} = URI.parse(object_id)
|
||||||
|
%URI{host: actor_host} = URI.parse(actor)
|
||||||
|
|
||||||
|
if object_id_host == actor_host do
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
[{:object, "The host of the object id doesn't match with the host of the actor"}]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_object_nonexistence(cng) do
|
||||||
|
cng
|
||||||
|
|> validate_change(:object, fn :object, object_id ->
|
||||||
|
if Object.get_cached_by_ap_id(object_id) do
|
||||||
|
[{:object, "The object to create already exists"}]
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_actors_match(cng, meta) do
|
||||||
|
attributed_to = meta[:object_data]["attributedTo"] || meta[:object_data]["actor"]
|
||||||
|
|
||||||
|
cng
|
||||||
|
|> validate_change(:actor, fn :actor, actor ->
|
||||||
|
if actor == attributed_to do
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
[{:actor, "Actor doesn't match with object attributedTo"}]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_context_match(cng, %{object_data: %{"context" => object_context}}) do
|
||||||
|
cng
|
||||||
|
|> validate_change(:context, fn :context, context ->
|
||||||
|
if context == object_context do
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
[{:context, "context field not matching between Create and object (#{object_context})"}]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_context_match(cng, _), do: cng
|
||||||
|
end
|
|
@ -7,7 +7,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
alias Pleroma.User
|
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
@ -59,7 +58,7 @@ def validate_data(cng) do
|
||||||
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
||||||
|> validate_inclusion(:type, ["Delete"])
|
|> validate_inclusion(:type, ["Delete"])
|
||||||
|> validate_actor_presence()
|
|> validate_actor_presence()
|
||||||
|> validate_deletion_rights()
|
|> validate_modification_rights()
|
||||||
|> validate_object_or_user_presence(allowed_types: @deletable_types)
|
|> validate_object_or_user_presence(allowed_types: @deletable_types)
|
||||||
|> add_deleted_activity_id()
|
|> add_deleted_activity_id()
|
||||||
end
|
end
|
||||||
|
@ -68,31 +67,6 @@ def do_not_federate?(cng) do
|
||||||
!same_domain?(cng)
|
!same_domain?(cng)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp same_domain?(cng) do
|
|
||||||
actor_uri =
|
|
||||||
cng
|
|
||||||
|> get_field(:actor)
|
|
||||||
|> URI.parse()
|
|
||||||
|
|
||||||
object_uri =
|
|
||||||
cng
|
|
||||||
|> get_field(:object)
|
|
||||||
|> URI.parse()
|
|
||||||
|
|
||||||
object_uri.host == actor_uri.host
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_deletion_rights(cng) do
|
|
||||||
actor = User.get_cached_by_ap_id(get_field(cng, :actor))
|
|
||||||
|
|
||||||
if User.superuser?(actor) || same_domain?(cng) do
|
|
||||||
cng
|
|
||||||
else
|
|
||||||
cng
|
|
||||||
|> add_error(:actor, "is not allowed to delete object")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def cast_and_validate(data) do
|
def cast_and_validate(data) do
|
||||||
data
|
data
|
||||||
|> cast_data
|
|> cast_data
|
||||||
|
|
|
@ -34,7 +34,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
|
||||||
field(:replies_count, :integer, default: 0)
|
field(:replies_count, :integer, default: 0)
|
||||||
field(:like_count, :integer, default: 0)
|
field(:like_count, :integer, default: 0)
|
||||||
field(:announcement_count, :integer, default: 0)
|
field(:announcement_count, :integer, default: 0)
|
||||||
field(:inRepyTo, :string)
|
field(:inReplyTo, :string)
|
||||||
field(:uri, ObjectValidators.Uri)
|
field(:uri, ObjectValidators.Uri)
|
||||||
|
|
||||||
field(:likes, {:array, :string}, default: [])
|
field(:likes, {:array, :string}, default: [])
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
|
||||||
|
embedded_schema do
|
||||||
|
field(:name, :string)
|
||||||
|
|
||||||
|
embeds_one :replies, Replies, primary_key: false do
|
||||||
|
field(:totalItems, :integer)
|
||||||
|
field(:type, :string)
|
||||||
|
end
|
||||||
|
|
||||||
|
field(:type, :string)
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(struct, data) do
|
||||||
|
struct
|
||||||
|
|> cast(data, [:name, :type])
|
||||||
|
|> cast_embed(:replies, with: &replies_changeset/2)
|
||||||
|
|> validate_inclusion(:type, ["Note"])
|
||||||
|
|> validate_required([:name, :type])
|
||||||
|
end
|
||||||
|
|
||||||
|
def replies_changeset(struct, data) do
|
||||||
|
struct
|
||||||
|
|> cast(data, [:totalItems, :type])
|
||||||
|
|> validate_inclusion(:type, ["Collection"])
|
||||||
|
|> validate_required([:type])
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,127 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
@derive Jason.Encoder
|
||||||
|
|
||||||
|
# Extends from NoteValidator
|
||||||
|
embedded_schema do
|
||||||
|
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||||
|
field(:to, {:array, :string}, default: [])
|
||||||
|
field(:cc, {:array, :string}, default: [])
|
||||||
|
field(:bto, {:array, :string}, default: [])
|
||||||
|
field(:bcc, {:array, :string}, default: [])
|
||||||
|
# TODO: Write type
|
||||||
|
field(:tag, {:array, :map}, default: [])
|
||||||
|
field(:type, :string)
|
||||||
|
field(:content, :string)
|
||||||
|
field(:context, :string)
|
||||||
|
|
||||||
|
# TODO: Remove actor on objects
|
||||||
|
field(:actor, ObjectValidators.ObjectID)
|
||||||
|
|
||||||
|
field(:attributedTo, ObjectValidators.ObjectID)
|
||||||
|
field(:summary, :string)
|
||||||
|
field(:published, ObjectValidators.DateTime)
|
||||||
|
# TODO: Write type
|
||||||
|
field(:emoji, :map, default: %{})
|
||||||
|
field(:sensitive, :boolean, default: false)
|
||||||
|
embeds_many(:attachment, AttachmentValidator)
|
||||||
|
field(:replies_count, :integer, default: 0)
|
||||||
|
field(:like_count, :integer, default: 0)
|
||||||
|
field(:announcement_count, :integer, default: 0)
|
||||||
|
field(:inReplyTo, :string)
|
||||||
|
field(:uri, ObjectValidators.Uri)
|
||||||
|
# short identifier for PleromaFE to group statuses by context
|
||||||
|
field(:context_id, :integer)
|
||||||
|
|
||||||
|
field(:likes, {:array, :string}, default: [])
|
||||||
|
field(:announcements, {:array, :string}, default: [])
|
||||||
|
|
||||||
|
field(:closed, ObjectValidators.DateTime)
|
||||||
|
field(:voters, {:array, ObjectValidators.ObjectID}, default: [])
|
||||||
|
embeds_many(:anyOf, QuestionOptionsValidator)
|
||||||
|
embeds_many(:oneOf, QuestionOptionsValidator)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_apply(data) do
|
||||||
|
data
|
||||||
|
|> cast_data
|
||||||
|
|> apply_action(:insert)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_validate(data) do
|
||||||
|
data
|
||||||
|
|> cast_data()
|
||||||
|
|> validate_data()
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_data(data) do
|
||||||
|
%__MODULE__{}
|
||||||
|
|> changeset(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fix_closed(data) do
|
||||||
|
cond do
|
||||||
|
is_binary(data["closed"]) -> data
|
||||||
|
is_binary(data["endTime"]) -> Map.put(data, "closed", data["endTime"])
|
||||||
|
true -> Map.drop(data, ["closed"])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# based on Pleroma.Web.ActivityPub.Utils.lazy_put_objects_defaults
|
||||||
|
defp fix_defaults(data) do
|
||||||
|
%{data: %{"id" => context}, id: context_id} =
|
||||||
|
Utils.create_context(data["context"] || data["conversation"])
|
||||||
|
|
||||||
|
data
|
||||||
|
|> Map.put_new_lazy("published", &Utils.make_date/0)
|
||||||
|
|> Map.put_new("context", context)
|
||||||
|
|> Map.put_new("context_id", context_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fix_attribution(data) do
|
||||||
|
data
|
||||||
|
|> Map.put_new("actor", data["attributedTo"])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fix(data) do
|
||||||
|
data
|
||||||
|
|> fix_attribution()
|
||||||
|
|> fix_closed()
|
||||||
|
|> fix_defaults()
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(struct, data) do
|
||||||
|
data = fix(data)
|
||||||
|
|
||||||
|
struct
|
||||||
|
|> cast(data, __schema__(:fields) -- [:anyOf, :oneOf, :attachment])
|
||||||
|
|> cast_embed(:attachment)
|
||||||
|
|> cast_embed(:anyOf)
|
||||||
|
|> cast_embed(:oneOf)
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_data(data_cng) do
|
||||||
|
data_cng
|
||||||
|
|> validate_inclusion(:type, ["Question"])
|
||||||
|
|> validate_required([:id, :actor, :attributedTo, :type, :context])
|
||||||
|
|> CommonValidations.validate_any_presence([:cc, :to])
|
||||||
|
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|
||||||
|
|> CommonValidations.validate_actor_presence()
|
||||||
|
|> CommonValidations.validate_any_presence([:oneOf, :anyOf])
|
||||||
|
|> CommonValidations.validate_host_match()
|
||||||
|
end
|
||||||
|
end
|
|
@ -13,7 +13,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator do
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:type, :string)
|
field(:type, :string)
|
||||||
field(:href, ObjectValidators.Uri)
|
field(:href, ObjectValidators.Uri)
|
||||||
field(:mediaType, :string)
|
field(:mediaType, :string, default: "application/octet-stream")
|
||||||
end
|
end
|
||||||
|
|
||||||
def changeset(struct, data) do
|
def changeset(struct, data) do
|
||||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
"""
|
"""
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Activity.Ir.Topics
|
alias Pleroma.Activity.Ir.Topics
|
||||||
|
alias Pleroma.ActivityExpiration
|
||||||
alias Pleroma.Chat
|
alias Pleroma.Chat
|
||||||
alias Pleroma.Chat.MessageReference
|
alias Pleroma.Chat.MessageReference
|
||||||
alias Pleroma.FollowingRelationship
|
alias Pleroma.FollowingRelationship
|
||||||
|
@ -19,6 +20,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.Push
|
alias Pleroma.Web.Push
|
||||||
alias Pleroma.Web.Streamer
|
alias Pleroma.Web.Streamer
|
||||||
|
alias Pleroma.Workers.BackgroundWorker
|
||||||
|
|
||||||
def handle(object, meta \\ [])
|
def handle(object, meta \\ [])
|
||||||
|
|
||||||
|
@ -135,10 +137,26 @@ def handle(%{data: %{"type" => "Like"}} = object, meta) do
|
||||||
# Tasks this handles
|
# Tasks this handles
|
||||||
# - Actually create object
|
# - Actually create object
|
||||||
# - Rollback if we couldn't create it
|
# - Rollback if we couldn't create it
|
||||||
|
# - Increase the user note count
|
||||||
|
# - Increase the reply count
|
||||||
|
# - Increase replies count
|
||||||
|
# - Set up ActivityExpiration
|
||||||
# - Set up notifications
|
# - Set up notifications
|
||||||
def handle(%{data: %{"type" => "Create"}} = activity, meta) do
|
def handle(%{data: %{"type" => "Create"}} = activity, meta) do
|
||||||
with {:ok, _object, meta} <- handle_object_creation(meta[:object_data], meta) do
|
with {:ok, object, meta} <- handle_object_creation(meta[:object_data], meta),
|
||||||
|
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||||
{:ok, notifications} = Notification.create_notifications(activity, do_send: false)
|
{:ok, notifications} = Notification.create_notifications(activity, do_send: false)
|
||||||
|
{:ok, _user} = ActivityPub.increase_note_count_if_public(user, object)
|
||||||
|
|
||||||
|
if in_reply_to = object.data["inReplyTo"] do
|
||||||
|
Object.increase_replies_count(in_reply_to)
|
||||||
|
end
|
||||||
|
|
||||||
|
if expires_at = activity.data["expires_at"] do
|
||||||
|
ActivityExpiration.create(activity, expires_at)
|
||||||
|
end
|
||||||
|
|
||||||
|
BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id})
|
||||||
|
|
||||||
meta =
|
meta =
|
||||||
meta
|
meta
|
||||||
|
@ -268,9 +286,27 @@ def handle_object_creation(%{"type" => "ChatMessage"} = object, meta) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do
|
||||||
|
with {:ok, object, meta} <- Pipeline.common_pipeline(object_map, meta) do
|
||||||
|
Object.increase_vote_count(
|
||||||
|
object.data["inReplyTo"],
|
||||||
|
object.data["name"],
|
||||||
|
object.data["actor"]
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, object, meta}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_object_creation(%{"type" => "Question"} = object, meta) do
|
||||||
|
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
|
||||||
|
{:ok, object, meta}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Nothing to do
|
# Nothing to do
|
||||||
def handle_object_creation(object) do
|
def handle_object_creation(object, meta) do
|
||||||
{:ok, object}
|
{:ok, object, meta}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp undo_like(nil, object), do: delete_object(object)
|
defp undo_like(nil, object), do: delete_object(object)
|
||||||
|
|
|
@ -157,7 +157,12 @@ def fix_addressing(object) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_actor(%{"attributedTo" => actor} = object) do
|
def fix_actor(%{"attributedTo" => actor} = object) do
|
||||||
Map.put(object, "actor", Containment.get_actor(%{"actor" => actor}))
|
actor = Containment.get_actor(%{"actor" => actor})
|
||||||
|
|
||||||
|
# TODO: Remove actor field for Objects
|
||||||
|
object
|
||||||
|
|> Map.put("actor", actor)
|
||||||
|
|> Map.put("attributedTo", actor)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_in_reply_to(object, options \\ [])
|
def fix_in_reply_to(object, options \\ [])
|
||||||
|
@ -240,13 +245,17 @@ def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachm
|
||||||
|
|
||||||
if href do
|
if href do
|
||||||
attachment_url =
|
attachment_url =
|
||||||
%{"href" => href}
|
%{
|
||||||
|
"href" => href,
|
||||||
|
"type" => Map.get(url || %{}, "type", "Link")
|
||||||
|
}
|
||||||
|> Maps.put_if_present("mediaType", media_type)
|
|> Maps.put_if_present("mediaType", media_type)
|
||||||
|> Maps.put_if_present("type", Map.get(url || %{}, "type"))
|
|
||||||
|
|
||||||
%{"url" => [attachment_url]}
|
%{
|
||||||
|
"url" => [attachment_url],
|
||||||
|
"type" => data["type"] || "Document"
|
||||||
|
}
|
||||||
|> Maps.put_if_present("mediaType", media_type)
|
|> Maps.put_if_present("mediaType", media_type)
|
||||||
|> Maps.put_if_present("type", data["type"])
|
|
||||||
|> Maps.put_if_present("name", data["name"])
|
|> Maps.put_if_present("name", data["name"])
|
||||||
else
|
else
|
||||||
nil
|
nil
|
||||||
|
@ -419,6 +428,29 @@ defp get_reported(objects) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Compatibility wrapper for Mastodon votes
|
||||||
|
defp handle_create(%{"object" => %{"type" => "Answer"}} = data, _user) do
|
||||||
|
handle_incoming(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_create(%{"object" => object} = data, user) do
|
||||||
|
%{
|
||||||
|
to: data["to"],
|
||||||
|
object: object,
|
||||||
|
actor: user,
|
||||||
|
context: object["context"],
|
||||||
|
local: false,
|
||||||
|
published: data["published"],
|
||||||
|
additional:
|
||||||
|
Map.take(data, [
|
||||||
|
"cc",
|
||||||
|
"directMessage",
|
||||||
|
"id"
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|> ActivityPub.create()
|
||||||
|
end
|
||||||
|
|
||||||
def handle_incoming(data, options \\ [])
|
def handle_incoming(data, options \\ [])
|
||||||
|
|
||||||
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
|
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
|
||||||
|
@ -457,30 +489,18 @@ def handle_incoming(
|
||||||
%{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
|
%{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
when objtype in ["Article", "Event", "Note", "Video", "Page", "Question", "Answer", "Audio"] do
|
when objtype in ["Article", "Event", "Note", "Video", "Page", "Audio"] do
|
||||||
actor = Containment.get_actor(data)
|
actor = Containment.get_actor(data)
|
||||||
|
|
||||||
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
|
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
|
||||||
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(actor),
|
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(actor) do
|
||||||
data <- Map.put(data, "actor", actor) |> fix_addressing() do
|
data =
|
||||||
object = fix_object(object, options)
|
data
|
||||||
|
|> Map.put("object", fix_object(object, options))
|
||||||
|
|> Map.put("actor", actor)
|
||||||
|
|> fix_addressing()
|
||||||
|
|
||||||
params = %{
|
with {:ok, created_activity} <- handle_create(data, user) do
|
||||||
to: data["to"],
|
|
||||||
object: object,
|
|
||||||
actor: user,
|
|
||||||
context: object["context"],
|
|
||||||
local: false,
|
|
||||||
published: data["published"],
|
|
||||||
additional:
|
|
||||||
Map.take(data, [
|
|
||||||
"cc",
|
|
||||||
"directMessage",
|
|
||||||
"id"
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
with {:ok, created_activity} <- ActivityPub.create(params) do
|
|
||||||
reply_depth = (options[:depth] || 0) + 1
|
reply_depth = (options[:depth] || 0) + 1
|
||||||
|
|
||||||
if Federator.allowed_thread_distance?(reply_depth) do
|
if Federator.allowed_thread_distance?(reply_depth) do
|
||||||
|
@ -614,9 +634,10 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Create", "object" => %{"type" => "ChatMessage"}} = data,
|
%{"type" => "Create", "object" => %{"type" => objtype}} = data,
|
||||||
_options
|
_options
|
||||||
) do
|
)
|
||||||
|
when objtype in ["Question", "Answer", "ChatMessage"] do
|
||||||
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
|
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
|
||||||
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
|
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
|
|
@ -28,10 +28,6 @@ def get_user(%Plug.Conn{} = conn) do
|
||||||
%User{} = user <- ldap_user(name, password) do
|
%User{} = user <- ldap_user(name, password) do
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
else
|
else
|
||||||
{:error, {:ldap_connection_error, _}} ->
|
|
||||||
# When LDAP is unavailable, try default authenticator
|
|
||||||
@base.get_user(conn)
|
|
||||||
|
|
||||||
{:ldap, _} ->
|
{:ldap, _} ->
|
||||||
@base.get_user(conn)
|
@base.get_user(conn)
|
||||||
|
|
||||||
|
@ -92,7 +88,7 @@ defp bind_user(connection, ldap, name, password) do
|
||||||
user
|
user
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
register_user(connection, base, uid, name, password)
|
register_user(connection, base, uid, name)
|
||||||
end
|
end
|
||||||
|
|
||||||
error ->
|
error ->
|
||||||
|
@ -100,35 +96,32 @@ defp bind_user(connection, ldap, name, password) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp register_user(connection, base, uid, name, password) do
|
defp register_user(connection, base, uid, name) do
|
||||||
case :eldap.search(connection, [
|
case :eldap.search(connection, [
|
||||||
{:base, to_charlist(base)},
|
{:base, to_charlist(base)},
|
||||||
{:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))},
|
{:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))},
|
||||||
{:scope, :eldap.wholeSubtree()},
|
{:scope, :eldap.wholeSubtree()},
|
||||||
{:attributes, ['mail', 'email']},
|
|
||||||
{:timeout, @search_timeout}
|
{:timeout, @search_timeout}
|
||||||
]) do
|
]) do
|
||||||
{:ok, {:eldap_search_result, [{:eldap_entry, _, attributes}], _}} ->
|
{:ok, {:eldap_search_result, [{:eldap_entry, _, attributes}], _}} ->
|
||||||
with {_, [mail]} <- List.keyfind(attributes, 'mail', 0) do
|
|
||||||
params = %{
|
params = %{
|
||||||
email: :erlang.list_to_binary(mail),
|
|
||||||
name: name,
|
name: name,
|
||||||
nickname: name,
|
nickname: name,
|
||||||
password: password,
|
password: nil
|
||||||
password_confirmation: password
|
|
||||||
}
|
}
|
||||||
|
|
||||||
changeset = User.register_changeset(%User{}, params)
|
params =
|
||||||
|
case List.keyfind(attributes, 'mail', 0) do
|
||||||
|
{_, [mail]} -> Map.put_new(params, :email, :erlang.list_to_binary(mail))
|
||||||
|
_ -> params
|
||||||
|
end
|
||||||
|
|
||||||
|
changeset = User.register_changeset_ldap(%User{}, params)
|
||||||
|
|
||||||
case User.register(changeset) do
|
case User.register(changeset) do
|
||||||
{:ok, user} -> user
|
{:ok, user} -> user
|
||||||
error -> error
|
error -> error
|
||||||
end
|
end
|
||||||
else
|
|
||||||
_ ->
|
|
||||||
Logger.error("Could not find LDAP attribute mail: #{inspect(attributes)}")
|
|
||||||
{:error, :ldap_registration_missing_attributes}
|
|
||||||
end
|
|
||||||
|
|
||||||
error ->
|
error ->
|
||||||
error
|
error
|
||||||
|
|
|
@ -308,18 +308,19 @@ def vote(user, %{data: %{"type" => "Question"}} = object, choices) do
|
||||||
{:ok, options, choices} <- normalize_and_validate_choices(choices, object) do
|
{:ok, options, choices} <- normalize_and_validate_choices(choices, object) do
|
||||||
answer_activities =
|
answer_activities =
|
||||||
Enum.map(choices, fn index ->
|
Enum.map(choices, fn index ->
|
||||||
answer_data = make_answer_data(user, object, Enum.at(options, index)["name"])
|
{:ok, answer_object, _meta} =
|
||||||
|
Builder.answer(user, object, Enum.at(options, index)["name"])
|
||||||
|
|
||||||
{:ok, activity} =
|
{:ok, activity_data, _meta} = Builder.create(user, answer_object, [])
|
||||||
ActivityPub.create(%{
|
|
||||||
to: answer_data["to"],
|
|
||||||
actor: user,
|
|
||||||
context: object.data["context"],
|
|
||||||
object: answer_data,
|
|
||||||
additional: %{"cc" => answer_data["cc"]}
|
|
||||||
})
|
|
||||||
|
|
||||||
activity
|
{:ok, activity, _meta} =
|
||||||
|
activity_data
|
||||||
|
|> Map.put("cc", answer_object["cc"])
|
||||||
|
|> Map.put("context", answer_object["context"])
|
||||||
|
|> Pipeline.common_pipeline(local: true)
|
||||||
|
|
||||||
|
# TODO: Do preload of Pleroma.Object in Pipeline
|
||||||
|
Activity.normalize(activity.data)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
object = Object.get_cached_by_ap_id(object.data["id"])
|
object = Object.get_cached_by_ap_id(object.data["id"])
|
||||||
|
@ -340,8 +341,13 @@ defp validate_existing_votes(%{ap_id: ap_id}, object) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_options_and_max_count(%{data: %{"anyOf" => any_of}}), do: {any_of, Enum.count(any_of)}
|
defp get_options_and_max_count(%{data: %{"anyOf" => any_of}})
|
||||||
defp get_options_and_max_count(%{data: %{"oneOf" => one_of}}), do: {one_of, 1}
|
when is_list(any_of) and any_of != [],
|
||||||
|
do: {any_of, Enum.count(any_of)}
|
||||||
|
|
||||||
|
defp get_options_and_max_count(%{data: %{"oneOf" => one_of}})
|
||||||
|
when is_list(one_of) and one_of != [],
|
||||||
|
do: {one_of, 1}
|
||||||
|
|
||||||
defp normalize_and_validate_choices(choices, object) do
|
defp normalize_and_validate_choices(choices, object) do
|
||||||
choices = Enum.map(choices, fn i -> if is_binary(i), do: String.to_integer(i), else: i end)
|
choices = Enum.map(choices, fn i -> if is_binary(i), do: String.to_integer(i), else: i end)
|
||||||
|
|
|
@ -548,17 +548,6 @@ def conversation_id_to_context(id) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_answer_data(%User{ap_id: ap_id}, object, name) do
|
|
||||||
%{
|
|
||||||
"type" => "Answer",
|
|
||||||
"actor" => ap_id,
|
|
||||||
"cc" => [object.data["actor"]],
|
|
||||||
"to" => [],
|
|
||||||
"name" => name,
|
|
||||||
"inReplyTo" => object.data["id"]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_character_limit("" = _full_payload, [] = _attachments) do
|
def validate_character_limit("" = _full_payload, [] = _attachments) do
|
||||||
{:error, dgettext("errors", "Cannot post an empty status without attachments")}
|
{:error, dgettext("errors", "Cannot post an empty status without attachments")}
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,6 +18,12 @@ def falsy_param?(value),
|
||||||
|
|
||||||
def truthy_param?(value), do: not falsy_param?(value)
|
def truthy_param?(value), do: not falsy_param?(value)
|
||||||
|
|
||||||
|
def json_response(conn, status, _) when status in [204, :no_content] do
|
||||||
|
conn
|
||||||
|
|> put_resp_header("content-type", "application/json")
|
||||||
|
|> send_resp(status, "")
|
||||||
|
end
|
||||||
|
|
||||||
def json_response(conn, status, json) do
|
def json_response(conn, status, json) do
|
||||||
conn
|
conn
|
||||||
|> put_status(status)
|
|> put_status(status)
|
||||||
|
|
|
@ -226,7 +226,7 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
|
||||||
with changeset <- User.update_changeset(user, user_params),
|
with changeset <- User.update_changeset(user, user_params),
|
||||||
{:ok, unpersisted_user} <- Ecto.Changeset.apply_action(changeset, :update),
|
{:ok, unpersisted_user} <- Ecto.Changeset.apply_action(changeset, :update),
|
||||||
updated_object <-
|
updated_object <-
|
||||||
Pleroma.Web.ActivityPub.UserView.render("user.json", user: user)
|
Pleroma.Web.ActivityPub.UserView.render("user.json", user: unpersisted_user)
|
||||||
|> Map.delete("@context"),
|
|> Map.delete("@context"),
|
||||||
{:ok, update_data, []} <- Builder.update(user, updated_object),
|
{:ok, update_data, []} <- Builder.update(user, updated_object),
|
||||||
{:ok, _update, _} <-
|
{:ok, _update, _} <-
|
||||||
|
|
|
@ -28,10 +28,10 @@ def render("show.json", %{object: object, multiple: multiple, options: options}
|
||||||
|
|
||||||
def render("show.json", %{object: object} = params) do
|
def render("show.json", %{object: object} = params) do
|
||||||
case object.data do
|
case object.data do
|
||||||
%{"anyOf" => options} when is_list(options) ->
|
%{"anyOf" => [_ | _] = options} ->
|
||||||
render(__MODULE__, "show.json", Map.merge(params, %{multiple: true, options: options}))
|
render(__MODULE__, "show.json", Map.merge(params, %{multiple: true, options: options}))
|
||||||
|
|
||||||
%{"oneOf" => options} when is_list(options) ->
|
%{"oneOf" => [_ | _] = options} ->
|
||||||
render(__MODULE__, "show.json", Map.merge(params, %{multiple: false, options: options}))
|
render(__MODULE__, "show.json", Map.merge(params, %{multiple: false, options: options}))
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -40,14 +40,12 @@ def render("show.json", %{object: object} = params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp end_time_and_expired(object) do
|
defp end_time_and_expired(object) do
|
||||||
case object.data["closed"] || object.data["endTime"] do
|
if object.data["closed"] do
|
||||||
end_time when is_binary(end_time) ->
|
end_time = NaiveDateTime.from_iso8601!(object.data["closed"])
|
||||||
end_time = NaiveDateTime.from_iso8601!(end_time)
|
|
||||||
expired = NaiveDateTime.compare(end_time, NaiveDateTime.utc_now()) == :lt
|
expired = NaiveDateTime.compare(end_time, NaiveDateTime.utc_now()) == :lt
|
||||||
|
|
||||||
{Utils.to_masto_date(end_time), expired}
|
{Utils.to_masto_date(end_time), expired}
|
||||||
|
else
|
||||||
_ ->
|
|
||||||
{nil, false}
|
{nil, false}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
2
mix.exs
2
mix.exs
|
@ -127,7 +127,7 @@ defp deps do
|
||||||
{:pbkdf2_elixir, "~> 1.2"},
|
{:pbkdf2_elixir, "~> 1.2"},
|
||||||
{:bcrypt_elixir, "~> 2.2"},
|
{:bcrypt_elixir, "~> 2.2"},
|
||||||
{:trailing_format_plug, "~> 0.0.7"},
|
{:trailing_format_plug, "~> 0.0.7"},
|
||||||
{:fast_sanitize, "~> 0.1"},
|
{:fast_sanitize, "~> 0.2.0"},
|
||||||
{:html_entities, "~> 0.5", override: true},
|
{:html_entities, "~> 0.5", override: true},
|
||||||
{:phoenix_html, "~> 2.14"},
|
{:phoenix_html, "~> 2.14"},
|
||||||
{:calendar, "~> 1.0"},
|
{:calendar, "~> 1.0"},
|
||||||
|
|
5
mix.lock
5
mix.lock
|
@ -45,8 +45,8 @@
|
||||||
"ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"},
|
"ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"},
|
||||||
"excoveralls": {:hex, :excoveralls, "0.13.1", "b9f1697f7c9e0cfe15d1a1d737fb169c398803ffcbc57e672aa007e9fd42864c", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b4bb550e045def1b4d531a37fb766cbbe1307f7628bf8f0414168b3f52021cce"},
|
"excoveralls": {:hex, :excoveralls, "0.13.1", "b9f1697f7c9e0cfe15d1a1d737fb169c398803ffcbc57e672aa007e9fd42864c", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b4bb550e045def1b4d531a37fb766cbbe1307f7628bf8f0414168b3f52021cce"},
|
||||||
"exexec": {:hex, :exexec, "0.2.0", "a6ffc48cba3ac9420891b847e4dc7120692fb8c08c9e82220ebddc0bb8d96103", [:mix], [{:erlexec, "~> 1.10", [hex: :erlexec, repo: "hexpm", optional: false]}], "hexpm", "312cd1c9befba9e078e57f3541e4f4257eabda6eb9c348154fe899d6ac633299"},
|
"exexec": {:hex, :exexec, "0.2.0", "a6ffc48cba3ac9420891b847e4dc7120692fb8c08c9e82220ebddc0bb8d96103", [:mix], [{:erlexec, "~> 1.10", [hex: :erlexec, repo: "hexpm", optional: false]}], "hexpm", "312cd1c9befba9e078e57f3541e4f4257eabda6eb9c348154fe899d6ac633299"},
|
||||||
"fast_html": {:hex, :fast_html, "1.0.3", "2cc0d4b68496266a1530e0c852cafeaede0bd10cfdee26fda50dc696c203162f", [:make, :mix], [], "hexpm", "ab3d782b639d3c4655fbaec0f9d032c91f8cab8dd791ac7469c2381bc7c32f85"},
|
"fast_html": {:hex, :fast_html, "2.0.1", "e126c74d287768ae78c48938da6711164517300d108a78f8a38993df8d588335", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "bdd6f8525c95ad391a4f10d9a1b3da4cea94078ec8638487aa8c24015ad9393a"},
|
||||||
"fast_sanitize": {:hex, :fast_sanitize, "0.1.7", "2a7cd8734c88a2de6de55022104f8a3b87f1fdbe8bbf131d9049764b53d50d0d", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f39fe8ea08fbac17487c30bf09b7d9f3e12472e51fb07a88ffeb8fd17da8ab67"},
|
"fast_sanitize": {:hex, :fast_sanitize, "0.2.0", "004b40d5bbecda182b6fdba762a51fffd3501e689e8eafe196e1a97eb0caf733", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "11fcb37f26d272a3a2aff861872bf100be4eeacea69505908b8cdbcea5b0813a"},
|
||||||
"flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
|
"flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
|
||||||
"floki": {:hex, :floki, "0.27.0", "6b29a14283f1e2e8fad824bc930eaa9477c462022075df6bea8f0ad811c13599", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "583b8c13697c37179f1f82443bcc7ad2f76fbc0bf4c186606eebd658f7f2631b"},
|
"floki": {:hex, :floki, "0.27.0", "6b29a14283f1e2e8fad824bc930eaa9477c462022075df6bea8f0ad811c13599", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "583b8c13697c37179f1f82443bcc7ad2f76fbc0bf4c186606eebd658f7f2631b"},
|
||||||
"gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
|
"gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
|
||||||
|
@ -79,6 +79,7 @@
|
||||||
"mox": {:hex, :mox, "0.5.2", "55a0a5ba9ccc671518d068c8dddd20eeb436909ea79d1799e2209df7eaa98b6c", [:mix], [], "hexpm", "df4310628cd628ee181df93f50ddfd07be3e5ecc30232d3b6aadf30bdfe6092b"},
|
"mox": {:hex, :mox, "0.5.2", "55a0a5ba9ccc671518d068c8dddd20eeb436909ea79d1799e2209df7eaa98b6c", [:mix], [], "hexpm", "df4310628cd628ee181df93f50ddfd07be3e5ecc30232d3b6aadf30bdfe6092b"},
|
||||||
"myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
|
"myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
|
||||||
"nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
|
"nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
|
||||||
|
"nimble_pool": {:hex, :nimble_pool, "0.1.0", "ffa9d5be27eee2b00b0c634eb649aa27f97b39186fec3c493716c2a33e784ec6", [:mix], [], "hexpm", "343a1eaa620ddcf3430a83f39f2af499fe2370390d4f785cd475b4df5acaf3f9"},
|
||||||
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
|
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
|
||||||
"oban": {:hex, :oban, "2.0.0", "e6ce70d94dd46815ec0882a1ffb7356df9a9d5b8a40a64ce5c2536617a447379", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cf574813bd048b98a698aa587c21367d2e06842d4e1b1993dcd6a696e9e633bd"},
|
"oban": {:hex, :oban, "2.0.0", "e6ce70d94dd46815ec0882a1ffb7356df9a9d5b8a40a64ce5c2536617a447379", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cf574813bd048b98a698aa587c21367d2e06842d4e1b1993dcd6a696e9e633bd"},
|
||||||
"open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "f296ac0924ba3cf79c7a588c4c252889df4c2edd", [ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"]},
|
"open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "f296ac0924ba3cf79c7a588c4c252889df4c2edd", [ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"]},
|
||||||
|
|
|
@ -18,8 +18,11 @@ defmodule Pleroma.Repo.Migrations.FixLegacyTags do
|
||||||
def change do
|
def change do
|
||||||
legacy_tags = Map.keys(@old_new_map)
|
legacy_tags = Map.keys(@old_new_map)
|
||||||
|
|
||||||
from(u in User, where: fragment("? && ?", u.tags, ^legacy_tags))
|
from(u in User,
|
||||||
|> Repo.all()
|
where: fragment("? && ?", u.tags, ^legacy_tags),
|
||||||
|
select: struct(u, [:tags, :id])
|
||||||
|
)
|
||||||
|
|> Repo.chunk_stream(100)
|
||||||
|> Enum.each(fn user ->
|
|> Enum.each(fn user ->
|
||||||
fix_tags_changeset(user)
|
fix_tags_changeset(user)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
|
|
|
@ -19,6 +19,7 @@ defmodule Pleroma.Emails.MailerTest do
|
||||||
test "not send email when mailer is disabled" do
|
test "not send email when mailer is disabled" do
|
||||||
Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false)
|
Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false)
|
||||||
Mailer.deliver(@email)
|
Mailer.deliver(@email)
|
||||||
|
:timer.sleep(100)
|
||||||
|
|
||||||
refute_email_sent(
|
refute_email_sent(
|
||||||
from: {"Pleroma", "noreply@example.com"},
|
from: {"Pleroma", "noreply@example.com"},
|
||||||
|
@ -30,6 +31,7 @@ test "not send email when mailer is disabled" do
|
||||||
|
|
||||||
test "send email" do
|
test "send email" do
|
||||||
Mailer.deliver(@email)
|
Mailer.deliver(@email)
|
||||||
|
:timer.sleep(100)
|
||||||
|
|
||||||
assert_email_sent(
|
assert_email_sent(
|
||||||
from: {"Pleroma", "noreply@example.com"},
|
from: {"Pleroma", "noreply@example.com"},
|
||||||
|
@ -41,6 +43,7 @@ test "send email" do
|
||||||
|
|
||||||
test "perform" do
|
test "perform" do
|
||||||
Mailer.perform(:deliver_async, @email, [])
|
Mailer.perform(:deliver_async, @email, [])
|
||||||
|
:timer.sleep(100)
|
||||||
|
|
||||||
assert_email_sent(
|
assert_email_sent(
|
||||||
from: {"Pleroma", "noreply@example.com"},
|
from: {"Pleroma", "noreply@example.com"},
|
||||||
|
|
|
@ -49,7 +49,6 @@
|
||||||
"en": "<p>Why is Tenshi eating a corndog so cute?</p>"
|
"en": "<p>Why is Tenshi eating a corndog so cute?</p>"
|
||||||
},
|
},
|
||||||
"endTime": "2019-05-11T09:03:36Z",
|
"endTime": "2019-05-11T09:03:36Z",
|
||||||
"closed": "2019-05-11T09:03:36Z",
|
|
||||||
"attachment": [],
|
"attachment": [],
|
||||||
"tag": [],
|
"tag": [],
|
||||||
"replies": {
|
"replies": {
|
||||||
|
|
99
test/fixtures/tesla_mock/poll_attachment.json
vendored
Normal file
99
test/fixtures/tesla_mock/poll_attachment.json
vendored
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://patch.cx/schemas/litepub-0.1.jsonld",
|
||||||
|
{
|
||||||
|
"@language": "und"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"actor": "https://patch.cx/users/rin",
|
||||||
|
"anyOf": [],
|
||||||
|
"attachment": [
|
||||||
|
{
|
||||||
|
"mediaType": "image/jpeg",
|
||||||
|
"name": "screenshot_mpv:Totoro@01:18:44.345.jpg",
|
||||||
|
"type": "Document",
|
||||||
|
"url": "https://shitposter.club/media/3bb4c4d402f8fdcc7f80963c3d7cf6f10f936897fd374922ade33199d2f86d87.jpg?name=screenshot_mpv%3ATotoro%4001%3A18%3A44.345.jpg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attributedTo": "https://patch.cx/users/rin",
|
||||||
|
"cc": [
|
||||||
|
"https://patch.cx/users/rin/followers"
|
||||||
|
],
|
||||||
|
"closed": "2020-06-19T23:22:02.754678Z",
|
||||||
|
"content": "<span class=\"h-card\"><a class=\"u-url mention\" data-user=\"9vwjTNzEWEM1TfkBGq\" href=\"https://mastodon.sdf.org/users/rinpatch\" rel=\"ugc\">@<span>rinpatch</span></a></span>",
|
||||||
|
"closed": "2019-09-19T00:32:36.785333",
|
||||||
|
"content": "can you vote on this poll?",
|
||||||
|
"id": "https://patch.cx/objects/tesla_mock/poll_attachment",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"name": "a",
|
||||||
|
"replies": {
|
||||||
|
"totalItems": 0,
|
||||||
|
"type": "Collection"
|
||||||
|
},
|
||||||
|
"type": "Note"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "A",
|
||||||
|
"replies": {
|
||||||
|
"totalItems": 0,
|
||||||
|
"type": "Collection"
|
||||||
|
},
|
||||||
|
"type": "Note"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Aa",
|
||||||
|
"replies": {
|
||||||
|
"totalItems": 0,
|
||||||
|
"type": "Collection"
|
||||||
|
},
|
||||||
|
"type": "Note"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AA",
|
||||||
|
"replies": {
|
||||||
|
"totalItems": 0,
|
||||||
|
"type": "Collection"
|
||||||
|
},
|
||||||
|
"type": "Note"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AAa",
|
||||||
|
"replies": {
|
||||||
|
"totalItems": 1,
|
||||||
|
"type": "Collection"
|
||||||
|
},
|
||||||
|
"type": "Note"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AAA",
|
||||||
|
"replies": {
|
||||||
|
"totalItems": 3,
|
||||||
|
"type": "Collection"
|
||||||
|
},
|
||||||
|
"type": "Note"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"published": "2020-06-19T23:12:02.786113Z",
|
||||||
|
"sensitive": false,
|
||||||
|
"summary": "",
|
||||||
|
"tag": [
|
||||||
|
{
|
||||||
|
"href": "https://mastodon.sdf.org/users/rinpatch",
|
||||||
|
"name": "@rinpatch@mastodon.sdf.org",
|
||||||
|
"type": "Mention"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public",
|
||||||
|
"https://mastodon.sdf.org/users/rinpatch"
|
||||||
|
],
|
||||||
|
"type": "Question",
|
||||||
|
"voters": [
|
||||||
|
"https://shitposter.club/users/moonman",
|
||||||
|
"https://skippers-bin.com/users/7v1w1r8ce6",
|
||||||
|
"https://mastodon.sdf.org/users/rinpatch",
|
||||||
|
"https://mastodon.social/users/emelie"
|
||||||
|
]
|
||||||
|
}
|
|
@ -177,6 +177,13 @@ test "handle HTTP 404 response" do
|
||||||
"https://mastodon.example.org/users/userisgone404"
|
"https://mastodon.example.org/users/userisgone404"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it can fetch pleroma polls with attachments" do
|
||||||
|
{:ok, object} =
|
||||||
|
Fetcher.fetch_object_from_id("https://patch.cx/objects/tesla_mock/poll_attachment")
|
||||||
|
|
||||||
|
assert object
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "pruning" do
|
describe "pruning" do
|
||||||
|
|
|
@ -56,6 +56,13 @@ defp request_content_type(%{conn: conn}) do
|
||||||
[conn: conn]
|
[conn: conn]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp empty_json_response(conn) do
|
||||||
|
body = response(conn, 204)
|
||||||
|
response_content_type(conn, :json)
|
||||||
|
|
||||||
|
body
|
||||||
|
end
|
||||||
|
|
||||||
defp json_response_and_validate_schema(
|
defp json_response_and_validate_schema(
|
||||||
%{
|
%{
|
||||||
private: %{
|
private: %{
|
||||||
|
@ -79,7 +86,7 @@ defp json_response_and_validate_schema(
|
||||||
end
|
end
|
||||||
|
|
||||||
schema = lookup[op_id].responses[status].content[content_type].schema
|
schema = lookup[op_id].responses[status].content[content_type].schema
|
||||||
json = json_response(conn, status)
|
json = if status == 204, do: empty_json_response(conn), else: json_response(conn, status)
|
||||||
|
|
||||||
case OpenApiSpex.cast_value(json, schema, spec) do
|
case OpenApiSpex.cast_value(json, schema, spec) do
|
||||||
{:ok, _data} ->
|
{:ok, _data} ->
|
||||||
|
|
|
@ -82,6 +82,14 @@ def get("https://mastodon.sdf.org/users/rinpatch", _, _, _) do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get("https://patch.cx/objects/tesla_mock/poll_attachment", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/poll_attachment.json")
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
def get(
|
def get(
|
||||||
"https://mastodon.social/.well-known/webfinger?resource=https://mastodon.social/users/emelie",
|
"https://mastodon.social/.well-known/webfinger?resource=https://mastodon.social/users/emelie",
|
||||||
_,
|
_,
|
||||||
|
|
|
@ -127,4 +127,43 @@ test "it turns OrderedCollection likes into empty arrays" do
|
||||||
assert Enum.empty?(Object.get_by_id(object2.id).data["likes"])
|
assert Enum.empty?(Object.get_by_id(object2.id).data["likes"])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "ensure_expiration" do
|
||||||
|
test "it adds to expiration old statuses" do
|
||||||
|
%{id: activity_id1} = insert(:note_activity)
|
||||||
|
|
||||||
|
%{id: activity_id2} =
|
||||||
|
insert(:note_activity, %{inserted_at: NaiveDateTime.from_iso8601!("2015-01-23 23:50:07")})
|
||||||
|
|
||||||
|
%{id: activity_id3} = activity3 = insert(:note_activity)
|
||||||
|
|
||||||
|
expires_at =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(60 * 61, :second)
|
||||||
|
|> NaiveDateTime.truncate(:second)
|
||||||
|
|
||||||
|
Pleroma.ActivityExpiration.create(activity3, expires_at)
|
||||||
|
|
||||||
|
Mix.Tasks.Pleroma.Database.run(["ensure_expiration"])
|
||||||
|
|
||||||
|
expirations =
|
||||||
|
Pleroma.ActivityExpiration
|
||||||
|
|> order_by(:activity_id)
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
|
assert [
|
||||||
|
%Pleroma.ActivityExpiration{
|
||||||
|
activity_id: ^activity_id1
|
||||||
|
},
|
||||||
|
%Pleroma.ActivityExpiration{
|
||||||
|
activity_id: ^activity_id2,
|
||||||
|
scheduled_at: ~N[2016-01-23 23:50:07]
|
||||||
|
},
|
||||||
|
%Pleroma.ActivityExpiration{
|
||||||
|
activity_id: ^activity_id3,
|
||||||
|
scheduled_at: ^expires_at
|
||||||
|
}
|
||||||
|
] = expirations
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Mix.Tasks.Pleroma.ReleaseEnvTest do
|
|
||||||
use ExUnit.Case
|
|
||||||
import ExUnit.CaptureIO, only: [capture_io: 1]
|
|
||||||
|
|
||||||
@path "config/pleroma.test.env"
|
|
||||||
|
|
||||||
def do_clean do
|
|
||||||
if File.exists?(@path) do
|
|
||||||
File.rm_rf(@path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
setup do
|
|
||||||
do_clean()
|
|
||||||
on_exit(fn -> do_clean() end)
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generate pleroma.env" do
|
|
||||||
assert capture_io(fn ->
|
|
||||||
Mix.Tasks.Pleroma.ReleaseEnv.run(["gen", "--path", @path, "--force"])
|
|
||||||
end) =~ "The file generated"
|
|
||||||
|
|
||||||
assert File.read!(@path) =~ "RELEASE_COOKIE="
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -87,7 +87,7 @@ test "it's invalid if the actor of the object and the actor of delete are from d
|
||||||
|
|
||||||
{:error, cng} = ObjectValidator.validate(invalid_other_actor, [])
|
{:error, cng} = ObjectValidator.validate(invalid_other_actor, [])
|
||||||
|
|
||||||
assert {:actor, {"is not allowed to delete object", []}} in cng.errors
|
assert {:actor, {"is not allowed to modify object", []}} in cng.errors
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it's valid if the actor of the object is a local superuser",
|
test "it's valid if the actor of the object is a local superuser",
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.Transmogrifier.AnswerHandlingTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
setup_all do
|
||||||
|
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
test "incoming, rewrites Note to Answer and increments vote counters" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
status: "suya...",
|
||||||
|
poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
|
||||||
|
})
|
||||||
|
|
||||||
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/mastodon-vote.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Kernel.put_in(["to"], user.ap_id)
|
||||||
|
|> Kernel.put_in(["object", "inReplyTo"], object.data["id"])
|
||||||
|
|> Kernel.put_in(["object", "to"], user.ap_id)
|
||||||
|
|
||||||
|
{:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
|
||||||
|
answer_object = Object.normalize(activity)
|
||||||
|
assert answer_object.data["type"] == "Answer"
|
||||||
|
assert answer_object.data["inReplyTo"] == object.data["id"]
|
||||||
|
|
||||||
|
new_object = Object.get_by_ap_id(object.data["id"])
|
||||||
|
assert new_object.data["replies_count"] == object.data["replies_count"]
|
||||||
|
|
||||||
|
assert Enum.any?(
|
||||||
|
new_object.data["oneOf"],
|
||||||
|
fn
|
||||||
|
%{"name" => "suya..", "replies" => %{"totalItems" => 1}} -> true
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "outgoing, rewrites Answer to Note" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, poll_activity} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
status: "suya...",
|
||||||
|
poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
|
||||||
|
})
|
||||||
|
|
||||||
|
poll_object = Object.normalize(poll_activity)
|
||||||
|
# TODO: Replace with CommonAPI vote creation when implemented
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/mastodon-vote.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Kernel.put_in(["to"], user.ap_id)
|
||||||
|
|> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
|
||||||
|
|> Kernel.put_in(["object", "to"], user.ap_id)
|
||||||
|
|
||||||
|
{:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
|
||||||
|
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
|
|
||||||
|
assert data["object"]["type"] == "Note"
|
||||||
|
end
|
||||||
|
end
|
123
test/web/activity_pub/transmogrifier/question_handling_test.exs
Normal file
123
test/web/activity_pub/transmogrifier/question_handling_test.exs
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.Transmogrifier.QuestionHandlingTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
setup_all do
|
||||||
|
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Mastodon Question activity" do
|
||||||
|
data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!()
|
||||||
|
|
||||||
|
{:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
object = Object.normalize(activity, false)
|
||||||
|
|
||||||
|
assert object.data["closed"] == "2019-05-11T09:03:36Z"
|
||||||
|
|
||||||
|
assert object.data["context"] == activity.data["context"]
|
||||||
|
|
||||||
|
assert object.data["context"] ==
|
||||||
|
"tag:mastodon.sdf.org,2019-05-10:objectId=15095122:objectType=Conversation"
|
||||||
|
|
||||||
|
assert object.data["context_id"]
|
||||||
|
|
||||||
|
assert object.data["anyOf"] == []
|
||||||
|
|
||||||
|
assert Enum.sort(object.data["oneOf"]) ==
|
||||||
|
Enum.sort([
|
||||||
|
%{
|
||||||
|
"name" => "25 char limit is dumb",
|
||||||
|
"replies" => %{"totalItems" => 0, "type" => "Collection"},
|
||||||
|
"type" => "Note"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"name" => "Dunno",
|
||||||
|
"replies" => %{"totalItems" => 0, "type" => "Collection"},
|
||||||
|
"type" => "Note"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"name" => "Everyone knows that!",
|
||||||
|
"replies" => %{"totalItems" => 1, "type" => "Collection"},
|
||||||
|
"type" => "Note"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"name" => "I can't even fit a funny",
|
||||||
|
"replies" => %{"totalItems" => 1, "type" => "Collection"},
|
||||||
|
"type" => "Note"
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, reply_activity} = CommonAPI.post(user, %{status: "hewwo", in_reply_to_id: activity.id})
|
||||||
|
|
||||||
|
reply_object = Object.normalize(reply_activity, false)
|
||||||
|
|
||||||
|
assert reply_object.data["context"] == object.data["context"]
|
||||||
|
assert reply_object.data["context_id"] == object.data["context_id"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Mastodon Question activity with HTML tags in plaintext" do
|
||||||
|
options = [
|
||||||
|
%{
|
||||||
|
"type" => "Note",
|
||||||
|
"name" => "<input type=\"date\">",
|
||||||
|
"replies" => %{"totalItems" => 0, "type" => "Collection"}
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"type" => "Note",
|
||||||
|
"name" => "<input type=\"date\"/>",
|
||||||
|
"replies" => %{"totalItems" => 0, "type" => "Collection"}
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"type" => "Note",
|
||||||
|
"name" => "<input type=\"date\" />",
|
||||||
|
"replies" => %{"totalItems" => 1, "type" => "Collection"}
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"type" => "Note",
|
||||||
|
"name" => "<input type=\"date\"></input>",
|
||||||
|
"replies" => %{"totalItems" => 1, "type" => "Collection"}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/mastodon-question-activity.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Kernel.put_in(["object", "oneOf"], options)
|
||||||
|
|
||||||
|
{:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
|
||||||
|
object = Object.normalize(activity, false)
|
||||||
|
|
||||||
|
assert Enum.sort(object.data["oneOf"]) == Enum.sort(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns an error if received a second time" do
|
||||||
|
data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!()
|
||||||
|
|
||||||
|
assert {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
assert {:error, {:validate_object, {:error, _}}} = Transmogrifier.handle_incoming(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "accepts a Question with no content" do
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/mastodon-question-activity.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Kernel.put_in(["object", "content"], "")
|
||||||
|
|
||||||
|
assert {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
end
|
||||||
|
end
|
|
@ -225,23 +225,6 @@ test "it works for incoming notices with hashtags" do
|
||||||
assert Enum.at(object.data["tag"], 2) == "moo"
|
assert Enum.at(object.data["tag"], 2) == "moo"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming questions" do
|
|
||||||
data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!()
|
|
||||||
|
|
||||||
{:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
|
|
||||||
|
|
||||||
object = Object.normalize(activity)
|
|
||||||
|
|
||||||
assert Enum.all?(object.data["oneOf"], fn choice ->
|
|
||||||
choice["name"] in [
|
|
||||||
"Dunno",
|
|
||||||
"Everyone knows that!",
|
|
||||||
"25 char limit is dumb",
|
|
||||||
"I can't even fit a funny"
|
|
||||||
]
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it works for incoming listens" do
|
test "it works for incoming listens" do
|
||||||
data = %{
|
data = %{
|
||||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||||
|
@ -271,38 +254,6 @@ test "it works for incoming listens" do
|
||||||
assert object.data["length"] == 180_000
|
assert object.data["length"] == 180_000
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it rewrites Note votes to Answers and increments vote counters on question activities" do
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, activity} =
|
|
||||||
CommonAPI.post(user, %{
|
|
||||||
status: "suya...",
|
|
||||||
poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
|
|
||||||
})
|
|
||||||
|
|
||||||
object = Object.normalize(activity)
|
|
||||||
|
|
||||||
data =
|
|
||||||
File.read!("test/fixtures/mastodon-vote.json")
|
|
||||||
|> Poison.decode!()
|
|
||||||
|> Kernel.put_in(["to"], user.ap_id)
|
|
||||||
|> Kernel.put_in(["object", "inReplyTo"], object.data["id"])
|
|
||||||
|> Kernel.put_in(["object", "to"], user.ap_id)
|
|
||||||
|
|
||||||
{:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
|
|
||||||
answer_object = Object.normalize(activity)
|
|
||||||
assert answer_object.data["type"] == "Answer"
|
|
||||||
object = Object.get_by_ap_id(object.data["id"])
|
|
||||||
|
|
||||||
assert Enum.any?(
|
|
||||||
object.data["oneOf"],
|
|
||||||
fn
|
|
||||||
%{"name" => "suya..", "replies" => %{"totalItems" => 1}} -> true
|
|
||||||
_ -> false
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it works for incoming notices with contentMap" do
|
test "it works for incoming notices with contentMap" do
|
||||||
data =
|
data =
|
||||||
File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!()
|
File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!()
|
||||||
|
@ -677,7 +628,8 @@ test "it remaps video URLs as attachments if necessary" do
|
||||||
%{
|
%{
|
||||||
"href" =>
|
"href" =>
|
||||||
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
|
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
|
||||||
"mediaType" => "video/mp4"
|
"mediaType" => "video/mp4",
|
||||||
|
"type" => "Link"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -696,7 +648,8 @@ test "it remaps video URLs as attachments if necessary" do
|
||||||
%{
|
%{
|
||||||
"href" =>
|
"href" =>
|
||||||
"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4",
|
"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4",
|
||||||
"mediaType" => "video/mp4"
|
"mediaType" => "video/mp4",
|
||||||
|
"type" => "Link"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1269,30 +1222,6 @@ test "successfully reserializes a message with AS2 objects in IR" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Rewrites Answers to Notes" do
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, poll_activity} =
|
|
||||||
CommonAPI.post(user, %{
|
|
||||||
status: "suya...",
|
|
||||||
poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
|
|
||||||
})
|
|
||||||
|
|
||||||
poll_object = Object.normalize(poll_activity)
|
|
||||||
# TODO: Replace with CommonAPI vote creation when implemented
|
|
||||||
data =
|
|
||||||
File.read!("test/fixtures/mastodon-vote.json")
|
|
||||||
|> Poison.decode!()
|
|
||||||
|> Kernel.put_in(["to"], user.ap_id)
|
|
||||||
|> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
|
|
||||||
|> Kernel.put_in(["object", "to"], user.ap_id)
|
|
||||||
|
|
||||||
{:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
|
|
||||||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
|
||||||
|
|
||||||
assert data["object"]["type"] == "Note"
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "fix_explicit_addressing" do
|
describe "fix_explicit_addressing" do
|
||||||
setup do
|
setup do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
@ -1540,8 +1469,13 @@ test "returns modified object when attachment is map" do
|
||||||
"attachment" => [
|
"attachment" => [
|
||||||
%{
|
%{
|
||||||
"mediaType" => "video/mp4",
|
"mediaType" => "video/mp4",
|
||||||
|
"type" => "Document",
|
||||||
"url" => [
|
"url" => [
|
||||||
%{"href" => "https://peertube.moe/stat-480.mp4", "mediaType" => "video/mp4"}
|
%{
|
||||||
|
"href" => "https://peertube.moe/stat-480.mp4",
|
||||||
|
"mediaType" => "video/mp4",
|
||||||
|
"type" => "Link"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1558,14 +1492,24 @@ test "returns modified object when attachment is list" do
|
||||||
"attachment" => [
|
"attachment" => [
|
||||||
%{
|
%{
|
||||||
"mediaType" => "video/mp4",
|
"mediaType" => "video/mp4",
|
||||||
|
"type" => "Document",
|
||||||
"url" => [
|
"url" => [
|
||||||
%{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
|
%{
|
||||||
|
"href" => "https://pe.er/stat-480.mp4",
|
||||||
|
"mediaType" => "video/mp4",
|
||||||
|
"type" => "Link"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
"mediaType" => "video/mp4",
|
"mediaType" => "video/mp4",
|
||||||
|
"type" => "Document",
|
||||||
"url" => [
|
"url" => [
|
||||||
%{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
|
%{
|
||||||
|
"href" => "https://pe.er/stat-480.mp4",
|
||||||
|
"mediaType" => "video/mp4",
|
||||||
|
"type" => "Link"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -439,7 +439,7 @@ test "it appends specified tags to users with specified nicknames", %{
|
||||||
user1: user1,
|
user1: user1,
|
||||||
user2: user2
|
user2: user2
|
||||||
} do
|
} do
|
||||||
assert json_response(conn, :no_content)
|
assert empty_json_response(conn)
|
||||||
assert User.get_cached_by_id(user1.id).tags == ["x", "foo", "bar"]
|
assert User.get_cached_by_id(user1.id).tags == ["x", "foo", "bar"]
|
||||||
assert User.get_cached_by_id(user2.id).tags == ["y", "foo", "bar"]
|
assert User.get_cached_by_id(user2.id).tags == ["y", "foo", "bar"]
|
||||||
|
|
||||||
|
@ -457,7 +457,7 @@ test "it appends specified tags to users with specified nicknames", %{
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
|
test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
|
||||||
assert json_response(conn, :no_content)
|
assert empty_json_response(conn)
|
||||||
assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
|
assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -485,7 +485,7 @@ test "it removes specified tags from users with specified nicknames", %{
|
||||||
user1: user1,
|
user1: user1,
|
||||||
user2: user2
|
user2: user2
|
||||||
} do
|
} do
|
||||||
assert json_response(conn, :no_content)
|
assert empty_json_response(conn)
|
||||||
assert User.get_cached_by_id(user1.id).tags == []
|
assert User.get_cached_by_id(user1.id).tags == []
|
||||||
assert User.get_cached_by_id(user2.id).tags == ["y"]
|
assert User.get_cached_by_id(user2.id).tags == ["y"]
|
||||||
|
|
||||||
|
@ -503,7 +503,7 @@ test "it removes specified tags from users with specified nicknames", %{
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
|
test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
|
||||||
assert json_response(conn, :no_content)
|
assert empty_json_response(conn)
|
||||||
assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
|
assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1164,6 +1164,27 @@ test "load users with tags list", %{conn: conn} do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "`active` filters out users pending approval", %{token: token} do
|
||||||
|
insert(:user, approval_pending: true)
|
||||||
|
%{id: user_id} = insert(:user, approval_pending: false)
|
||||||
|
%{id: admin_id} = token.user
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, token.user)
|
||||||
|
|> assign(:token, token)
|
||||||
|
|> get("/api/pleroma/admin/users?filters=active")
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
"count" => 2,
|
||||||
|
"page_size" => 50,
|
||||||
|
"users" => [
|
||||||
|
%{"id" => ^admin_id},
|
||||||
|
%{"id" => ^user_id}
|
||||||
|
]
|
||||||
|
} = json_response(conn, 200)
|
||||||
|
end
|
||||||
|
|
||||||
test "it works with multiple filters" do
|
test "it works with multiple filters" do
|
||||||
admin = insert(:user, nickname: "john", is_admin: true)
|
admin = insert(:user, nickname: "john", is_admin: true)
|
||||||
token = insert(:oauth_admin_token, user: admin)
|
token = insert(:oauth_admin_token, user: admin)
|
||||||
|
@ -1756,7 +1777,7 @@ test "sets password_reset_pending to true", %{conn: conn} do
|
||||||
conn =
|
conn =
|
||||||
patch(conn, "/api/pleroma/admin/users/force_password_reset", %{nicknames: [user.nickname]})
|
patch(conn, "/api/pleroma/admin/users/force_password_reset", %{nicknames: [user.nickname]})
|
||||||
|
|
||||||
assert json_response(conn, 204) == ""
|
assert empty_json_response(conn) == ""
|
||||||
|
|
||||||
ObanHelpers.perform_all()
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
|
|
|
@ -214,6 +214,10 @@ test "updates the user's name", %{conn: conn} do
|
||||||
|
|
||||||
assert user_data = json_response_and_validate_schema(conn, 200)
|
assert user_data = json_response_and_validate_schema(conn, 200)
|
||||||
assert user_data["display_name"] == "markorepairs"
|
assert user_data["display_name"] == "markorepairs"
|
||||||
|
|
||||||
|
update_activity = Repo.one(Pleroma.Activity)
|
||||||
|
assert update_activity.data["type"] == "Update"
|
||||||
|
assert update_activity.data["object"]["name"] == "markorepairs"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "updates the user's avatar", %{user: user, conn: conn} do
|
test "updates the user's avatar", %{user: user, conn: conn} do
|
||||||
|
|
|
@ -135,4 +135,33 @@ test "does not crash on polls with no end date" do
|
||||||
assert result[:expires_at] == nil
|
assert result[:expires_at] == nil
|
||||||
assert result[:expired] == false
|
assert result[:expired] == false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "doesn't strips HTML tags" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
status: "What's with the smug face?",
|
||||||
|
poll: %{
|
||||||
|
options: [
|
||||||
|
"<input type=\"date\">",
|
||||||
|
"<input type=\"date\" >",
|
||||||
|
"<input type=\"date\"/>",
|
||||||
|
"<input type=\"date\"></input>"
|
||||||
|
],
|
||||||
|
expires_in: 20
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
options: [
|
||||||
|
%{title: "<input type=\"date\">", votes_count: 0},
|
||||||
|
%{title: "<input type=\"date\" >", votes_count: 0},
|
||||||
|
%{title: "<input type=\"date\"/>", votes_count: 0},
|
||||||
|
%{title: "<input type=\"date\"></input>", votes_count: 0}
|
||||||
|
]
|
||||||
|
} = PollView.render("show.json", %{object: object})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,13 +9,12 @@ defmodule Pleroma.Web.Metadata.Providers.RelMeTest do
|
||||||
|
|
||||||
test "it renders all links with rel='me' from user bio" do
|
test "it renders all links with rel='me' from user bio" do
|
||||||
bio =
|
bio =
|
||||||
~s(<a href="https://some-link.com">https://some-link.com</a> <a rel="me" href="https://another-link.com">https://another-link.com</a>
|
~s(<a href="https://some-link.com">https://some-link.com</a> <a rel="me" href="https://another-link.com">https://another-link.com</a> <link href="http://some.com"> <link rel="me" href="http://some3.com">)
|
||||||
<link href="http://some.com"> <link rel="me" href="http://some3.com>")
|
|
||||||
|
|
||||||
user = insert(:user, %{bio: bio})
|
user = insert(:user, %{bio: bio})
|
||||||
|
|
||||||
assert RelMe.build_tags(%{user: user}) == [
|
assert RelMe.build_tags(%{user: user}) == [
|
||||||
{:link, [rel: "me", href: "http://some3.com>"], []},
|
{:link, [rel: "me", href: "http://some3.com"], []},
|
||||||
{:link, [rel: "me", href: "https://another-link.com"], []}
|
{:link, [rel: "me", href: "https://another-link.com"], []}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,7 +7,6 @@ defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.Web.OAuth.Token
|
alias Pleroma.Web.OAuth.Token
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
import ExUnit.CaptureLog
|
|
||||||
import Mock
|
import Mock
|
||||||
|
|
||||||
@skip if !Code.ensure_loaded?(:eldap), do: :skip
|
@skip if !Code.ensure_loaded?(:eldap), do: :skip
|
||||||
|
@ -72,9 +71,7 @@ test "creates a new user after successful LDAP authorization" do
|
||||||
equalityMatch: fn _type, _value -> :ok end,
|
equalityMatch: fn _type, _value -> :ok end,
|
||||||
wholeSubtree: fn -> :ok end,
|
wholeSubtree: fn -> :ok end,
|
||||||
search: fn _connection, _options ->
|
search: fn _connection, _options ->
|
||||||
{:ok,
|
{:ok, {:eldap_search_result, [{:eldap_entry, '', []}], []}}
|
||||||
{:eldap_search_result, [{:eldap_entry, '', [{'mail', [to_charlist(user.email)]}]}],
|
|
||||||
[]}}
|
|
||||||
end,
|
end,
|
||||||
close: fn _connection ->
|
close: fn _connection ->
|
||||||
send(self(), :close_connection)
|
send(self(), :close_connection)
|
||||||
|
@ -101,50 +98,6 @@ test "creates a new user after successful LDAP authorization" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag @skip
|
|
||||||
test "falls back to the default authorization when LDAP is unavailable" do
|
|
||||||
password = "testpassword"
|
|
||||||
user = insert(:user, password_hash: Pbkdf2.hash_pwd_salt(password))
|
|
||||||
app = insert(:oauth_app, scopes: ["read", "write"])
|
|
||||||
|
|
||||||
host = Pleroma.Config.get([:ldap, :host]) |> to_charlist
|
|
||||||
port = Pleroma.Config.get([:ldap, :port])
|
|
||||||
|
|
||||||
with_mocks [
|
|
||||||
{:eldap, [],
|
|
||||||
[
|
|
||||||
open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:error, 'connect failed'} end,
|
|
||||||
simple_bind: fn _connection, _dn, ^password -> :ok end,
|
|
||||||
close: fn _connection ->
|
|
||||||
send(self(), :close_connection)
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
]}
|
|
||||||
] do
|
|
||||||
log =
|
|
||||||
capture_log(fn ->
|
|
||||||
conn =
|
|
||||||
build_conn()
|
|
||||||
|> post("/oauth/token", %{
|
|
||||||
"grant_type" => "password",
|
|
||||||
"username" => user.nickname,
|
|
||||||
"password" => password,
|
|
||||||
"client_id" => app.client_id,
|
|
||||||
"client_secret" => app.client_secret
|
|
||||||
})
|
|
||||||
|
|
||||||
assert %{"access_token" => token} = json_response(conn, 200)
|
|
||||||
|
|
||||||
token = Repo.get_by(Token, token: token)
|
|
||||||
|
|
||||||
assert token.user_id == user.id
|
|
||||||
end)
|
|
||||||
|
|
||||||
assert log =~ "Could not open LDAP connection: 'connect failed'"
|
|
||||||
refute_received :close_connection
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@tag @skip
|
@tag @skip
|
||||||
test "disallow authorization for wrong LDAP credentials" do
|
test "disallow authorization for wrong LDAP credentials" do
|
||||||
password = "testpassword"
|
password = "testpassword"
|
||||||
|
|
Loading…
Reference in a new issue