Merge branch 'develop' into issue/1383
authorMaksim Pechnikov <parallel588@gmail.com>
Mon, 9 Dec 2019 16:41:43 +0000 (19:41 +0300)
committerMaksim Pechnikov <parallel588@gmail.com>
Mon, 9 Dec 2019 16:41:43 +0000 (19:41 +0300)
37 files changed:
.formatter.exs
CHANGELOG.md
docs/API/differences_in_mastoapi_responses.md
docs/API/pleroma_api.md
docs/administration/CLI_tasks/config.md
docs/administration/CLI_tasks/database.md
docs/administration/CLI_tasks/digest.md
docs/administration/CLI_tasks/emoji.md
docs/administration/CLI_tasks/general_cli_task_info.include [new file with mode: 0644]
docs/administration/CLI_tasks/instance.md
docs/administration/CLI_tasks/relay.md
docs/administration/CLI_tasks/uploads.md
docs/administration/CLI_tasks/user.md
lib/mix/tasks/pleroma/notification_settings.ex [new file with mode: 0644]
lib/mix/tasks/pleroma/user.ex
lib/pleroma/application.ex
lib/pleroma/html.ex
lib/pleroma/notification.ex
lib/pleroma/plugs/parsers_plug.ex [new file with mode: 0644]
lib/pleroma/user.ex
lib/pleroma/user/notification_setting.ex [new file with mode: 0644]
lib/pleroma/web/endpoint.ex
lib/pleroma/web/push/impl.ex
lib/pleroma/workers/web_pusher_worker.ex
priv/scrubbers/default.ex [new file with mode: 0644]
priv/scrubbers/links_only.ex [new file with mode: 0644]
priv/scrubbers/media_proxy.ex [new file with mode: 0644]
priv/scrubbers/twitter_text.ex [new file with mode: 0644]
test/notification_test.exs
test/support/builders/user_builder.ex
test/support/factory.ex
test/user/notification_setting_test.exs [new file with mode: 0644]
test/user_search_test.exs
test/web/mastodon_api/controllers/notification_controller_test.exs
test/web/mastodon_api/views/account_view_test.exs
test/web/push/impl_test.exs
test/web/twitter_api/util_controller_test.exs

index 7fa95a6190189ee1602c5dd5ba039c10910e00a4..5799ac127aa70ed8856089106c704ca68f8a1958 100644 (file)
@@ -1,3 +1,3 @@
 [
-  inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}", "priv/repo/migrations/*.exs"]
+  inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}", "priv/repo/migrations/*.exs", "priv/scrubbers/*.ex"]
 ]
index d000977482f42fed2265c1a57193aa688ed28843..89044d0462ce3047af98c21a604a18d99b7fd762 100644 (file)
@@ -48,6 +48,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mix task to list all users (`mix pleroma.user list`)
 - Support for `X-Forwarded-For` and similar HTTP headers which used by reverse proxies to pass a real user IP address to the backend. Must not be enabled unless your instance is behind at least one reverse proxy (such as Nginx, Apache HTTPD or Varnish Cache).
 - MRF: New module which handles incoming posts based on their age. By default, all incoming posts that are older than 2 days will be unlisted and not shown to their followers.
+- User notification settings: Add `privacy_option` option.
 <details>
   <summary>API Changes</summary>
 
@@ -82,6 +83,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Report emails now include functional links to profiles of remote user accounts
 - Not being able to log in to some third-party apps when logged in to MastoFE
 - MRF: `Delete` activities being exempt from MRF policies
+- OTP releases: Not being able to configure OAuth expired token cleanup interval
+- OTP releases: Not being able to configure HTML sanitization policy
 <details>
   <summary>API Changes</summary>
 
index 006d17c1bc28abd63ae6331f0cf9e9ab99dbac9a..566789ec74809801d98656734612bbac5256cf10 100644 (file)
@@ -103,6 +103,7 @@ The `type` value is `move`. Has an additional field:
 Accepts additional parameters:
 
 - `exclude_visibilities`: will exclude the notifications for activities with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`). Usage example: `GET /api/v1/notifications?exclude_visibilities[]=direct&exclude_visibilities[]=private`.
+- `with_move`: boolean, when set to `true` will include Move notifications. `false` by default.
 
 ## POST `/api/v1/statuses`
 
index ad16d027e6e8d311741f6be092197d2b79f63db8..7228d805b5a7e850628f9b9168febf3ce7f8c9c5 100644 (file)
@@ -302,6 +302,7 @@ See [Admin-API](admin_api.md)
     * `follows`: BOOLEAN field, receives notifications from people the user follows
     * `remote`: BOOLEAN field, receives notifications from people on remote instances
     * `local`: BOOLEAN field, receives notifications from people on the local instance
+    * `privacy_option`: BOOLEAN field. When set to true, it removes the contents of a message from the push notification.
 * Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}`
 
 ## `/api/pleroma/healthcheck`
index ce19e2402ac0911d56ec06310c81b6612c8982ba..e9d44b9a426f0d55e157a36f1ba48a8b70a7c5b6 100644 (file)
@@ -3,17 +3,26 @@
 !!! danger
     This is a Work In Progress, not usable just yet.
 
-Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl config` and in case of source installs it's
-`mix pleroma.config`.
+{! backend/administration/CLI_tasks/general_cli_task_info.include !}
 
 ## Transfer config from file to DB.
 
-```sh
-$PREFIX migrate_to_db
+```sh tab="OTP"
+ ./bin/pleroma_ctl config migrate_to_db
 ```
 
+```sh tab="From Source"
+mix pleroma.config migrate_to_db
+```
+
+
 ## Transfer config from DB to `config/env.exported_from_db.secret.exs`
 
-```sh
-$PREFIX migrate_from_db <env>
+```sh tab="OTP"
+ ./bin/pleroma_ctl config migrate_from_db <env>
 ```
+
+```sh tab="From Source"
+mix pleroma.config migrate_from_db <env>
+```
+
index 3011646c8bf65ea9342f154f439e97490a8a99ea..51c7484ba21343153ac310408b59fd4755556c2d 100644 (file)
@@ -1,6 +1,6 @@
 # Database maintenance tasks
 
-Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl database` and in case of source installs it's `mix pleroma.database`.
+{! backend/administration/CLI_tasks/general_cli_task_info.include !}
 
 !!! danger
     These mix tasks can take a long time to complete. Many of them were written to address specific database issues that happened because of bugs in migrations or other specific scenarios. Do not run these tasks "just in case" if everything is fine your instance.
@@ -9,8 +9,12 @@ Every command should be ran with a prefix, in case of OTP releases it is `./bin/
 
 Replaces embedded objects with references to them in the `objects` table. Only needs to be ran once if the instance was created before Pleroma 1.0.5. The reason why this is not a migration is because it could significantly increase the database size after being ran, however after this `VACUUM FULL` will be able to reclaim about 20% (really depends on what is in the database, your mileage may vary) of the db size before the migration.
 
-```sh
-$PREFIX remove_embedded_objects [<options>]
+```sh tab="OTP"
+./bin/pleroma_ctl database remove_embedded_objects [<options>]
+```
+
+```sh tab="From Source"
+mix pleroma.database remove_embedded_objects [<options>]
 ```
 
 ### Options
@@ -20,11 +24,15 @@ $PREFIX remove_embedded_objects [<options>]
 
 This will prune remote posts older than 90 days (configurable with [`config :pleroma, :instance, remote_post_retention_days`](../../configuration/cheatsheet.md#instance)) from the database, they will be refetched from source when accessed.
 
-!!! note
-    The disk space will only be reclaimed after `VACUUM FULL`
+!!! danger
+    The disk space will only be reclaimed after `VACUUM FULL`. You may run out of disk space during the execution of the task or vacuuming if you don't have about 1/3rds of the database size free.
+
+```sh tab="OTP"
+./bin/pleroma_ctl database prune_objects [<options>]
+```
 
-```sh
-$PREFIX pleroma.database prune_objects [<options>]
+```sh tab="From Source"
+mix pleroma.database prune_objects [<options>]
 ```
 
 ### Options
@@ -34,18 +42,30 @@ $PREFIX pleroma.database prune_objects [<options>]
 
 Can be safely re-run
 
-```sh
-$PREFIX bump_all_conversations
+```sh tab="OTP"
+./bin/pleroma_ctl database bump_all_conversations
+```
+
+```sh tab="From Source"
+mix pleroma.database bump_all_conversations
 ```
 
 ## Remove duplicated items from following and update followers count for all users
 
-```sh
-$PREFIX update_users_following_followers_counts
+```sh tab="OTP"
+./bin/pleroma_ctl database update_users_following_followers_counts
+```
+
+```sh tab="From Source"
+mix pleroma.database update_users_following_followers_counts
 ```
 
 ## Fix the pre-existing "likes" collections for all objects
 
-```sh
-$PREFIX fix_likes_collections
+```sh tab="OTP"
+./bin/pleroma_ctl database fix_likes_collections
+```
+
+```sh tab="From Source"
+mix pleroma.database fix_likes_collections
 ```
index 5477020318bafb9987e489ba171d4931abf0cf4b..a70f24c061b17624c6fa9fbf536abe9b45467b15 100644 (file)
@@ -1,13 +1,24 @@
 # Managing digest emails
-Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl digest` and in case of source installs it's `mix pleroma.digest`.
+
+{! backend/administration/CLI_tasks/general_cli_task_info.include !}
 
 ## Send digest email since given date (user registration date by default) ignoring user activity status.
 
-```sh
-$PREFIX test <nickname> [<since_date>]
+```sh tab="OTP"
+ ./bin/pleroma_ctl digest test <nickname> [<since_date>]
+```
+
+```sh tab="From Source"
+mix pleroma.digest test <nickname> [<since_date>]
 ```
 
+
 Example: 
-```sh
-$PREFIX test donaldtheduck 2019-05-20
+```sh tab="OTP"
+ ./bin/pleroma_ctl digest test donaldtheduck 2019-05-20
 ```
+
+```sh tab="From Source"
+mix pleroma.digest test donaldtheduck 2019-05-20
+```
+
index eee02f2ef30bc2057a34d02c5ec53a642fcb3edb..a3207bc6c290a3940c2703ba52265ce10732308d 100644 (file)
@@ -1,28 +1,44 @@
 # Managing emoji packs
 
-Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl emoji` and in case of source installs it's `mix pleroma.emoji`.
+{! backend/administration/CLI_tasks/general_cli_task_info.include !}
 
 ## Lists emoji packs and metadata specified in the manifest
 
-```sh
-$PREFIX ls-packs [<options>]
+```sh tab="OTP"
+./bin/pleroma_ctl emoji ls-packs [<options>]
 ```
 
+```sh tab="From Source"
+mix pleroma.emoji ls-packs [<options>]
+```
+
+
 ### Options
 - `-m, --manifest PATH/URL` - path to a custom manifest, it can either be an URL starting with `http`, in that case the manifest will be fetched from that address, or a local path
 
 ## Fetch, verify and install the specified packs from the manifest into `STATIC-DIR/emoji/PACK-NAME`
-```sh
-$PREFIX get-packs [<options>] <packs>
+
+```sh tab="OTP"
+./bin/pleroma_ctl emoji get-packs [<options>] <packs>
+```
+
+```sh tab="From Source"
+mix pleroma.emoji get-packs [<options>] <packs>
 ```
 
 ### Options
 - `-m, --manifest PATH/URL` - same as [`ls-packs`](#ls-packs)
 
 ## Create a new manifest entry and a file list from the specified remote pack file
-```sh
-$PREFIX gen-pack PACK-URL
+
+```sh tab="OTP"
+./bin/pleroma_ctl emoji gen-pack PACK-URL
 ```
+
+```sh tab="From Source"
+mix pleroma.emoji gen-pack PACK-URL
+```
+
 Currently, only .zip archives are recognized as remote pack files and packs are therefore assumed to be zip archives. This command is intended to run interactively and will first ask you some basic questions about the pack, then download the remote file and generate an SHA256 checksum for it, then generate an emoji file list for you. 
 
   The manifest entry will either be written to a newly created `index.json` file or appended to the existing one, *replacing* the old pack with the same name if it was in the file previously.
diff --git a/docs/administration/CLI_tasks/general_cli_task_info.include b/docs/administration/CLI_tasks/general_cli_task_info.include
new file mode 100644 (file)
index 0000000..a1ff1da
--- /dev/null
@@ -0,0 +1,5 @@
+Every command should be ran as the `pleroma` user from it's home directory. For example if you are superuser, you would have to wrap the command in `su pleroma -s $SHELL -lc "$COMMAND"`.
+
+??? note "From source note about `MIX_ENV`"
+
+     The `mix` command should be prefixed with the name of environment your Pleroma server is running in, usually it's `MIX_ENV=prod`
index ab0b68ad0e487bb88dc75b1d4bb28eda4f820ea4..1a3b268be52f7e6aa2af8d5adb74ac317603b579 100644 (file)
@@ -1,12 +1,17 @@
 # Managing instance configuration
 
-Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl instance` and in case of source installs it's `mix pleroma.instance`.
+{! backend/administration/CLI_tasks/general_cli_task_info.include !}
 
 ## Generate a new configuration file
-```sh
-$PREFIX gen [<options>]
+```sh tab="OTP"
+ ./bin/pleroma_ctl instance gen [<options>]
 ```
 
+```sh tab="From Source"
+mix pleroma.instance gen [<options>]
+```
+
+
 If any of the options are left unspecified, you will be prompted interactively.
 
 ### Options
index aa44617dfbba08822da6da6ed82df1533e9d14bc..c4f078f4d1a2f4a5b3f037d67bca3829c92610dc 100644 (file)
@@ -1,30 +1,33 @@
 # Managing relays
 
-Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl relay` and in case of source installs it's `mix pleroma.relay`.
+{! backend/administration/CLI_tasks/general_cli_task_info.include !}
 
 ## Follow a relay
-```sh
-$PREFIX follow <relay_url>
+
+```sh tab="OTP"
+./bin/pleroma_ctl relay follow <relay_url>
 ```
 
-Example:
-```sh
-$PREFIX follow https://example.org/relay
+```sh tab="From Source"
+mix pleroma.relay follow <relay_url>
 ```
 
 ## Unfollow a remote relay
 
-```sh
-$PREFIX unfollow <relay_url>
+```sh tab="OTP"
+./bin/pleroma_ctl relay unfollow <relay_url>
 ```
 
-Example:
-```sh
-$PREFIX unfollow https://example.org/relay
+```sh tab="From Source"
+mix pleroma.relay unfollow <relay_url>
 ```
 
 ## List relay subscriptions
 
-```sh
-$PREFIX list
+```sh tab="OTP"
+./bin/pleroma_ctl relay list
+```
+
+```sh tab="From Source"
+mix pleroma.relay list
 ```
index 71800e341944c269989a01d5dcc0e43a78425508..e36c94c389985ab09dc551146fb69d64433e847c 100644 (file)
@@ -1,11 +1,16 @@
 # Managing uploads
 
-Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl uploads` and in case of source installs it's `mix pleroma.uploads`.
+{! backend/administration/CLI_tasks/general_cli_task_info.include !}
 
 ## Migrate uploads from local to remote storage
-```sh
-$PREFIX migrate_local <target_uploader> [<options>]
+```sh tab="OTP"
+ ./bin/pleroma_ctl uploads migrate_local <target_uploader> [<options>]
 ```
+
+```sh tab="From Source"
+mix pleroma.uploads migrate_local <target_uploader> [<options>]
+```
+
 ### Options
 - `--delete` - delete local uploads after migrating them to the target uploader
 
index 96b2d9e6a3326fafcd5bf54e0cceeaa88a288bf0..da8363131b0167408542b8618b4abe0f7a9171f5 100644 (file)
@@ -1,12 +1,18 @@
 # Managing users
 
-Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl user` and in case of source installs it's `mix pleroma.user`.
+{! backend/administration/CLI_tasks/general_cli_task_info.include !}
 
 ## Create a user
-```sh
-$PREFIX new <nickname> <email> [<options>]
+
+```sh tab="OTP"
+./bin/pleroma_ctl user new <email> [<options>]
+```
+
+```sh tab="From Source"
+mix pleroma.user new <email> [<options>]
 ```
 
+
 ### Options
 - `--name <name>` - the user's display name
 - `--bio <bio>` - the user's bio
@@ -16,84 +22,159 @@ $PREFIX new <nickname> <email> [<options>]
 - `-y`, `--assume-yes`/`--no-assume-yes` - whether to assume yes to all questions
 
 ## List local users
-```sh
-$PREFIX list
+```sh tab="OTP"
+ ./bin/pleroma_ctl user list
 ```
 
+```sh tab="From Source"
+mix pleroma.user list
+```
+
+
 ## Generate an invite link
-```sh
-$PREFIX invite [<options>]
+```sh tab="OTP"
+ ./bin/pleroma_ctl user invite [<options>]
 ```
 
+```sh tab="From Source"
+mix pleroma.user invite [<options>]
+```
+
+
 ### Options
 - `--expires-at DATE` - last day on which token is active (e.g. "2019-04-05")
 - `--max-use NUMBER` - maximum numbers of token uses
 
 ## List generated invites
-```sh
-$PREFIX invites
+```sh tab="OTP"
+ ./bin/pleroma_ctl user invites
 ```
 
+```sh tab="From Source"
+mix pleroma.user invites
+```
+
+
 ## Revoke invite
-```sh
-$PREFIX revoke_invite <token_or_id>
+```sh tab="OTP"
+ ./bin/pleroma_ctl user revoke_invite <token_or_id>
 ```
 
+```sh tab="From Source"
+mix pleroma.user revoke_invite <token_or_id>
+```
+
+
 ## Delete a user
-```sh
-$PREFIX rm <nickname>
+```sh tab="OTP"
+ ./bin/pleroma_ctl user rm <nickname>
 ```
 
+```sh tab="From Source"
+mix pleroma.user rm <nickname>
+```
+
+
 ## Delete user's posts and interactions
-```sh
-$PREFIX delete_activities <nickname>
+```sh tab="OTP"
+ ./bin/pleroma_ctl user delete_activities <nickname>
 ```
 
+```sh tab="From Source"
+mix pleroma.user delete_activities <nickname>
+```
+
+
 ## Sign user out from all applications (delete user's OAuth tokens and authorizations)
-```sh
-$PREFIX sign_out <nickname>
+```sh tab="OTP"
+ ./bin/pleroma_ctl user sign_out <nickname>
 ```
 
+```sh tab="From Source"
+mix pleroma.user sign_out <nickname>
+```
+
+
 ## Deactivate or activate a user 
-```sh
-$PREFIX toggle_activated <nickname> 
+```sh tab="OTP"
+ ./bin/pleroma_ctl user toggle_activated <nickname> 
 ```
 
+```sh tab="From Source"
+mix pleroma.user toggle_activated <nickname> 
+```
+
+
 ## Unsubscribe local users from a user and deactivate the user
-```sh
-$PREFIX unsubscribe NICKNAME
+```sh tab="OTP"
+ ./bin/pleroma_ctl user unsubscribe NICKNAME
 ```
 
+```sh tab="From Source"
+mix pleroma.user unsubscribe NICKNAME
+```
+
+
 ## Unsubscribe local users from an instance and deactivate all accounts on it
-```sh
-$PREFIX unsubscribe_all_from_instance <instance>
+```sh tab="OTP"
+ ./bin/pleroma_ctl user unsubscribe_all_from_instance <instance>
 ```
 
+```sh tab="From Source"
+mix pleroma.user unsubscribe_all_from_instance <instance>
+```
+
+
 ## Create a password reset link for user
-```sh
-$PREFIX reset_password <nickname>
+```sh tab="OTP"
+ ./bin/pleroma_ctl user reset_password <nickname>
 ```
 
+```sh tab="From Source"
+mix pleroma.user reset_password <nickname>
+```
+
+
 ## Set the value of the given user's settings
-```sh
-$PREFIX set <nickname> [<options>]
+```sh tab="OTP"
+ ./bin/pleroma_ctl user set <nickname> [<options>]
 ```
+
+```sh tab="From Source"
+mix pleroma.user set <nickname> [<options>]
+```
+
 ### Options
 - `--locked`/`--no-locked` - whether the user should be locked
 - `--moderator`/`--no-moderator` - whether the user should be a moderator
 - `--admin`/`--no-admin` - whether the user should be an admin
 
 ## Add tags to a user
-```sh
-$PREFIX tag <nickname> <tags>
+```sh tab="OTP"
+ ./bin/pleroma_ctl user tag <nickname> <tags>
 ```
 
+```sh tab="From Source"
+mix pleroma.user tag <nickname> <tags>
+```
+
+
 ## Delete tags from a user
-```sh
-$PREFIX untag <nickname> <tags>
+```sh tab="OTP"
+ ./bin/pleroma_ctl user untag <nickname> <tags>
 ```
 
+```sh tab="From Source"
+mix pleroma.user untag <nickname> <tags>
+```
+
+
 ## Toggle confirmation status of the user
-```sh
-$PREFIX toggle_confirmed <nickname>
+```sh tab="OTP"
+ ./bin/pleroma_ctl user toggle_confirmed <nickname>
 ```
+
+```sh tab="From Source"
+mix pleroma.user toggle_confirmed <nickname>
+```
+
diff --git a/lib/mix/tasks/pleroma/notification_settings.ex b/lib/mix/tasks/pleroma/notification_settings.ex
new file mode 100644 (file)
index 0000000..7d65f05
--- /dev/null
@@ -0,0 +1,83 @@
+defmodule Mix.Tasks.Pleroma.NotificationSettings do
+  @shortdoc "Enable&Disable privacy option for push notifications"
+  @moduledoc """
+  Example:
+
+  > mix pleroma.notification_settings --privacy-option=false --nickname-users="parallel588"  # set false only for parallel588 user
+  > mix pleroma.notification_settings --privacy-option=true # set true for all users
+
+  """
+
+  use Mix.Task
+  import Mix.Pleroma
+  import Ecto.Query
+
+  def run(args) do
+    start_pleroma()
+
+    {options, _, _} =
+      OptionParser.parse(
+        args,
+        strict: [
+          privacy_option: :boolean,
+          email_users: :string,
+          nickname_users: :string
+        ]
+      )
+
+    privacy_option = Keyword.get(options, :privacy_option)
+
+    if not is_nil(privacy_option) do
+      privacy_option
+      |> build_query(options)
+      |> Pleroma.Repo.update_all([])
+    end
+
+    shell_info("Done")
+  end
+
+  defp build_query(privacy_option, options) do
+    query =
+      from(u in Pleroma.User,
+        update: [
+          set: [
+            notification_settings:
+              fragment(
+                "jsonb_set(notification_settings, '{privacy_option}', ?)",
+                ^privacy_option
+              )
+          ]
+        ]
+      )
+
+    user_emails =
+      options
+      |> Keyword.get(:email_users, "")
+      |> String.split(",")
+      |> Enum.map(&String.trim(&1))
+      |> Enum.reject(&(&1 == ""))
+
+    query =
+      if length(user_emails) > 0 do
+        where(query, [u], u.email in ^user_emails)
+      else
+        query
+      end
+
+    user_nicknames =
+      options
+      |> Keyword.get(:nickname_users, "")
+      |> String.split(",")
+      |> Enum.map(&String.trim(&1))
+      |> Enum.reject(&(&1 == ""))
+
+    query =
+      if length(user_nicknames) > 0 do
+        where(query, [u], u.nickname in ^user_nicknames)
+      else
+        query
+      end
+
+    query
+  end
+end
index bc8eacda82aa3ac23774605db4d52d3f98cdcc1c..0adb78fe3727ed83868ad424a59a2bdb25db475a 100644 (file)
@@ -373,9 +373,9 @@ defmodule Mix.Tasks.Pleroma.User do
       users
       |> Enum.each(fn user ->
         shell_info(
-          "#{user.nickname} moderator: #{user.info.is_moderator}, admin: #{user.info.is_admin}, locked: #{
-            user.info.locked
-          }, deactivated: #{user.info.deactivated}"
+          "#{user.nickname} moderator: #{user.is_moderator}, admin: #{user.is_admin}, locked: #{
+            user.locked
+          }, deactivated: #{user.deactivated}"
         )
       end)
     end)
index 0f7b40840e79ef22c7695251aefc5ff972a82390..ab7f6d50215f2307641b2a23222b2b481af494ea 100644 (file)
@@ -30,6 +30,7 @@ defmodule Pleroma.Application do
   # See http://elixir-lang.org/docs/stable/elixir/Application.html
   # for more information on OTP Applications
   def start(_type, _args) do
+    Pleroma.HTML.compile_scrubbers()
     Pleroma.Config.DeprecationWarnings.warn()
     setup_instrumenters()
 
index 71c53ce0ed7f6d827b522a814663882bd3ea15b8..2cae29f35170fe5b952551b405db677c8baba9f2 100644 (file)
@@ -3,6 +3,25 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.HTML do
+  # Scrubbers are compiled on boot so they can be configured in OTP releases
+  #  @on_load :compile_scrubbers
+
+  def compile_scrubbers do
+    dir = Path.join(:code.priv_dir(:pleroma), "scrubbers")
+
+    dir
+    |> File.ls!()
+    |> Enum.map(&Path.join(dir, &1))
+    |> Kernel.ParallelCompiler.compile()
+    |> case do
+      {:error, _errors, _warnings} ->
+        raise "Compiling scrubbers failed"
+
+      {:ok, _modules, _warnings} ->
+        :ok
+    end
+  end
+
   defp get_scrubbers(scrubber) when is_atom(scrubber), do: [scrubber]
   defp get_scrubbers(scrubbers) when is_list(scrubbers), do: scrubbers
   defp get_scrubbers(_), do: [Pleroma.HTML.Scrubber.Default]
@@ -99,216 +118,3 @@ defmodule Pleroma.HTML do
     end)
   end
 end
-
-defmodule Pleroma.HTML.Scrubber.TwitterText do
-  @moduledoc """
-  An HTML scrubbing policy which limits to twitter-style text.  Only
-  paragraphs, breaks and links are allowed through the filter.
-  """
-
-  @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
-
-  require FastSanitize.Sanitizer.Meta
-  alias FastSanitize.Sanitizer.Meta
-
-  Meta.strip_comments()
-
-  # links
-  Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes)
-
-  Meta.allow_tag_with_this_attribute_values(:a, "class", [
-    "hashtag",
-    "u-url",
-    "mention",
-    "u-url mention",
-    "mention u-url"
-  ])
-
-  Meta.allow_tag_with_this_attribute_values(:a, "rel", [
-    "tag",
-    "nofollow",
-    "noopener",
-    "noreferrer"
-  ])
-
-  Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
-
-  # paragraphs and linebreaks
-  Meta.allow_tag_with_these_attributes(:br, [])
-  Meta.allow_tag_with_these_attributes(:p, [])
-
-  # microformats
-  Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
-  Meta.allow_tag_with_these_attributes(:span, [])
-
-  # allow inline images for custom emoji
-  if Pleroma.Config.get([:markup, :allow_inline_images]) do
-    # restrict img tags to http/https only, because of MediaProxy.
-    Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
-
-    Meta.allow_tag_with_these_attributes(:img, [
-      "width",
-      "height",
-      "class",
-      "title",
-      "alt"
-    ])
-  end
-
-  Meta.strip_everything_not_covered()
-end
-
-defmodule Pleroma.HTML.Scrubber.Default do
-  @doc "The default HTML scrubbing policy: no "
-
-  require FastSanitize.Sanitizer.Meta
-  alias FastSanitize.Sanitizer.Meta
-
-  # credo:disable-for-previous-line
-  # No idea how to fix this one…
-
-  @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
-
-  Meta.strip_comments()
-
-  Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes)
-
-  Meta.allow_tag_with_this_attribute_values(:a, "class", [
-    "hashtag",
-    "u-url",
-    "mention",
-    "u-url mention",
-    "mention u-url"
-  ])
-
-  Meta.allow_tag_with_this_attribute_values(:a, "rel", [
-    "tag",
-    "nofollow",
-    "noopener",
-    "noreferrer",
-    "ugc"
-  ])
-
-  Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
-
-  Meta.allow_tag_with_these_attributes(:abbr, ["title"])
-
-  Meta.allow_tag_with_these_attributes(:b, [])
-  Meta.allow_tag_with_these_attributes(:blockquote, [])
-  Meta.allow_tag_with_these_attributes(:br, [])
-  Meta.allow_tag_with_these_attributes(:code, [])
-  Meta.allow_tag_with_these_attributes(:del, [])
-  Meta.allow_tag_with_these_attributes(:em, [])
-  Meta.allow_tag_with_these_attributes(:i, [])
-  Meta.allow_tag_with_these_attributes(:li, [])
-  Meta.allow_tag_with_these_attributes(:ol, [])
-  Meta.allow_tag_with_these_attributes(:p, [])
-  Meta.allow_tag_with_these_attributes(:pre, [])
-  Meta.allow_tag_with_these_attributes(:strong, [])
-  Meta.allow_tag_with_these_attributes(:sub, [])
-  Meta.allow_tag_with_these_attributes(:sup, [])
-  Meta.allow_tag_with_these_attributes(:u, [])
-  Meta.allow_tag_with_these_attributes(:ul, [])
-
-  Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
-  Meta.allow_tag_with_these_attributes(:span, [])
-
-  @allow_inline_images Pleroma.Config.get([:markup, :allow_inline_images])
-
-  if @allow_inline_images do
-    # restrict img tags to http/https only, because of MediaProxy.
-    Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
-
-    Meta.allow_tag_with_these_attributes(:img, [
-      "width",
-      "height",
-      "class",
-      "title",
-      "alt"
-    ])
-  end
-
-  if Pleroma.Config.get([:markup, :allow_tables]) do
-    Meta.allow_tag_with_these_attributes(:table, [])
-    Meta.allow_tag_with_these_attributes(:tbody, [])
-    Meta.allow_tag_with_these_attributes(:td, [])
-    Meta.allow_tag_with_these_attributes(:th, [])
-    Meta.allow_tag_with_these_attributes(:thead, [])
-    Meta.allow_tag_with_these_attributes(:tr, [])
-  end
-
-  if Pleroma.Config.get([:markup, :allow_headings]) do
-    Meta.allow_tag_with_these_attributes(:h1, [])
-    Meta.allow_tag_with_these_attributes(:h2, [])
-    Meta.allow_tag_with_these_attributes(:h3, [])
-    Meta.allow_tag_with_these_attributes(:h4, [])
-    Meta.allow_tag_with_these_attributes(:h5, [])
-  end
-
-  if Pleroma.Config.get([:markup, :allow_fonts]) do
-    Meta.allow_tag_with_these_attributes(:font, ["face"])
-  end
-
-  Meta.strip_everything_not_covered()
-end
-
-defmodule Pleroma.HTML.Transform.MediaProxy do
-  @moduledoc "Transforms inline image URIs to use MediaProxy."
-
-  alias Pleroma.Web.MediaProxy
-
-  def before_scrub(html), do: html
-
-  def scrub_attribute(:img, {"src", "http" <> target}) do
-    media_url =
-      ("http" <> target)
-      |> MediaProxy.url()
-
-    {"src", media_url}
-  end
-
-  def scrub_attribute(_tag, attribute), do: attribute
-
-  def scrub({:img, attributes, children}) do
-    attributes =
-      attributes
-      |> Enum.map(fn attr -> scrub_attribute(:img, attr) end)
-      |> Enum.reject(&is_nil(&1))
-
-    {:img, attributes, children}
-  end
-
-  def scrub({:comment, _text, _children}), do: ""
-
-  def scrub({tag, attributes, children}), do: {tag, attributes, children}
-  def scrub({_tag, children}), do: children
-  def scrub(text), do: text
-end
-
-defmodule Pleroma.HTML.Scrubber.LinksOnly do
-  @moduledoc """
-  An HTML scrubbing policy which limits to links only.
-  """
-
-  @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
-
-  require FastSanitize.Sanitizer.Meta
-  alias FastSanitize.Sanitizer.Meta
-
-  Meta.strip_comments()
-
-  # links
-  Meta.allow_tag_with_uri_attributes(:a, ["href"], @valid_schemes)
-
-  Meta.allow_tag_with_this_attribute_values(:a, "rel", [
-    "tag",
-    "nofollow",
-    "noopener",
-    "noreferrer",
-    "me",
-    "ugc"
-  ])
-
-  Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
-  Meta.strip_everything_not_covered()
-end
index 71423ce5e89695175073e6d44d41bfaba56b5f88..8f3e46af98d187e52949b4f9bdf4eb3dcda49fc1 100644 (file)
@@ -121,10 +121,28 @@ defmodule Pleroma.Notification do
        when is_list(visibility) do
     if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
       query
+      |> join(:left, [n, a], mutated_activity in Pleroma.Activity,
+        on:
+          fragment("?->>'context'", a.data) ==
+            fragment("?->>'context'", mutated_activity.data) and
+            fragment("(?->>'type' = 'Like' or ?->>'type' = 'Announce')", a.data, a.data) and
+            fragment("?->>'type'", mutated_activity.data) == "Create",
+        as: :mutated_activity
+      )
       |> where(
-        [n, a],
+        [n, a, mutated_activity: mutated_activity],
         not fragment(
-          "activity_visibility(?, ?, ?) = ANY (?)",
+          """
+          CASE WHEN (?->>'type') = 'Like' or (?->>'type') = 'Announce'
+            THEN (activity_visibility(?, ?, ?) = ANY (?))
+            ELSE (activity_visibility(?, ?, ?) = ANY (?)) END
+          """,
+          a.data,
+          a.data,
+          mutated_activity.actor,
+          mutated_activity.recipients,
+          mutated_activity.data,
+          ^visibility,
           a.actor,
           a.recipients,
           a.data,
@@ -139,17 +157,7 @@ defmodule Pleroma.Notification do
 
   defp exclude_visibility(query, %{exclude_visibilities: visibility})
        when visibility in @valid_visibilities do
-    query
-    |> where(
-      [n, a],
-      not fragment(
-        "activity_visibility(?, ?, ?) = (?)",
-        a.actor,
-        a.recipients,
-        a.data,
-        ^visibility
-      )
-    )
+    exclude_visibility(query, [visibility])
   end
 
   defp exclude_visibility(query, %{exclude_visibilities: visibility})
@@ -347,7 +355,7 @@ defmodule Pleroma.Notification do
   def skip?(
         :followers,
         activity,
-        %{notification_settings: %{"followers" => false}} = user
+        %{notification_settings: %{followers: false}} = user
       ) do
     actor = activity.data["actor"]
     follower = User.get_cached_by_ap_id(actor)
@@ -357,14 +365,14 @@ defmodule Pleroma.Notification do
   def skip?(
         :non_followers,
         activity,
-        %{notification_settings: %{"non_followers" => false}} = user
+        %{notification_settings: %{non_followers: false}} = user
       ) do
     actor = activity.data["actor"]
     follower = User.get_cached_by_ap_id(actor)
     !User.following?(follower, user)
   end
 
-  def skip?(:follows, activity, %{notification_settings: %{"follows" => false}} = user) do
+  def skip?(:follows, activity, %{notification_settings: %{follows: false}} = user) do
     actor = activity.data["actor"]
     followed = User.get_cached_by_ap_id(actor)
     User.following?(user, followed)
@@ -373,7 +381,7 @@ defmodule Pleroma.Notification do
   def skip?(
         :non_follows,
         activity,
-        %{notification_settings: %{"non_follows" => false}} = user
+        %{notification_settings: %{non_follows: false}} = user
       ) do
     actor = activity.data["actor"]
     followed = User.get_cached_by_ap_id(actor)
diff --git a/lib/pleroma/plugs/parsers_plug.ex b/lib/pleroma/plugs/parsers_plug.ex
new file mode 100644 (file)
index 0000000..2e493ce
--- /dev/null
@@ -0,0 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.Parsers do
+  @moduledoc "Initializes Plug.Parsers with upload limit set at boot time"
+
+  @behaviour Plug
+
+  def init(_opts) do
+    Plug.Parsers.init(
+      parsers: [:urlencoded, :multipart, :json],
+      pass: ["*/*"],
+      json_decoder: Jason,
+      length: Pleroma.Config.get([:instance, :upload_limit]),
+      body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
+    )
+  end
+
+  defdelegate call(conn, opts), to: Plug.Parsers
+end
index b7f50e5acb4cbbf2add0b8ae6739b79acaa1568f..e2afc6de8215b3555f41a8f44b56984e388ecac5 100644 (file)
@@ -129,13 +129,10 @@ defmodule Pleroma.User do
     field(:skip_thread_containment, :boolean, default: false)
     field(:also_known_as, {:array, :string}, default: [])
 
-    field(:notification_settings, :map,
-      default: %{
-        "followers" => true,
-        "follows" => true,
-        "non_follows" => true,
-        "non_followers" => true
-      }
+    embeds_one(
+      :notification_settings,
+      Pleroma.User.NotificationSetting,
+      on_replace: :update
     )
 
     has_many(:notifications, Notification)
@@ -1221,20 +1218,9 @@ defmodule Pleroma.User do
   end
 
   def update_notification_settings(%User{} = user, settings) do
-    settings =
-      settings
-      |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
-      |> Map.new()
-
-    notification_settings =
-      user.notification_settings
-      |> Map.merge(settings)
-      |> Map.take(["followers", "follows", "non_follows", "non_followers"])
-
-    params = %{notification_settings: notification_settings}
-
     user
-    |> cast(params, [:notification_settings])
+    |> cast(%{notification_settings: settings}, [])
+    |> cast_embed(:notification_settings)
     |> validate_required([:notification_settings])
     |> update_and_set_cache()
   end
diff --git a/lib/pleroma/user/notification_setting.ex b/lib/pleroma/user/notification_setting.ex
new file mode 100644 (file)
index 0000000..f089961
--- /dev/null
@@ -0,0 +1,40 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.User.NotificationSetting do
+  use Ecto.Schema
+  import Ecto.Changeset
+
+  @derive Jason.Encoder
+  @primary_key false
+
+  embedded_schema do
+    field(:followers, :boolean, default: true)
+    field(:follows, :boolean, default: true)
+    field(:non_follows, :boolean, default: true)
+    field(:non_followers, :boolean, default: true)
+    field(:privacy_option, :boolean, default: false)
+  end
+
+  def changeset(schema, params) do
+    schema
+    |> cast(prepare_attrs(params), [
+      :followers,
+      :follows,
+      :non_follows,
+      :non_followers,
+      :privacy_option
+    ])
+  end
+
+  defp prepare_attrs(params) do
+    Enum.reduce(params, %{}, fn
+      {k, v}, acc when is_binary(v) ->
+        Map.put(acc, k, String.downcase(v))
+
+      {k, v}, acc ->
+        Map.put(acc, k, v)
+    end)
+  end
+end
index 49735b5c2f858fd38e0cb8aa13baa2b7a7e9de1e..bbea31682c1f6533eb95f53011774df6f6c8f0cc 100644 (file)
@@ -61,14 +61,7 @@ defmodule Pleroma.Web.Endpoint do
   plug(Plug.RequestId)
   plug(Plug.Logger)
 
-  plug(
-    Plug.Parsers,
-    parsers: [:urlencoded, :multipart, :json],
-    pass: ["*/*"],
-    json_decoder: Jason,
-    length: Pleroma.Config.get([:instance, :upload_limit]),
-    body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
-  )
+  plug(Pleroma.Plugs.Parsers)
 
   plug(Plug.MethodOverride)
   plug(Plug.Head)
index a6a924d02f067191ddda7e157ffbb40209fd2f12..34ec1d8d967493140032966f90b80602cb334222 100644 (file)
@@ -22,8 +22,8 @@ defmodule Pleroma.Web.Push.Impl do
   @spec perform(Notification.t()) :: list(any) | :error
   def perform(
         %{
-          activity: %{data: %{"type" => activity_type}, id: activity_id} = activity,
-          user_id: user_id
+          activity: %{data: %{"type" => activity_type}} = activity,
+          user: %User{id: user_id}
         } = notif
       )
       when activity_type in @types do
@@ -39,18 +39,17 @@ defmodule Pleroma.Web.Push.Impl do
     for subscription <- fetch_subsriptions(user_id),
         get_in(subscription.data, ["alerts", type]) do
       %{
-        title: format_title(notif),
         access_token: subscription.token.token,
-        body: format_body(notif, actor, object),
         notification_id: notif.id,
         notification_type: type,
         icon: avatar_url,
         preferred_locale: "en",
         pleroma: %{
-          activity_id: activity_id,
+          activity_id: notif.activity.id,
           direct_conversation_id: direct_conversation_id
         }
       }
+      |> Map.merge(build_content(notif, actor, object))
       |> Jason.encode!()
       |> push_message(build_sub(subscription), gcm_api_key, subscription)
     end
@@ -100,6 +99,24 @@ defmodule Pleroma.Web.Push.Impl do
     }
   end
 
+  def build_content(
+        %{
+          activity: %{data: %{"directMessage" => true}},
+          user: %{notification_settings: %{privacy_option: true}}
+        },
+        actor,
+        _
+      ) do
+    %{title: "New Direct Message", body: "@#{actor.nickname}"}
+  end
+
+  def build_content(notif, actor, object) do
+    %{
+      title: format_title(notif),
+      body: format_body(notif, actor, object)
+    }
+  end
+
   def format_body(
         %{activity: %{data: %{"type" => "Create"}}},
         actor,
index 61b451e3ed99aecd8fb7bdee08c084b0138bf74f..a978c4013d03837dca7fc24cec2166e1f6ff0ec7 100644 (file)
@@ -13,7 +13,7 @@ defmodule Pleroma.Workers.WebPusherWorker do
     notification =
       Notification
       |> Repo.get(notification_id)
-      |> Repo.preload([:activity])
+      |> Repo.preload([:activity, :user])
 
     Pleroma.Web.Push.Impl.perform(notification)
   end
diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex
new file mode 100644 (file)
index 0000000..ea0480d
--- /dev/null
@@ -0,0 +1,93 @@
+defmodule Pleroma.HTML.Scrubber.Default do
+  @doc "The default HTML scrubbing policy: no "
+
+  require FastSanitize.Sanitizer.Meta
+  alias FastSanitize.Sanitizer.Meta
+
+  # credo:disable-for-previous-line
+  # No idea how to fix this one…
+
+  @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
+
+  Meta.strip_comments()
+
+  Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes)
+
+  Meta.allow_tag_with_this_attribute_values(:a, "class", [
+    "hashtag",
+    "u-url",
+    "mention",
+    "u-url mention",
+    "mention u-url"
+  ])
+
+  Meta.allow_tag_with_this_attribute_values(:a, "rel", [
+    "tag",
+    "nofollow",
+    "noopener",
+    "noreferrer",
+    "ugc"
+  ])
+
+  Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
+
+  Meta.allow_tag_with_these_attributes(:abbr, ["title"])
+
+  Meta.allow_tag_with_these_attributes(:b, [])
+  Meta.allow_tag_with_these_attributes(:blockquote, [])
+  Meta.allow_tag_with_these_attributes(:br, [])
+  Meta.allow_tag_with_these_attributes(:code, [])
+  Meta.allow_tag_with_these_attributes(:del, [])
+  Meta.allow_tag_with_these_attributes(:em, [])
+  Meta.allow_tag_with_these_attributes(:i, [])
+  Meta.allow_tag_with_these_attributes(:li, [])
+  Meta.allow_tag_with_these_attributes(:ol, [])
+  Meta.allow_tag_with_these_attributes(:p, [])
+  Meta.allow_tag_with_these_attributes(:pre, [])
+  Meta.allow_tag_with_these_attributes(:strong, [])
+  Meta.allow_tag_with_these_attributes(:sub, [])
+  Meta.allow_tag_with_these_attributes(:sup, [])
+  Meta.allow_tag_with_these_attributes(:u, [])
+  Meta.allow_tag_with_these_attributes(:ul, [])
+
+  Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
+  Meta.allow_tag_with_these_attributes(:span, [])
+
+  @allow_inline_images Pleroma.Config.get([:markup, :allow_inline_images])
+
+  if @allow_inline_images do
+    # restrict img tags to http/https only, because of MediaProxy.
+    Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
+
+    Meta.allow_tag_with_these_attributes(:img, [
+      "width",
+      "height",
+      "class",
+      "title",
+      "alt"
+    ])
+  end
+
+  if Pleroma.Config.get([:markup, :allow_tables]) do
+    Meta.allow_tag_with_these_attributes(:table, [])
+    Meta.allow_tag_with_these_attributes(:tbody, [])
+    Meta.allow_tag_with_these_attributes(:td, [])
+    Meta.allow_tag_with_these_attributes(:th, [])
+    Meta.allow_tag_with_these_attributes(:thead, [])
+    Meta.allow_tag_with_these_attributes(:tr, [])
+  end
+
+  if Pleroma.Config.get([:markup, :allow_headings]) do
+    Meta.allow_tag_with_these_attributes(:h1, [])
+    Meta.allow_tag_with_these_attributes(:h2, [])
+    Meta.allow_tag_with_these_attributes(:h3, [])
+    Meta.allow_tag_with_these_attributes(:h4, [])
+    Meta.allow_tag_with_these_attributes(:h5, [])
+  end
+
+  if Pleroma.Config.get([:markup, :allow_fonts]) do
+    Meta.allow_tag_with_these_attributes(:font, ["face"])
+  end
+
+  Meta.strip_everything_not_covered()
+end
diff --git a/priv/scrubbers/links_only.ex b/priv/scrubbers/links_only.ex
new file mode 100644 (file)
index 0000000..b30a005
--- /dev/null
@@ -0,0 +1,27 @@
+defmodule Pleroma.HTML.Scrubber.LinksOnly do
+  @moduledoc """
+  An HTML scrubbing policy which limits to links only.
+  """
+
+  @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
+
+  require FastSanitize.Sanitizer.Meta
+  alias FastSanitize.Sanitizer.Meta
+
+  Meta.strip_comments()
+
+  # links
+  Meta.allow_tag_with_uri_attributes(:a, ["href"], @valid_schemes)
+
+  Meta.allow_tag_with_this_attribute_values(:a, "rel", [
+    "tag",
+    "nofollow",
+    "noopener",
+    "noreferrer",
+    "me",
+    "ugc"
+  ])
+
+  Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
+  Meta.strip_everything_not_covered()
+end
diff --git a/priv/scrubbers/media_proxy.ex b/priv/scrubbers/media_proxy.ex
new file mode 100644 (file)
index 0000000..5dbe576
--- /dev/null
@@ -0,0 +1,32 @@
+defmodule Pleroma.HTML.Transform.MediaProxy do
+  @moduledoc "Transforms inline image URIs to use MediaProxy."
+
+  alias Pleroma.Web.MediaProxy
+
+  def before_scrub(html), do: html
+
+  def scrub_attribute(:img, {"src", "http" <> target}) do
+    media_url =
+      ("http" <> target)
+      |> MediaProxy.url()
+
+    {"src", media_url}
+  end
+
+  def scrub_attribute(_tag, attribute), do: attribute
+
+  def scrub({:img, attributes, children}) do
+    attributes =
+      attributes
+      |> Enum.map(fn attr -> scrub_attribute(:img, attr) end)
+      |> Enum.reject(&is_nil(&1))
+
+    {:img, attributes, children}
+  end
+
+  def scrub({:comment, _text, _children}), do: ""
+
+  def scrub({tag, attributes, children}), do: {tag, attributes, children}
+  def scrub({_tag, children}), do: children
+  def scrub(text), do: text
+end
diff --git a/priv/scrubbers/twitter_text.ex b/priv/scrubbers/twitter_text.ex
new file mode 100644 (file)
index 0000000..c4e796c
--- /dev/null
@@ -0,0 +1,57 @@
+defmodule Pleroma.HTML.Scrubber.TwitterText do
+  @moduledoc """
+  An HTML scrubbing policy which limits to twitter-style text.  Only
+  paragraphs, breaks and links are allowed through the filter.
+  """
+
+  @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
+
+  require FastSanitize.Sanitizer.Meta
+  alias FastSanitize.Sanitizer.Meta
+
+  Meta.strip_comments()
+
+  # links
+  Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes)
+
+  Meta.allow_tag_with_this_attribute_values(:a, "class", [
+    "hashtag",
+    "u-url",
+    "mention",
+    "u-url mention",
+    "mention u-url"
+  ])
+
+  Meta.allow_tag_with_this_attribute_values(:a, "rel", [
+    "tag",
+    "nofollow",
+    "noopener",
+    "noreferrer"
+  ])
+
+  Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
+
+  # paragraphs and linebreaks
+  Meta.allow_tag_with_these_attributes(:br, [])
+  Meta.allow_tag_with_these_attributes(:p, [])
+
+  # microformats
+  Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
+  Meta.allow_tag_with_these_attributes(:span, [])
+
+  # allow inline images for custom emoji
+  if Pleroma.Config.get([:markup, :allow_inline_images]) do
+    # restrict img tags to http/https only, because of MediaProxy.
+    Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
+
+    Meta.allow_tag_with_these_attributes(:img, [
+      "width",
+      "height",
+      "class",
+      "title",
+      "alt"
+    ])
+  end
+
+  Meta.strip_everything_not_covered()
+end
index 827ac4f06fe4ca9ff6cf9576456e94f911f8c53a..ffa3d4b8c52604f0c638928de65d49b9b1de0c24 100644 (file)
@@ -136,7 +136,10 @@ defmodule Pleroma.NotificationTest do
 
     test "it disables notifications from followers" do
       follower = insert(:user)
-      followed = insert(:user, notification_settings: %{"followers" => false})
+
+      followed =
+        insert(:user, notification_settings: %Pleroma.User.NotificationSetting{followers: false})
+
       User.follow(follower, followed)
       {:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"})
       refute Notification.create_notification(activity, followed)
@@ -144,13 +147,20 @@ defmodule Pleroma.NotificationTest do
 
     test "it disables notifications from non-followers" do
       follower = insert(:user)
-      followed = insert(:user, notification_settings: %{"non_followers" => false})
+
+      followed =
+        insert(:user,
+          notification_settings: %Pleroma.User.NotificationSetting{non_followers: false}
+        )
+
       {:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"})
       refute Notification.create_notification(activity, followed)
     end
 
     test "it disables notifications from people the user follows" do
-      follower = insert(:user, notification_settings: %{"follows" => false})
+      follower =
+        insert(:user, notification_settings: %Pleroma.User.NotificationSetting{follows: false})
+
       followed = insert(:user)
       User.follow(follower, followed)
       follower = Repo.get(User, follower.id)
@@ -159,7 +169,9 @@ defmodule Pleroma.NotificationTest do
     end
 
     test "it disables notifications from people the user does not follow" do
-      follower = insert(:user, notification_settings: %{"non_follows" => false})
+      follower =
+        insert(:user, notification_settings: %Pleroma.User.NotificationSetting{non_follows: false})
+
       followed = insert(:user)
       {:ok, activity} = CommonAPI.post(followed, %{"status" => "hey @#{follower.nickname}"})
       refute Notification.create_notification(activity, follower)
index 6da16f71a90df664f9467633cfef851502f4c132..fcfea666f1a2f517f2b8a698e632cc0f1c1a9728 100644 (file)
@@ -10,7 +10,8 @@ defmodule Pleroma.Builders.UserBuilder do
       password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
       bio: "A tester.",
       ap_id: "some id",
-      last_digest_emailed_at: NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
+      last_digest_emailed_at: NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
+      notification_settings: %Pleroma.User.NotificationSetting{}
     }
 
     Map.merge(user, data)
index 35ba523a1c48c795c25a5b1a475ca460b66d7ed0..314f26ec99d1de43b7d7f07311b50ab39753a867 100644 (file)
@@ -31,7 +31,8 @@ defmodule Pleroma.Factory do
       nickname: sequence(:nickname, &"nick#{&1}"),
       password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
       bio: sequence(:bio, &"Tester Number #{&1}"),
-      last_digest_emailed_at: NaiveDateTime.utc_now()
+      last_digest_emailed_at: NaiveDateTime.utc_now(),
+      notification_settings: %Pleroma.User.NotificationSetting{}
     }
 
     %{
diff --git a/test/user/notification_setting_test.exs b/test/user/notification_setting_test.exs
new file mode 100644 (file)
index 0000000..4744d7b
--- /dev/null
@@ -0,0 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.User.NotificationSettingTest do
+  use Pleroma.DataCase
+
+  alias Pleroma.User.NotificationSetting
+
+  describe "changeset/2" do
+    test "sets valid privacy option" do
+      changeset =
+        NotificationSetting.changeset(
+          %NotificationSetting{},
+          %{"privacy_option" => true}
+        )
+
+      assert %Ecto.Changeset{valid?: true} = changeset
+    end
+  end
+end
index 98841dbbda2c231f586a3b17896ca08815f2b737..82185847672a6a2e5affa1be18339721fd78f0f6 100644 (file)
@@ -174,6 +174,7 @@ defmodule Pleroma.UserSearchTest do
         |> Map.put(:search_rank, nil)
         |> Map.put(:search_type, nil)
         |> Map.put(:last_digest_emailed_at, nil)
+        |> Map.put(:notification_settings, nil)
 
       assert user == expected
     end
index f6d4ab9f0dba0139dfa7a0543e345f246cd327ef..6635ea7a2a027f562b60be06aa66ec1d6def10cf 100644 (file)
@@ -137,55 +137,151 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
     assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
   end
 
-  test "filters notifications using exclude_visibilities", %{conn: conn} do
-    user = insert(:user)
-    other_user = insert(:user)
-
-    {:ok, public_activity} =
-      CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "public"})
-
-    {:ok, direct_activity} =
-      CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "direct"})
-
-    {:ok, unlisted_activity} =
-      CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "unlisted"})
-
-    {:ok, private_activity} =
-      CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "private"})
-
-    conn = assign(conn, :user, user)
-
-    conn_res =
-      get(conn, "/api/v1/notifications", %{
-        exclude_visibilities: ["public", "unlisted", "private"]
-      })
-
-    assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
-    assert id == direct_activity.id
-
-    conn_res =
-      get(conn, "/api/v1/notifications", %{
-        exclude_visibilities: ["public", "unlisted", "direct"]
-      })
-
-    assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
-    assert id == private_activity.id
-
-    conn_res =
-      get(conn, "/api/v1/notifications", %{
-        exclude_visibilities: ["public", "private", "direct"]
-      })
-
-    assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
-    assert id == unlisted_activity.id
-
-    conn_res =
-      get(conn, "/api/v1/notifications", %{
-        exclude_visibilities: ["unlisted", "private", "direct"]
-      })
-
-    assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
-    assert id == public_activity.id
+  describe "exclude_visibilities" do
+    test "filters notifications for mentions", %{conn: conn} do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {:ok, public_activity} =
+        CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "public"})
+
+      {:ok, direct_activity} =
+        CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "direct"})
+
+      {:ok, unlisted_activity} =
+        CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "unlisted"})
+
+      {:ok, private_activity} =
+        CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "private"})
+
+      conn = assign(conn, :user, user)
+
+      conn_res =
+        get(conn, "/api/v1/notifications", %{
+          exclude_visibilities: ["public", "unlisted", "private"]
+        })
+
+      assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
+      assert id == direct_activity.id
+
+      conn_res =
+        get(conn, "/api/v1/notifications", %{
+          exclude_visibilities: ["public", "unlisted", "direct"]
+        })
+
+      assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
+      assert id == private_activity.id
+
+      conn_res =
+        get(conn, "/api/v1/notifications", %{
+          exclude_visibilities: ["public", "private", "direct"]
+        })
+
+      assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
+      assert id == unlisted_activity.id
+
+      conn_res =
+        get(conn, "/api/v1/notifications", %{
+          exclude_visibilities: ["unlisted", "private", "direct"]
+        })
+
+      assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
+      assert id == public_activity.id
+    end
+
+    test "filters notifications for Like activities", %{conn: conn} do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {:ok, public_activity} =
+        CommonAPI.post(other_user, %{"status" => ".", "visibility" => "public"})
+
+      {:ok, direct_activity} =
+        CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "direct"})
+
+      {:ok, unlisted_activity} =
+        CommonAPI.post(other_user, %{"status" => ".", "visibility" => "unlisted"})
+
+      {:ok, private_activity} =
+        CommonAPI.post(other_user, %{"status" => ".", "visibility" => "private"})
+
+      {:ok, _, _} = CommonAPI.favorite(public_activity.id, user)
+      {:ok, _, _} = CommonAPI.favorite(direct_activity.id, user)
+      {:ok, _, _} = CommonAPI.favorite(unlisted_activity.id, user)
+      {:ok, _, _} = CommonAPI.favorite(private_activity.id, user)
+
+      activity_ids =
+        conn
+        |> assign(:user, other_user)
+        |> get("/api/v1/notifications", %{exclude_visibilities: ["direct"]})
+        |> json_response(200)
+        |> Enum.map(& &1["status"]["id"])
+
+      assert public_activity.id in activity_ids
+      assert unlisted_activity.id in activity_ids
+      assert private_activity.id in activity_ids
+      refute direct_activity.id in activity_ids
+
+      activity_ids =
+        conn
+        |> assign(:user, other_user)
+        |> get("/api/v1/notifications", %{exclude_visibilities: ["unlisted"]})
+        |> json_response(200)
+        |> Enum.map(& &1["status"]["id"])
+
+      assert public_activity.id in activity_ids
+      refute unlisted_activity.id in activity_ids
+      assert private_activity.id in activity_ids
+      assert direct_activity.id in activity_ids
+
+      activity_ids =
+        conn
+        |> assign(:user, other_user)
+        |> get("/api/v1/notifications", %{exclude_visibilities: ["private"]})
+        |> json_response(200)
+        |> Enum.map(& &1["status"]["id"])
+
+      assert public_activity.id in activity_ids
+      assert unlisted_activity.id in activity_ids
+      refute private_activity.id in activity_ids
+      assert direct_activity.id in activity_ids
+
+      activity_ids =
+        conn
+        |> assign(:user, other_user)
+        |> get("/api/v1/notifications", %{exclude_visibilities: ["public"]})
+        |> json_response(200)
+        |> Enum.map(& &1["status"]["id"])
+
+      refute public_activity.id in activity_ids
+      assert unlisted_activity.id in activity_ids
+      assert private_activity.id in activity_ids
+      assert direct_activity.id in activity_ids
+    end
+
+    test "filters notifications for Announce activities", %{conn: conn} do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {:ok, public_activity} =
+        CommonAPI.post(other_user, %{"status" => ".", "visibility" => "public"})
+
+      {:ok, unlisted_activity} =
+        CommonAPI.post(other_user, %{"status" => ".", "visibility" => "unlisted"})
+
+      {:ok, _, _} = CommonAPI.repeat(public_activity.id, user)
+      {:ok, _, _} = CommonAPI.repeat(unlisted_activity.id, user)
+
+      activity_ids =
+        conn
+        |> assign(:user, other_user)
+        |> get("/api/v1/notifications", %{exclude_visibilities: ["unlisted"]})
+        |> json_response(200)
+        |> Enum.map(& &1["status"]["id"])
+
+      assert public_activity.id in activity_ids
+      refute unlisted_activity.id in activity_ids
+    end
   end
 
   test "filters notifications using exclude_types", %{conn: conn} do
index ed6f2ecbd7577f70de4c67c074c3d636c43531f1..5e297d1298015fe8a2637512c921d95ba6ede6f6 100644 (file)
@@ -92,13 +92,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
   test "Represent the user account for the account owner" do
     user = insert(:user)
 
-    notification_settings = %{
-      "followers" => true,
-      "follows" => true,
-      "non_follows" => true,
-      "non_followers" => true
-    }
-
+    notification_settings = %Pleroma.User.NotificationSetting{}
     privacy = user.default_scope
 
     assert %{
index 9b554601d9c146079ccb1809a49ba37aef522bbd..acae7a734deca67cb8d147f0c58e8d26feebb349 100644 (file)
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.Push.ImplTest do
   use Pleroma.DataCase
 
   alias Pleroma.Object
+  alias Pleroma.User
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.Push.Impl
   alias Pleroma.Web.Push.Subscription
@@ -182,4 +183,50 @@ defmodule Pleroma.Web.Push.ImplTest do
     assert Impl.format_title(%{activity: activity}) ==
              "New Direct Message"
   end
+
+  describe "build_content/3" do
+    test "returns info content for direct message with enabled privacy option" do
+      user = insert(:user, nickname: "Bob")
+      user2 = insert(:user, nickname: "Rob", notification_settings: %{privacy_option: true})
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "visibility" => "direct",
+          "status" => "<Lorem ipsum dolor sit amet."
+        })
+
+      notif = insert(:notification, user: user2, activity: activity)
+
+      actor = User.get_cached_by_ap_id(notif.activity.data["actor"])
+      object = Object.normalize(activity)
+
+      assert Impl.build_content(notif, actor, object) == %{
+               body: "@Bob",
+               title: "New Direct Message"
+             }
+    end
+
+    test "returns regular content for direct message with disabled privacy option" do
+      user = insert(:user, nickname: "Bob")
+      user2 = insert(:user, nickname: "Rob", notification_settings: %{privacy_option: false})
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "visibility" => "direct",
+          "status" =>
+            "<span>Lorem ipsum dolor sit amet</span>, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis."
+        })
+
+      notif = insert(:notification, user: user2, activity: activity)
+
+      actor = User.get_cached_by_ap_id(notif.activity.data["actor"])
+      object = Object.normalize(activity)
+
+      assert Impl.build_content(notif, actor, object) == %{
+               body:
+                 "@Bob: Lorem ipsum dolor sit amet, consectetur  adipiscing elit. Fusce sagittis fini...",
+               title: "New Direct Message"
+             }
+    end
+  end
 end
index 986ee01f35ecb850f9493045942b4e28873d8e68..734cd221199554898ef40bbec79c0b658a4b139a 100644 (file)
@@ -159,11 +159,31 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
 
       user = Repo.get(User, user.id)
 
-      assert %{
-               "followers" => false,
-               "follows" => true,
-               "non_follows" => true,
-               "non_followers" => true
+      assert %Pleroma.User.NotificationSetting{
+               followers: false,
+               follows: true,
+               non_follows: true,
+               non_followers: true,
+               privacy_option: false
+             } == user.notification_settings
+    end
+
+    test "it update notificatin privacy option", %{conn: conn} do
+      user = insert(:user)
+
+      conn
+      |> assign(:user, user)
+      |> put("/api/pleroma/notification_settings", %{"privacy_option" => "1"})
+      |> json_response(:ok)
+
+      user = refresh_record(user)
+
+      assert %Pleroma.User.NotificationSetting{
+               followers: true,
+               follows: true,
+               non_follows: true,
+               non_followers: true,
+               privacy_option: true
              } == user.notification_settings
     end
   end