aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.gitlab-ci.yml1
-rw-r--r--CHANGELOG.md2
-rw-r--r--SECURITY.md16
-rw-r--r--config/config.exs12
-rw-r--r--docs/administration/CLI_tasks/database.md12
-rw-r--r--docs/administration/CLI_tasks/release_environments.md9
-rw-r--r--docs/configuration/cheatsheet.md6
-rw-r--r--docs/installation/alpine_linux_en.md3
-rw-r--r--docs/installation/arch_linux_en.md3
-rw-r--r--docs/installation/debian_based_en.md3
-rw-r--r--docs/installation/debian_based_jp.md3
-rw-r--r--docs/installation/gentoo_en.md3
-rw-r--r--docs/installation/netbsd_en.md1
-rw-r--r--docs/installation/openbsd_en.md3
-rw-r--r--docs/installation/openbsd_fi.md2
-rw-r--r--docs/installation/otp_en.md7
-rwxr-xr-xinstallation/init.d/pleroma1
-rw-r--r--installation/pleroma.service2
-rw-r--r--lib/mix/tasks/pleroma/database.ex24
-rw-r--r--lib/mix/tasks/pleroma/release_env.ex76
-rw-r--r--lib/pleroma/activity_expiration.ex12
-rw-r--r--lib/pleroma/object.ex15
-rw-r--r--lib/pleroma/object/containment.ex2
-rw-r--r--lib/pleroma/object/fetcher.ex39
-rw-r--r--lib/pleroma/user.ex28
-rw-r--r--lib/pleroma/user/query.ex1
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex15
-rw-r--r--lib/pleroma/web/activity_pub/builder.ex26
-rw-r--r--lib/pleroma/web/activity_pub/object_validator.ex57
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/answer_validator.ex65
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/common_validations.ex60
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex133
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/delete_validator.ex28
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/note_validator.ex2
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex37
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/question_validator.ex127
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex2
-rw-r--r--lib/pleroma/web/activity_pub/side_effects.ex42
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex75
-rw-r--r--lib/pleroma/web/auth/ldap_authenticator.ex43
-rw-r--r--lib/pleroma/web/common_api/common_api.ex34
-rw-r--r--lib/pleroma/web/common_api/utils.ex11
-rw-r--r--lib/pleroma/web/controller_helper.ex6
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/account_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/views/poll_view.ex18
-rw-r--r--mix.exs2
-rw-r--r--mix.lock5
-rw-r--r--priv/repo/migrations/20200802170532_fix_legacy_tags.exs7
-rw-r--r--test/emails/mailer_test.exs3
-rw-r--r--test/fixtures/mastodon-question-activity.json1
-rw-r--r--test/fixtures/tesla_mock/poll_attachment.json99
-rw-r--r--test/object/fetcher_test.exs7
-rw-r--r--test/support/conn_case.ex9
-rw-r--r--test/support/http_request_mock.ex8
-rw-r--r--test/tasks/database_test.exs39
-rw-r--r--test/tasks/release_env_test.exs30
-rw-r--r--test/web/activity_pub/object_validators/delete_validation_test.exs2
-rw-r--r--test/web/activity_pub/transmogrifier/answer_handling_test.exs78
-rw-r--r--test/web/activity_pub/transmogrifier/question_handling_test.exs123
-rw-r--r--test/web/activity_pub/transmogrifier_test.exs100
-rw-r--r--test/web/admin_api/controllers/admin_api_controller_test.exs31
-rw-r--r--test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs4
-rw-r--r--test/web/mastodon_api/views/poll_view_test.exs29
-rw-r--r--test/web/metadata/rel_me_test.exs5
-rw-r--r--test/web/oauth/ldap_authorization_test.exs49
66 files changed, 1257 insertions, 445 deletions
diff --git a/.gitignore b/.gitignore
index 6ae21e914..599b52b9e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,8 +27,6 @@ erl_crash.dump
# variables.
/config/*.secret.exs
/config/generated_config.exs
-/config/*.env
-
# Database setup file, some may forget to delete it
/config/setup_db.psql
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ab2bc9f98..30f90d044 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -24,6 +24,7 @@ stages:
- docker
before_script:
+ - apt-get update && apt-get install -y cmake
- mix local.hex --force
- mix local.rebar --force
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 572f9e84b..a8e80eb3c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [unreleased]
### 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:** Configuration: `:auto_linker, :opts` moved to `:pleroma, Pleroma.Formatter`. Old config namespace is deprecated.
- 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: `: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:** 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>
<summary>API Changes</summary>
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..c212a2505
--- /dev/null
+++ b/SECURITY.md
@@ -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>.
diff --git a/config/config.exs b/config/config.exs
index d257865a9..dfc4e1629 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -527,7 +527,13 @@ config :pleroma, Pleroma.User,
"user-search",
"user_exists",
"users",
- "web"
+ "web",
+ "verify_credentials",
+ "update_credentials",
+ "relationships",
+ "search",
+ "confirmation_resend",
+ "mfa"
],
email_blacklist: []
@@ -749,6 +755,10 @@ config :ex_aws, http_client: Pleroma.HTTP.ExAws
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,
root_mode: false,
options: %{}
diff --git a/docs/administration/CLI_tasks/database.md b/docs/administration/CLI_tasks/database.md
index 647f6f274..64dd66c0c 100644
--- a/docs/administration/CLI_tasks/database.md
+++ b/docs/administration/CLI_tasks/database.md
@@ -97,4 +97,14 @@ but should only be run if necessary. **It is safe to cancel this.**
```sh tab="From Source"
mix pleroma.database vacuum full
-``` \ No newline at end of file
+```
+
+## Add expiration to all local statuses
+
+```sh tab="OTP"
+./bin/pleroma_ctl database ensure_expiration
+```
+
+```sh tab="From Source"
+mix pleroma.database ensure_expiration
+```
diff --git a/docs/administration/CLI_tasks/release_environments.md b/docs/administration/CLI_tasks/release_environments.md
deleted file mode 100644
index 36ab43864..000000000
--- a/docs/administration/CLI_tasks/release_environments.md
+++ /dev/null
@@ -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
-```
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index f23cf4fe4..ca587af8e 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -858,9 +858,6 @@ Warning: it's discouraged to use this feature because of the associated security
### :auth
-* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator.
-* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication.
-
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`.
@@ -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"
* `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 allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).
diff --git a/docs/installation/alpine_linux_en.md b/docs/installation/alpine_linux_en.md
index c726d559f..a5683f18c 100644
--- a/docs/installation/alpine_linux_en.md
+++ b/docs/installation/alpine_linux_en.md
@@ -14,6 +14,7 @@ It assumes that you have administrative rights, either as root or a user with [s
* `erlang-xmerl`
* `git`
* Development Tools
+* `cmake`
#### Optional packages used in this guide
@@ -39,7 +40,7 @@ sudo apk upgrade
* Install some tools, which are needed later:
```shell
-sudo apk add git build-base
+sudo apk add git build-base cmake
```
### Install Elixir and Erlang
diff --git a/docs/installation/arch_linux_en.md b/docs/installation/arch_linux_en.md
index bf9cfb488..7fb69dd60 100644
--- a/docs/installation/arch_linux_en.md
+++ b/docs/installation/arch_linux_en.md
@@ -9,6 +9,7 @@ This guide will assume that you have administrative rights, either as root or a
* `elixir`
* `git`
* `base-devel`
+* `cmake`
#### Optional packages used in this guide
@@ -26,7 +27,7 @@ sudo pacman -Syu
* Install some of the above mentioned programs:
```shell
-sudo pacman -S git base-devel elixir
+sudo pacman -S git base-devel elixir cmake
```
### Install PostgreSQL
diff --git a/docs/installation/debian_based_en.md b/docs/installation/debian_based_en.md
index 8ae5044b5..60c2f47e5 100644
--- a/docs/installation/debian_based_en.md
+++ b/docs/installation/debian_based_en.md
@@ -12,6 +12,7 @@ This guide will assume you are on Debian Stretch. This guide should also work wi
* `erlang-nox`
* `git`
* `build-essential`
+* `cmake`
#### Optional packages used in this guide
@@ -30,7 +31,7 @@ sudo apt full-upgrade
* Install some of the above mentioned programs:
```shell
-sudo apt install git build-essential postgresql postgresql-contrib
+sudo apt install git build-essential postgresql postgresql-contrib cmake
```
### Install Elixir and Erlang
diff --git a/docs/installation/debian_based_jp.md b/docs/installation/debian_based_jp.md
index 42e91cda7..c2dd840d3 100644
--- a/docs/installation/debian_based_jp.md
+++ b/docs/installation/debian_based_jp.md
@@ -16,6 +16,7 @@
- `erlang-nox`
- `git`
- `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
```
diff --git a/docs/installation/gentoo_en.md b/docs/installation/gentoo_en.md
index 32152aea7..5a676380c 100644
--- a/docs/installation/gentoo_en.md
+++ b/docs/installation/gentoo_en.md
@@ -28,6 +28,7 @@ Gentoo quite pointedly does not come with a cron daemon installed, and as such i
* `dev-db/postgresql`
* `dev-lang/elixir`
* `dev-vcs/git`
+* `dev-util/cmake`
#### 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:
```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.
diff --git a/docs/installation/netbsd_en.md b/docs/installation/netbsd_en.md
index 3626acc69..6ad0de2f6 100644
--- a/docs/installation/netbsd_en.md
+++ b/docs/installation/netbsd_en.md
@@ -19,6 +19,7 @@ databases/postgresql11-client
databases/postgresql11-server
devel/git-base
devel/git-docs
+devel/cmake
lang/elixir
security/acmesh
security/sudo
diff --git a/docs/installation/openbsd_en.md b/docs/installation/openbsd_en.md
index 5dbe24f75..eee452845 100644
--- a/docs/installation/openbsd_en.md
+++ b/docs/installation/openbsd_en.md
@@ -14,11 +14,12 @@ The following packages need to be installed:
* git
* postgresql-server
* postgresql-contrib
+ * cmake
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.
diff --git a/docs/installation/openbsd_fi.md b/docs/installation/openbsd_fi.md
index 272273cff..b5b5056a9 100644
--- a/docs/installation/openbsd_fi.md
+++ b/docs/installation/openbsd_fi.md
@@ -16,7 +16,7 @@ Matrix-kanava #freenode_#pleroma:matrix.org ovat hyviä paikkoja löytää apua
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:
diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md
index 338dfa7d0..e4f822d1c 100644
--- a/docs/installation/otp_en.md
+++ b/docs/installation/otp_en.md
@@ -121,9 +121,6 @@ chown -R pleroma /etc/pleroma
# 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"
-# Run the environment file generator.
-su pleroma -s $SHELL -lc "./bin/pleroma_ctl release_env gen"
-
# Create the postgres database
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/"
# 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
sleep 20 && curl http://localhost:4000/api/v1/instance
@@ -203,7 +200,6 @@ rc-update add pleroma
# Copy the service into a proper directory
cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
-
# Start pleroma and enable it on boot
systemctl start pleroma
systemctl enable pleroma
@@ -279,3 +275,4 @@ This will create an account withe the username of 'joeuser' with the email addre
## 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**.
+
diff --git a/installation/init.d/pleroma b/installation/init.d/pleroma
index e908cda1b..384536f7e 100755
--- a/installation/init.d/pleroma
+++ b/installation/init.d/pleroma
@@ -8,7 +8,6 @@ pidfile="/var/run/pleroma.pid"
directory=/opt/pleroma
healthcheck_delay=60
healthcheck_timer=30
-export $(cat /opt/pleroma/config/pleroma.env)
: ${pleroma_port:-4000}
diff --git a/installation/pleroma.service b/installation/pleroma.service
index ee00a3b7a..5dcbc1387 100644
--- a/installation/pleroma.service
+++ b/installation/pleroma.service
@@ -17,8 +17,6 @@ Environment="MIX_ENV=prod"
Environment="HOME=/var/lib/pleroma"
; Path to the folder containing the Pleroma installation.
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.
ExecStart=/usr/bin/mix phx.server
diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index 82e2abdcb..d57e59b11 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -10,6 +10,7 @@ defmodule Mix.Tasks.Pleroma.Database do
alias Pleroma.User
require Logger
require Pleroma.Constants
+ import Ecto.Query
import Mix.Pleroma
use Mix.Task
@@ -53,8 +54,6 @@ defmodule Mix.Tasks.Pleroma.Database do
end
def run(["prune_objects" | args]) do
- import Ecto.Query
-
{options, [], []} =
OptionParser.parse(
args,
@@ -94,8 +93,6 @@ defmodule Mix.Tasks.Pleroma.Database do
end
def run(["fix_likes_collections"]) do
- import Ecto.Query
-
start_pleroma()
from(object in Object,
@@ -130,4 +127,23 @@ defmodule Mix.Tasks.Pleroma.Database do
Maintenance.vacuum(args)
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
diff --git a/lib/mix/tasks/pleroma/release_env.ex b/lib/mix/tasks/pleroma/release_env.ex
deleted file mode 100644
index 9da74ffcf..000000000
--- a/lib/mix/tasks/pleroma/release_env.ex
+++ /dev/null
@@ -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
diff --git a/lib/pleroma/activity_expiration.ex b/lib/pleroma/activity_expiration.ex
index db9c88d84..7cc9668b3 100644
--- a/lib/pleroma/activity_expiration.ex
+++ b/lib/pleroma/activity_expiration.ex
@@ -20,11 +20,11 @@ defmodule Pleroma.ActivityExpiration do
field(:scheduled_at, :naive_datetime)
end
- def changeset(%ActivityExpiration{} = expiration, attrs) do
+ def changeset(%ActivityExpiration{} = expiration, attrs, validate_scheduled_at) do
expiration
|> cast(attrs, [:scheduled_at])
|> validate_required([:scheduled_at])
- |> validate_scheduled_at()
+ |> validate_scheduled_at(validate_scheduled_at)
end
def get_by_activity_id(activity_id) do
@@ -33,9 +33,9 @@ defmodule Pleroma.ActivityExpiration do
|> Repo.one()
end
- def create(%Activity{} = activity, scheduled_at) do
+ def create(%Activity{} = activity, scheduled_at, validate_scheduled_at \\ true) do
%ActivityExpiration{activity_id: activity.id}
- |> changeset(%{scheduled_at: scheduled_at})
+ |> changeset(%{scheduled_at: scheduled_at}, validate_scheduled_at)
|> Repo.insert()
end
@@ -49,7 +49,9 @@ defmodule Pleroma.ActivityExpiration do
|> Repo.all()
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 ->
if not expires_late_enough?(scheduled_at) do
[scheduled_at: "an ephemeral activity must live for at least one hour"]
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 546c4ea01..052ad413b 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -255,6 +255,10 @@ defmodule Pleroma.Object do
end
end
+ defp poll_is_multiple?(%Object{data: %{"anyOf" => [_ | _]}}), do: true
+
+ defp poll_is_multiple?(_), do: false
+
def decrease_replies_count(ap_id) do
Object
|> where([o], fragment("?->>'id' = ?::text", o.data, ^to_string(ap_id)))
@@ -281,10 +285,10 @@ defmodule Pleroma.Object do
def increase_vote_count(ap_id, name, actor) do
with %Object{} = object <- Object.normalize(ap_id),
"Question" <- object.data["type"] do
- multiple = Map.has_key?(object.data, "anyOf")
+ key = if poll_is_multiple?(object), do: "anyOf", else: "oneOf"
options =
- (object.data["anyOf"] || object.data["oneOf"] || [])
+ object.data[key]
|> Enum.map(fn
%{"name" => ^name} = option ->
Kernel.update_in(option["replies"]["totalItems"], &(&1 + 1))
@@ -296,11 +300,8 @@ defmodule Pleroma.Object do
voters = [actor | object.data["voters"] || []] |> Enum.uniq()
data =
- if multiple do
- Map.put(object.data, "anyOf", options)
- else
- Map.put(object.data, "oneOf", options)
- end
+ object.data
+ |> Map.put(key, options)
|> Map.put("voters", voters)
object
diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex
index 99608b8a5..bc88e8a0c 100644
--- a/lib/pleroma/object/containment.ex
+++ b/lib/pleroma/object/containment.ex
@@ -55,7 +55,7 @@ defmodule Pleroma.Object.Containment do
defp compare_uris(_id_uri, _other_uri), do: :error
@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
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index e74c87269..3ff25118d 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Object.Fetcher do
alias Pleroma.Repo
alias Pleroma.Signature
alias Pleroma.Web.ActivityPub.InternalFetchActor
+ alias Pleroma.Web.ActivityPub.ObjectValidator
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.Federator
@@ -23,21 +24,39 @@ defmodule Pleroma.Object.Fetcher do
Ecto.Changeset.put_change(changeset, :updated_at, updated_at)
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())
- Map.merge(data, internal_fields)
+ Map.merge(new_data, internal_fields)
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()}
- defp reinject_object(struct, data) do
- Logger.debug("Reinjecting object #{data["id"]}")
+ defp reinject_object(%Object{data: %{"type" => "Question"}} = object, new_data) do
+ Logger.debug("Reinjecting object #{new_data["id"]}")
- with data <- Transmogrifier.fix_object(data),
- data <- maybe_reinject_internal_fields(data, struct),
- changeset <- Object.change(struct, %{data: data}),
+ with new_data <- Transmogrifier.fix_object(new_data),
+ data <- maybe_reinject_internal_fields(object, new_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),
{:ok, object} <- Repo.insert_or_update(changeset),
{:ok, object} <- Object.set_cache(object) do
@@ -51,8 +70,8 @@ defmodule Pleroma.Object.Fetcher do
def refetch_object(%Object{data: %{"id" => id}} = object) do
with {:local, false} <- {:local, Object.local?(object)},
- {:ok, data} <- fetch_and_contain_remote_object_from_id(id),
- {:ok, object} <- reinject_object(object, data) do
+ {:ok, new_data} <- fetch_and_contain_remote_object_from_id(id),
+ {:ok, object} <- reinject_object(object, new_data) do
{:ok, object}
else
{:local, true} -> {:ok, object}
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 09e606b37..d1436a688 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -638,6 +638,34 @@ defmodule Pleroma.User do
@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)
+ # 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
bio_limit = Config.get([:instance, :user_bio_length], 5000)
name_limit = Config.get([:instance, :user_name_length], 100)
diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex
index 45553cb6c..d618432ff 100644
--- a/lib/pleroma/user/query.ex
+++ b/lib/pleroma/user/query.ex
@@ -130,6 +130,7 @@ defmodule Pleroma.User.Query do
defp compose_query({:active, _}, query) do
User.restrict_deactivated(query)
|> where([u], not is_nil(u.nickname))
+ |> where([u], u.approval_pending == false)
end
defp compose_query({:legacy_active, _}, query) do
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index a4db1d87c..fe62673dc 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -66,7 +66,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
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}
end
@@ -85,17 +85,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp increase_replies_count_if_reply(_create_data), do: :noop
- defp increase_poll_votes_if_vote(%{
- "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"]
+ @object_types ["ChatMessage", "Question", "Answer"]
@spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
def persist(%{"type" => type} = object, meta) when type in @object_types do
with {:ok, object} <- Object.create(object) do
@@ -258,7 +248,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
with {:ok, activity} <- insert(create_data, local, fake),
{:fake, false, activity} <- {:fake, fake, activity},
_ <- increase_replies_count_if_reply(create_data),
- _ <- increase_poll_votes_if_vote(create_data),
{:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
_ <- notify_and_stream(activity),
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
index d5f3610ed..1b4c421b8 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -80,6 +80,13 @@ defmodule Pleroma.Web.ActivityPub.Builder do
end
def create(actor, object, recipients) do
+ context =
+ if is_map(object) do
+ object["context"]
+ else
+ nil
+ end
+
{:ok,
%{
"id" => Utils.generate_activity_id(),
@@ -88,7 +95,8 @@ defmodule Pleroma.Web.ActivityPub.Builder do
"object" => object,
"type" => "Create",
"published" => DateTime.utc_now() |> DateTime.to_iso8601()
- }, []}
+ }
+ |> Pleroma.Maps.put_if_present("context", context), []}
end
def chat_message(actor, recipient, content, opts \\ []) do
@@ -115,6 +123,22 @@ defmodule Pleroma.Web.ActivityPub.Builder do
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()}
def tombstone(actor, id) do
{:ok,
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 0dcc7be4d..e1114a44d 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -14,13 +14,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
@@ -112,17 +115,40 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
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
with {:ok, object} <-
object
|> EmojiReactValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
- object = stringify_keys(object |> Map.from_struct())
+ object = stringify_keys(object)
{:ok, object, meta}
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),
meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
{:ok, create_activity} <-
@@ -134,12 +160,28 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
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
with {:ok, object} <-
object
|> AnnounceValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
- object = stringify_keys(object |> Map.from_struct())
+ object = stringify_keys(object)
{:ok, object, meta}
end
end
@@ -148,8 +190,17 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
ChatMessageValidator.cast_and_apply(object)
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}}
+ # is_struct/1 isn't present in Elixir 1.8.x
def stringify_keys(%{__struct__: _} = object) do
object
|> Map.from_struct()
diff --git a/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex b/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex
new file mode 100644
index 000000000..323367642
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex
@@ -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
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
index bd46f8034..603d87b8e 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
alias Pleroma.Object
alias Pleroma.User
- def validate_recipients_presence(cng, fields \\ [:to, :cc]) do
+ def validate_any_presence(cng, fields) do
non_empty =
fields
|> Enum.map(fn field -> get_field(cng, field) end)
@@ -24,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
fields
|> Enum.reduce(cng, fn field, cng ->
cng
- |> add_error(field, "no recipients in any field")
+ |> add_error(field, "none of #{inspect(fields)} present")
end)
end
end
@@ -82,4 +82,60 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
if actor_cng.valid?, do: actor_cng, else: object_cng
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
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
new file mode 100644
index 000000000..60868eae0
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
@@ -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
diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
index 93a7b0e0b..2634e8d4d 100644
--- a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
@@ -7,7 +7,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
alias Pleroma.Activity
alias Pleroma.EctoType.ActivityPub.ObjectValidators
- alias Pleroma.User
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@@ -59,7 +58,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|> validate_inclusion(:type, ["Delete"])
|> validate_actor_presence()
- |> validate_deletion_rights()
+ |> validate_modification_rights()
|> validate_object_or_user_presence(allowed_types: @deletable_types)
|> add_deleted_activity_id()
end
@@ -68,31 +67,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
!same_domain?(cng)
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
data
|> cast_data
diff --git a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
index 56b93dde8..a65fe2354 100644
--- a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
@@ -34,7 +34,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
field(:replies_count, :integer, default: 0)
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
- field(:inRepyTo, :string)
+ field(:inReplyTo, :string)
field(:uri, ObjectValidators.Uri)
field(:likes, {:array, :string}, default: [])
diff --git a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex
new file mode 100644
index 000000000..478b3b5cf
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex
@@ -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
diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
new file mode 100644
index 000000000..f47acf606
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
@@ -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
diff --git a/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex b/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex
index f64fac46d..881030f38 100644
--- a/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex
@@ -13,7 +13,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator do
embedded_schema do
field(:type, :string)
field(:href, ObjectValidators.Uri)
- field(:mediaType, :string)
+ field(:mediaType, :string, default: "application/octet-stream")
end
def changeset(struct, data) do
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index 1d2c296a5..5104d38ee 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
"""
alias Pleroma.Activity
alias Pleroma.Activity.Ir.Topics
+ alias Pleroma.ActivityExpiration
alias Pleroma.Chat
alias Pleroma.Chat.MessageReference
alias Pleroma.FollowingRelationship
@@ -19,6 +20,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Push
alias Pleroma.Web.Streamer
+ alias Pleroma.Workers.BackgroundWorker
def handle(object, meta \\ [])
@@ -135,10 +137,26 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# Tasks this handles
# - Actually create object
# - 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
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, _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
@@ -268,9 +286,27 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
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
- def handle_object_creation(object) do
- {:ok, object}
+ def handle_object_creation(object, meta) do
+ {:ok, object, meta}
end
defp undo_like(nil, object), do: delete_object(object)
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 35aa05eb5..2f04cc6ff 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -157,7 +157,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
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
def fix_in_reply_to(object, options \\ [])
@@ -240,13 +245,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
if href do
attachment_url =
- %{"href" => href}
+ %{
+ "href" => href,
+ "type" => Map.get(url || %{}, "type", "Link")
+ }
|> 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("type", data["type"])
|> Maps.put_if_present("name", data["name"])
else
nil
@@ -419,6 +428,29 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
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 \\ [])
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
@@ -457,30 +489,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
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)
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
- {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(actor),
- data <- Map.put(data, "actor", actor) |> fix_addressing() do
- object = fix_object(object, options)
-
- params = %{
- to: data["to"],
- object: object,
- actor: user,
- context: object["context"],
- local: false,
- published: data["published"],
- additional:
- Map.take(data, [
- "cc",
- "directMessage",
- "id"
- ])
- }
+ {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(actor) do
+ data =
+ data
+ |> Map.put("object", fix_object(object, options))
+ |> Map.put("actor", actor)
+ |> fix_addressing()
- with {:ok, created_activity} <- ActivityPub.create(params) do
+ with {:ok, created_activity} <- handle_create(data, user) do
reply_depth = (options[:depth] || 0) + 1
if Federator.allowed_thread_distance?(reply_depth) do
@@ -614,9 +634,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Create", "object" => %{"type" => "ChatMessage"}} = data,
+ %{"type" => "Create", "object" => %{"type" => objtype}} = data,
_options
- ) do
+ )
+ when objtype in ["Question", "Answer", "ChatMessage"] do
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex
index f63a66c03..402ab428b 100644
--- a/lib/pleroma/web/auth/ldap_authenticator.ex
+++ b/lib/pleroma/web/auth/ldap_authenticator.ex
@@ -28,10 +28,6 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
%User{} = user <- ldap_user(name, password) do
{:ok, user}
else
- {:error, {:ldap_connection_error, _}} ->
- # When LDAP is unavailable, try default authenticator
- @base.get_user(conn)
-
{:ldap, _} ->
@base.get_user(conn)
@@ -92,7 +88,7 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
user
_ ->
- register_user(connection, base, uid, name, password)
+ register_user(connection, base, uid, name)
end
error ->
@@ -100,34 +96,31 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
end
end
- defp register_user(connection, base, uid, name, password) do
+ defp register_user(connection, base, uid, name) do
case :eldap.search(connection, [
{:base, to_charlist(base)},
{:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))},
{:scope, :eldap.wholeSubtree()},
- {:attributes, ['mail', 'email']},
{:timeout, @search_timeout}
]) do
{:ok, {:eldap_search_result, [{:eldap_entry, _, attributes}], _}} ->
- with {_, [mail]} <- List.keyfind(attributes, 'mail', 0) do
- params = %{
- email: :erlang.list_to_binary(mail),
- name: name,
- nickname: name,
- password: password,
- password_confirmation: password
- }
-
- changeset = User.register_changeset(%User{}, params)
-
- case User.register(changeset) do
- {:ok, user} -> user
- error -> error
+ params = %{
+ name: name,
+ nickname: name,
+ password: nil
+ }
+
+ params =
+ case List.keyfind(attributes, 'mail', 0) do
+ {_, [mail]} -> Map.put_new(params, :email, :erlang.list_to_binary(mail))
+ _ -> params
end
- else
- _ ->
- Logger.error("Could not find LDAP attribute mail: #{inspect(attributes)}")
- {:error, :ldap_registration_missing_attributes}
+
+ changeset = User.register_changeset_ldap(%User{}, params)
+
+ case User.register(changeset) do
+ {:ok, user} -> user
+ error -> error
end
error ->
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 4d5b0decf..c08e0ffeb 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -308,18 +308,19 @@ defmodule Pleroma.Web.CommonAPI do
{:ok, options, choices} <- normalize_and_validate_choices(choices, object) do
answer_activities =
Enum.map(choices, fn index ->
- answer_data = make_answer_data(user, object, Enum.at(options, index)["name"])
-
- {:ok, activity} =
- ActivityPub.create(%{
- to: answer_data["to"],
- actor: user,
- context: object.data["context"],
- object: answer_data,
- additional: %{"cc" => answer_data["cc"]}
- })
-
- activity
+ {:ok, answer_object, _meta} =
+ Builder.answer(user, object, Enum.at(options, index)["name"])
+
+ {:ok, activity_data, _meta} = Builder.create(user, answer_object, [])
+
+ {: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)
object = Object.get_cached_by_ap_id(object.data["id"])
@@ -340,8 +341,13 @@ defmodule Pleroma.Web.CommonAPI do
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: %{"oneOf" => one_of}}), do: {one_of, 1}
+ defp get_options_and_max_count(%{data: %{"anyOf" => any_of}})
+ 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
choices = Enum.map(choices, fn i -> if is_binary(i), do: String.to_integer(i), else: i end)
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 9c38b73eb..9d7b24eb2 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -548,17 +548,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
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
{:error, dgettext("errors", "Cannot post an empty status without attachments")}
end
diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex
index 69946fb81..6445966e0 100644
--- a/lib/pleroma/web/controller_helper.ex
+++ b/lib/pleroma/web/controller_helper.ex
@@ -18,6 +18,12 @@ defmodule Pleroma.Web.ControllerHelper do
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
conn
|> put_status(status)
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index f45678184..95d8452df 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -226,7 +226,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
with changeset <- User.update_changeset(user, user_params),
{:ok, unpersisted_user} <- Ecto.Changeset.apply_action(changeset, :update),
updated_object <-
- Pleroma.Web.ActivityPub.UserView.render("user.json", user: user)
+ Pleroma.Web.ActivityPub.UserView.render("user.json", user: unpersisted_user)
|> Map.delete("@context"),
{:ok, update_data, []} <- Builder.update(user, updated_object),
{:ok, _update, _} <-
diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex
index 59a5deb28..1208dc9a0 100644
--- a/lib/pleroma/web/mastodon_api/views/poll_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex
@@ -28,10 +28,10 @@ defmodule Pleroma.Web.MastodonAPI.PollView do
def render("show.json", %{object: object} = params) do
case object.data do
- %{"anyOf" => options} when is_list(options) ->
+ %{"anyOf" => [_ | _] = 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}))
_ ->
@@ -40,15 +40,13 @@ defmodule Pleroma.Web.MastodonAPI.PollView do
end
defp end_time_and_expired(object) do
- case object.data["closed"] || object.data["endTime"] do
- end_time when is_binary(end_time) ->
- end_time = NaiveDateTime.from_iso8601!(end_time)
- expired = NaiveDateTime.compare(end_time, NaiveDateTime.utc_now()) == :lt
+ if object.data["closed"] do
+ end_time = NaiveDateTime.from_iso8601!(object.data["closed"])
+ expired = NaiveDateTime.compare(end_time, NaiveDateTime.utc_now()) == :lt
- {Utils.to_masto_date(end_time), expired}
-
- _ ->
- {nil, false}
+ {Utils.to_masto_date(end_time), expired}
+ else
+ {nil, false}
end
end
diff --git a/mix.exs b/mix.exs
index 3266f1cda..33c4411c4 100644
--- a/mix.exs
+++ b/mix.exs
@@ -127,7 +127,7 @@ defmodule Pleroma.Mixfile do
{:pbkdf2_elixir, "~> 1.2"},
{:bcrypt_elixir, "~> 2.2"},
{:trailing_format_plug, "~> 0.0.7"},
- {:fast_sanitize, "~> 0.1"},
+ {:fast_sanitize, "~> 0.2.0"},
{:html_entities, "~> 0.5", override: true},
{:phoenix_html, "~> 2.14"},
{:calendar, "~> 1.0"},
diff --git a/mix.lock b/mix.lock
index 435c9f3df..f5acc89eb 100644
--- a/mix.lock
+++ b/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"},
"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"},
- "fast_html": {:hex, :fast_html, "1.0.3", "2cc0d4b68496266a1530e0c852cafeaede0bd10cfdee26fda50dc696c203162f", [:make, :mix], [], "hexpm", "ab3d782b639d3c4655fbaec0f9d032c91f8cab8dd791ac7469c2381bc7c32f85"},
- "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_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.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"},
"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"},
@@ -79,6 +79,7 @@
"mox": {:hex, :mox, "0.5.2", "55a0a5ba9ccc671518d068c8dddd20eeb436909ea79d1799e2209df7eaa98b6c", [:mix], [], "hexpm", "df4310628cd628ee181df93f50ddfd07be3e5ecc30232d3b6aadf30bdfe6092b"},
"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_pool": {:hex, :nimble_pool, "0.1.0", "ffa9d5be27eee2b00b0c634eb649aa27f97b39186fec3c493716c2a33e784ec6", [:mix], [], "hexpm", "343a1eaa620ddcf3430a83f39f2af499fe2370390d4f785cd475b4df5acaf3f9"},
"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"},
"open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "f296ac0924ba3cf79c7a588c4c252889df4c2edd", [ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"]},
diff --git a/priv/repo/migrations/20200802170532_fix_legacy_tags.exs b/priv/repo/migrations/20200802170532_fix_legacy_tags.exs
index f7274b44e..ca82fac42 100644
--- a/priv/repo/migrations/20200802170532_fix_legacy_tags.exs
+++ b/priv/repo/migrations/20200802170532_fix_legacy_tags.exs
@@ -18,8 +18,11 @@ defmodule Pleroma.Repo.Migrations.FixLegacyTags do
def change do
legacy_tags = Map.keys(@old_new_map)
- from(u in User, where: fragment("? && ?", u.tags, ^legacy_tags))
- |> Repo.all()
+ from(u in User,
+ where: fragment("? && ?", u.tags, ^legacy_tags),
+ select: struct(u, [:tags, :id])
+ )
+ |> Repo.chunk_stream(100)
|> Enum.each(fn user ->
fix_tags_changeset(user)
|> Repo.update()
diff --git a/test/emails/mailer_test.exs b/test/emails/mailer_test.exs
index e6e34cba8..3da45056b 100644
--- a/test/emails/mailer_test.exs
+++ b/test/emails/mailer_test.exs
@@ -19,6 +19,7 @@ defmodule Pleroma.Emails.MailerTest do
test "not send email when mailer is disabled" do
Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false)
Mailer.deliver(@email)
+ :timer.sleep(100)
refute_email_sent(
from: {"Pleroma", "noreply@example.com"},
@@ -30,6 +31,7 @@ defmodule Pleroma.Emails.MailerTest do
test "send email" do
Mailer.deliver(@email)
+ :timer.sleep(100)
assert_email_sent(
from: {"Pleroma", "noreply@example.com"},
@@ -41,6 +43,7 @@ defmodule Pleroma.Emails.MailerTest do
test "perform" do
Mailer.perform(:deliver_async, @email, [])
+ :timer.sleep(100)
assert_email_sent(
from: {"Pleroma", "noreply@example.com"},
diff --git a/test/fixtures/mastodon-question-activity.json b/test/fixtures/mastodon-question-activity.json
index ac329c7d5..3648b9f90 100644
--- a/test/fixtures/mastodon-question-activity.json
+++ b/test/fixtures/mastodon-question-activity.json
@@ -49,7 +49,6 @@
"en": "<p>Why is Tenshi eating a corndog so cute?</p>"
},
"endTime": "2019-05-11T09:03:36Z",
- "closed": "2019-05-11T09:03:36Z",
"attachment": [],
"tag": [],
"replies": {
diff --git a/test/fixtures/tesla_mock/poll_attachment.json b/test/fixtures/tesla_mock/poll_attachment.json
new file mode 100644
index 000000000..92e822dc8
--- /dev/null
+++ b/test/fixtures/tesla_mock/poll_attachment.json
@@ -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"
+ ]
+}
diff --git a/test/object/fetcher_test.exs b/test/object/fetcher_test.exs
index d9098ea1b..16cfa7f5c 100644
--- a/test/object/fetcher_test.exs
+++ b/test/object/fetcher_test.exs
@@ -177,6 +177,13 @@ defmodule Pleroma.Object.FetcherTest do
"https://mastodon.example.org/users/userisgone404"
)
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
describe "pruning" do
diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex
index b23918dd1..7ef681258 100644
--- a/test/support/conn_case.ex
+++ b/test/support/conn_case.ex
@@ -56,6 +56,13 @@ defmodule Pleroma.Web.ConnCase do
[conn: conn]
end
+ defp empty_json_response(conn) do
+ body = response(conn, 204)
+ response_content_type(conn, :json)
+
+ body
+ end
+
defp json_response_and_validate_schema(
%{
private: %{
@@ -79,7 +86,7 @@ defmodule Pleroma.Web.ConnCase do
end
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
{:ok, _data} ->
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
index 19a202654..eeeba7880 100644
--- a/test/support/http_request_mock.ex
+++ b/test/support/http_request_mock.ex
@@ -82,6 +82,14 @@ defmodule HttpRequestMock do
}}
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(
"https://mastodon.social/.well-known/webfinger?resource=https://mastodon.social/users/emelie",
_,
diff --git a/test/tasks/database_test.exs b/test/tasks/database_test.exs
index 883828d77..3a28aa133 100644
--- a/test/tasks/database_test.exs
+++ b/test/tasks/database_test.exs
@@ -127,4 +127,43 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do
assert Enum.empty?(Object.get_by_id(object2.id).data["likes"])
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
diff --git a/test/tasks/release_env_test.exs b/test/tasks/release_env_test.exs
deleted file mode 100644
index 519f1eba9..000000000
--- a/test/tasks/release_env_test.exs
+++ /dev/null
@@ -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
diff --git a/test/web/activity_pub/object_validators/delete_validation_test.exs b/test/web/activity_pub/object_validators/delete_validation_test.exs
index 42cd18298..02683b899 100644
--- a/test/web/activity_pub/object_validators/delete_validation_test.exs
+++ b/test/web/activity_pub/object_validators/delete_validation_test.exs
@@ -87,7 +87,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidationTest do
{: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
test "it's valid if the actor of the object is a local superuser",
diff --git a/test/web/activity_pub/transmogrifier/answer_handling_test.exs b/test/web/activity_pub/transmogrifier/answer_handling_test.exs
new file mode 100644
index 000000000..0f6605c3f
--- /dev/null
+++ b/test/web/activity_pub/transmogrifier/answer_handling_test.exs
@@ -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
diff --git a/test/web/activity_pub/transmogrifier/question_handling_test.exs b/test/web/activity_pub/transmogrifier/question_handling_test.exs
new file mode 100644
index 000000000..9fb965d7f
--- /dev/null
+++ b/test/web/activity_pub/transmogrifier/question_handling_test.exs
@@ -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
diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs
index 828964a36..6dd9a3fec 100644
--- a/test/web/activity_pub/transmogrifier_test.exs
+++ b/test/web/activity_pub/transmogrifier_test.exs
@@ -225,23 +225,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert Enum.at(object.data["tag"], 2) == "moo"
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
data = %{
"@context" => "https://www.w3.org/ns/activitystreams",
@@ -271,38 +254,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert object.data["length"] == 180_000
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
data =
File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!()
@@ -677,7 +628,8 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
%{
"href" =>
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
- "mediaType" => "video/mp4"
+ "mediaType" => "video/mp4",
+ "type" => "Link"
}
]
}
@@ -696,7 +648,8 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
%{
"href" =>
"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4",
- "mediaType" => "video/mp4"
+ "mediaType" => "video/mp4",
+ "type" => "Link"
}
]
}
@@ -1269,30 +1222,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
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
setup do
user = insert(:user)
@@ -1540,8 +1469,13 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
"attachment" => [
%{
"mediaType" => "video/mp4",
+ "type" => "Document",
"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 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
"attachment" => [
%{
"mediaType" => "video/mp4",
+ "type" => "Document",
"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",
+ "type" => "Document",
"url" => [
- %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
+ %{
+ "href" => "https://pe.er/stat-480.mp4",
+ "mediaType" => "video/mp4",
+ "type" => "Link"
+ }
]
}
]
diff --git a/test/web/admin_api/controllers/admin_api_controller_test.exs b/test/web/admin_api/controllers/admin_api_controller_test.exs
index b5d5bd8c7..eca9272e0 100644
--- a/test/web/admin_api/controllers/admin_api_controller_test.exs
+++ b/test/web/admin_api/controllers/admin_api_controller_test.exs
@@ -439,7 +439,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
user1: user1,
user2: user2
} 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(user2.id).tags == ["y", "foo", "bar"]
@@ -457,7 +457,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
end
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"]
end
end
@@ -485,7 +485,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
user1: user1,
user2: user2
} 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(user2.id).tags == ["y"]
@@ -503,7 +503,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
end
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"]
end
end
@@ -1164,6 +1164,27 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
}
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
admin = insert(:user, nickname: "john", is_admin: true)
token = insert(:oauth_admin_token, user: admin)
@@ -1756,7 +1777,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
conn =
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()
diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
index b888e4c71..2e6704726 100644
--- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
@@ -214,6 +214,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
assert user_data = json_response_and_validate_schema(conn, 200)
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
test "updates the user's avatar", %{user: user, conn: conn} do
diff --git a/test/web/mastodon_api/views/poll_view_test.exs b/test/web/mastodon_api/views/poll_view_test.exs
index 76672f36c..b7e2f17ef 100644
--- a/test/web/mastodon_api/views/poll_view_test.exs
+++ b/test/web/mastodon_api/views/poll_view_test.exs
@@ -135,4 +135,33 @@ defmodule Pleroma.Web.MastodonAPI.PollViewTest do
assert result[:expires_at] == nil
assert result[:expired] == false
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
diff --git a/test/web/metadata/rel_me_test.exs b/test/web/metadata/rel_me_test.exs
index 4107a8459..2293d6e13 100644
--- a/test/web/metadata/rel_me_test.exs
+++ b/test/web/metadata/rel_me_test.exs
@@ -9,13 +9,12 @@ defmodule Pleroma.Web.Metadata.Providers.RelMeTest do
test "it renders all links with rel='me' from user bio" do
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>
- <link href="http://some.com"> <link rel="me" href="http://some3.com>")
+ ~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">)
user = insert(:user, %{bio: bio})
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"], []}
]
end
diff --git a/test/web/oauth/ldap_authorization_test.exs b/test/web/oauth/ldap_authorization_test.exs
index 011642c08..63b1c0eb8 100644
--- a/test/web/oauth/ldap_authorization_test.exs
+++ b/test/web/oauth/ldap_authorization_test.exs
@@ -7,7 +7,6 @@ defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do
alias Pleroma.Repo
alias Pleroma.Web.OAuth.Token
import Pleroma.Factory
- import ExUnit.CaptureLog
import Mock
@skip if !Code.ensure_loaded?(:eldap), do: :skip
@@ -72,9 +71,7 @@ defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do
equalityMatch: fn _type, _value -> :ok end,
wholeSubtree: fn -> :ok end,
search: fn _connection, _options ->
- {:ok,
- {:eldap_search_result, [{:eldap_entry, '', [{'mail', [to_charlist(user.email)]}]}],
- []}}
+ {:ok, {:eldap_search_result, [{:eldap_entry, '', []}], []}}
end,
close: fn _connection ->
send(self(), :close_connection)
@@ -102,50 +99,6 @@ defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do
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
test "disallow authorization for wrong LDAP credentials" do
password = "testpassword"
user = insert(:user, password_hash: Pbkdf2.hash_pwd_salt(password))