Merge remote-tracking branch 'pleroma/develop' into feature/addressable-lists
authorEgor Kislitsyn <egor@kislitsyn.com>
Fri, 17 May 2019 12:57:14 +0000 (19:57 +0700)
committerEgor Kislitsyn <egor@kislitsyn.com>
Fri, 17 May 2019 12:57:14 +0000 (19:57 +0700)
55 files changed:
CHANGELOG.md
config/config.exs
config/test.exs
docs/api/admin_api.md
docs/config.md
docs/introduction.md
lib/mix/tasks/pleroma/database.ex
lib/mix/tasks/pleroma/user.ex
lib/pleroma/activity.ex
lib/pleroma/bbs/handler.ex
lib/pleroma/conversation.ex
lib/pleroma/conversation/participation.ex
lib/pleroma/emails/admin_email.ex
lib/pleroma/filter.ex
lib/pleroma/plugs/http_security_plug.ex
lib/pleroma/user.ex
lib/pleroma/user/info.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/mrf/simple_policy.ex
lib/pleroma/web/activity_pub/mrf/tag_policy.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/activity_pub/utils.ex
lib/pleroma/web/activity_pub/visibility.ex
lib/pleroma/web/admin_api/admin_api_controller.ex
lib/pleroma/web/admin_api/views/report_view.ex [new file with mode: 0644]
lib/pleroma/web/common_api/common_api.ex
lib/pleroma/web/common_api/utils.ex
lib/pleroma/web/federator/publisher.ex
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
lib/pleroma/web/mastodon_api/views/account_view.ex
lib/pleroma/web/oauth/authorization.ex
lib/pleroma/web/oauth/token.ex
lib/pleroma/web/router.ex
lib/pleroma/web/twitter_api/twitter_api_controller.ex
lib/pleroma/web/web_finger/web_finger.ex
mix.exs
mix.lock
priv/repo/migrations/20190511191044_set_default_state_to_reports.exs [new file with mode: 0644]
priv/repo/migrations/20190515222404_add_thread_visibility_function.exs [new file with mode: 0644]
test/conversation_test.exs
test/formatter_test.exs
test/plugs/http_security_plug_test.exs
test/plugs/legacy_authentication_plug_test.exs
test/repo_test.exs
test/support/factory.ex
test/tasks/database_test.exs [new file with mode: 0644]
test/tasks/user_test.exs
test/user_test.exs
test/web/activity_pub/activity_pub_test.exs
test/web/activity_pub/mrf/simple_policy_test.exs [new file with mode: 0644]
test/web/activity_pub/visibilty_test.exs
test/web/admin_api/admin_api_controller_test.exs
test/web/common_api/common_api_test.exs
test/web/mastodon_api/mastodon_api_controller_test.exs
test/web/ostatus/ostatus_test.exs

index 87144b7f1258cf3c06e02c539ceb2a72167e6f2a..c9f8ee5ab35997bc484a639cb4decbb1809cc90c 100644 (file)
@@ -10,18 +10,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - A [job queue](https://git.pleroma.social/pleroma/pleroma_job_queue) for federation, emails, web push, etc.
 - [Prometheus](https://prometheus.io/) metrics
 - Support for Mastodon's remote interaction
+- Mix Tasks: `mix pleroma.database bump_all_conversations`
 - Mix Tasks: `mix pleroma.database remove_embedded_objects`
+- Mix Tasks: `mix pleroma.database update_users_following_followers_counts`
+- Mix Tasks: `mix pleroma.user toggle_confirmed`
 - Federation: Support for reports
 - Configuration: `safe_dm_mentions` option
 - Configuration: `link_name` option
 - Configuration: `fetch_initial_posts` option
 - Configuration: `notify_email` option
 - Configuration: Media proxy `whitelist` option
+- Configuration: `report_uri` option
 - Pleroma API: User subscriptions
 - Pleroma API: Healthcheck endpoint
 - Admin API: Endpoints for listing/revoking invite tokens
 - Admin API: Endpoints for making users follow/unfollow each other
 - Admin API: added filters (role, tags, email, name) for users endpoint
+- Admin API: Endpoints for managing reports
+- Admin API: Endpoints for deleting and changing the scope of individual reported statuses
 - AdminFE: initial release with basic user management accessible at /pleroma/admin/
 - Mastodon API: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/)
 - Mastodon API: `/api/v1/notifications/destroy_multiple` (glitch-soc extension)
@@ -99,7 +105,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mastodon API: Make `irreversible` field default to `false` [`POST /api/v1/filters`]
 
 ## Removed
-- Configuration: `config :pleroma, :fe` in favor of the more flexible `config :pleroma, :frontend_configurations` 
+- Configuration: `config :pleroma, :fe` in favor of the more flexible `config :pleroma, :frontend_configurations`
 
 ## [0.9.9999] - 2019-04-05
 ### Security
index 9cbac35ebdbeb3767a18b2310bbdde235d9d425a..61e2648a983e70da686da47bb0e24ba2cade90bb 100644 (file)
@@ -48,7 +48,8 @@ config :pleroma, ecto_repos: [Pleroma.Repo]
 
 config :pleroma, Pleroma.Repo,
   types: Pleroma.PostgresTypes,
-  telemetry_event: [Pleroma.Repo.Instrumenter]
+  telemetry_event: [Pleroma.Repo.Instrumenter],
+  migration_lock: nil
 
 config :pleroma, Pleroma.Captcha,
   enabled: false,
index a0c90c3717c5fbcfcd0a8fba11699a8f98a9b54a..40db66170f2af4ed98f5f92d051fb043bf13f92e 100644 (file)
@@ -61,6 +61,8 @@ config :pleroma, Pleroma.ScheduledActivity,
 
 config :pleroma, :app_account_creation, max_requests: 5
 
+config :pleroma, :http_security, report_uri: "https://endpoint.com"
+
 try do
   import_config "test.secret.exs"
 rescue
index 59578f8d1fb4d65181277b2154dc01a0fbe47b9f..b45c5e2856778c1fef1f3ec3e84162eba51761fa 100644 (file)
@@ -24,7 +24,7 @@ Authentication is required and the user must be an admin.
 - Example: `https://mypleroma.org/api/pleroma/admin/users?query=john&filters=local,active&page=1&page_size=10&tags[]=some_tag&tags[]=another_tag&name=display_name&email=email@example.com`
 - Response:
 
-```JSON
+```json
 {
   "page_size": integer,
   "count": integer,
@@ -92,7 +92,7 @@ Authentication is required and the user must be an admin.
   - `nickname`
 - Response: User’s object
 
-```JSON
+```json
 {
   "deactivated": bool,
   "id": integer,
@@ -124,7 +124,7 @@ Authentication is required and the user must be an admin.
 - Params: none
 - Response:
 
-```JSON
+```json
 {
   "is_moderator": bool,
   "is_admin": bool
@@ -141,7 +141,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
 - Params: none
 - Response:
 
-```JSON
+```json
 {
   "is_moderator": bool,
   "is_admin": bool
@@ -223,7 +223,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
 - Params: none
 - Response:
 
-```JSON
+```json
 {
 
   "invites": [
@@ -250,7 +250,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
   - `token`
 - Response:
 
-```JSON
+```json
 {
   "id": integer,
   "token": string,
@@ -280,3 +280,280 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
 - Methods: `GET`
 - Params: none
 - Response: password reset token (base64 string)
+
+## `/api/pleroma/admin/reports`
+### Get a list of reports
+- Method `GET`
+- Params:
+  - `state`: optional, the state of reports. Valid values are `open`, `closed` and `resolved`
+  - `limit`: optional, the number of records to retrieve
+  - `since_id`: optional, returns results that are more recent than the specified id
+  - `max_id`: optional, returns results that are older than the specified id
+- Response: 
+  - On failure: 403 Forbidden error `{"error": "error_msg"}` when requested by anonymous or non-admin
+  - On success: JSON, returns a list of reports, where:
+    - `account`: the user who has been reported
+    - `actor`: the user who has sent the report
+    - `statuses`: list of statuses that have been included to the report
+
+```json
+{
+  "reports": [
+    {
+      "account": {
+        "acct": "user",
+        "avatar": "https://pleroma.example.org/images/avi.png",
+        "avatar_static": "https://pleroma.example.org/images/avi.png",
+        "bot": false,
+        "created_at": "2019-04-23T17:32:04.000Z",
+        "display_name": "User",
+        "emojis": [],
+        "fields": [],
+        "followers_count": 1,
+        "following_count": 1,
+        "header": "https://pleroma.example.org/images/banner.png",
+        "header_static": "https://pleroma.example.org/images/banner.png",
+        "id": "9i6dAJqSGSKMzLG2Lo",
+        "locked": false,
+        "note": "",
+        "pleroma": {
+          "confirmation_pending": false,
+          "hide_favorites": true,
+          "hide_followers": false,
+          "hide_follows": false,
+          "is_admin": false,
+          "is_moderator": false,
+          "relationship": {},
+          "tags": []
+        },
+        "source": {
+          "note": "",
+          "pleroma": {},
+          "sensitive": false
+        },
+        "statuses_count": 3,
+        "url": "https://pleroma.example.org/users/user",
+        "username": "user"
+      },
+      "actor": {
+        "acct": "lain",
+        "avatar": "https://pleroma.example.org/images/avi.png",
+        "avatar_static": "https://pleroma.example.org/images/avi.png",
+        "bot": false,
+        "created_at": "2019-03-28T17:36:03.000Z",
+        "display_name": "Roger Braun",
+        "emojis": [],
+        "fields": [],
+        "followers_count": 1,
+        "following_count": 1,
+        "header": "https://pleroma.example.org/images/banner.png",
+        "header_static": "https://pleroma.example.org/images/banner.png",
+        "id": "9hEkA5JsvAdlSrocam",
+        "locked": false,
+        "note": "",
+        "pleroma": {
+          "confirmation_pending": false,
+          "hide_favorites": false,
+          "hide_followers": false,
+          "hide_follows": false,
+          "is_admin": false,
+          "is_moderator": false,
+          "relationship": {},
+          "tags": []
+        },
+        "source": {
+          "note": "",
+          "pleroma": {},
+          "sensitive": false
+        },
+        "statuses_count": 1,
+        "url": "https://pleroma.example.org/users/lain",
+        "username": "lain"
+      },
+      "content": "Please delete it",
+      "created_at": "2019-04-29T19:48:15.000Z",
+      "id": "9iJGOv1j8hxuw19bcm",
+      "state": "open",
+      "statuses": [
+        {
+          "account": { ... },
+          "application": {
+            "name": "Web",
+            "website": null
+          },
+          "bookmarked": false,
+          "card": null,
+          "content": "<span class=\"h-card\"><a data-user=\"9hEkA5JsvAdlSrocam\" class=\"u-url mention\" href=\"https://pleroma.example.org/users/lain\">@<span>lain</span></a></span> click on my link <a href=\"https://www.google.com/\">https://www.google.com/</a>",
+          "created_at": "2019-04-23T19:15:47.000Z",
+          "emojis": [],
+          "favourited": false,
+          "favourites_count": 0,
+          "id": "9i6mQ9uVrrOmOime8m",
+          "in_reply_to_account_id": null,
+          "in_reply_to_id": null,
+          "language": null,
+          "media_attachments": [],
+          "mentions": [
+            {
+              "acct": "lain",
+              "id": "9hEkA5JsvAdlSrocam",
+              "url": "https://pleroma.example.org/users/lain",
+              "username": "lain"
+            },
+            {
+              "acct": "user",
+              "id": "9i6dAJqSGSKMzLG2Lo",
+              "url": "https://pleroma.example.org/users/user",
+              "username": "user"
+            }
+          ],
+          "muted": false,
+          "pinned": false,
+          "pleroma": {
+            "content": {
+              "text/plain": "@lain click on my link https://www.google.com/"
+            },
+            "conversation_id": 28,
+            "in_reply_to_account_acct": null,
+            "local": true,
+            "spoiler_text": {
+              "text/plain": ""
+            }
+          },
+          "reblog": null,
+          "reblogged": false,
+          "reblogs_count": 0,
+          "replies_count": 0,
+          "sensitive": false,
+          "spoiler_text": "",
+          "tags": [],
+          "uri": "https://pleroma.example.org/objects/8717b90f-8e09-4b58-97b0-e3305472b396",
+          "url": "https://pleroma.example.org/notice/9i6mQ9uVrrOmOime8m",
+          "visibility": "direct"
+        }
+      ]
+    }
+  ]
+}
+```
+
+## `/api/pleroma/admin/reports/:id`
+### Get an individual report
+- Method `GET`
+- Params:
+  - `id`
+- Response:
+  - On failure: 
+    - 403 Forbidden `{"error": "error_msg"}`
+    - 404 Not Found `"Not found"`
+  - On success: JSON, Report object (see above)
+
+## `/api/pleroma/admin/reports/:id`
+### Change the state of the report
+- Method `PUT`
+- Params:
+  - `id`
+  - `state`: required, the new state. Valid values are `open`, `closed` and `resolved`
+- Response: 
+  - On failure: 
+    - 400 Bad Request `"Unsupported state"`
+    - 403 Forbidden `{"error": "error_msg"}`
+    - 404 Not Found `"Not found"`
+  - On success: JSON, Report object (see above)
+
+## `/api/pleroma/admin/reports/:id/respond`
+### Respond to a report
+- Method `POST`
+- Params:
+  - `id`
+  - `status`: required, the message
+- Response: 
+  - On failure: 
+    - 400 Bad Request `"Invalid parameters"` when `status` is missing 
+    - 403 Forbidden `{"error": "error_msg"}` 
+    - 404 Not Found `"Not found"`
+  - On success: JSON, created Mastodon Status entity
+
+```json
+{
+  "account": { ... },
+  "application": {
+    "name": "Web",
+    "website": null
+  },
+  "bookmarked": false,
+  "card": null,
+  "content": "Your claim is going to be closed",
+  "created_at": "2019-05-11T17:13:03.000Z",
+  "emojis": [],
+  "favourited": false,
+  "favourites_count": 0,
+  "id": "9ihuiSL1405I65TmEq",
+  "in_reply_to_account_id": null,
+  "in_reply_to_id": null,
+  "language": null,
+  "media_attachments": [],
+  "mentions": [
+    {
+      "acct": "user",
+      "id": "9i6dAJqSGSKMzLG2Lo",
+      "url": "https://pleroma.example.org/users/user",
+      "username": "user"
+    },
+    {
+      "acct": "admin",
+      "id": "9hEkA5JsvAdlSrocam",
+      "url": "https://pleroma.example.org/users/admin",
+      "username": "admin"
+    }
+  ],
+  "muted": false,
+  "pinned": false,
+  "pleroma": {
+    "content": {
+      "text/plain": "Your claim is going to be closed"
+    },
+    "conversation_id": 35,
+    "in_reply_to_account_acct": null,
+    "local": true,
+    "spoiler_text": {
+      "text/plain": ""
+    }
+  },
+  "reblog": null,
+  "reblogged": false,
+  "reblogs_count": 0,
+  "replies_count": 0,
+  "sensitive": false,
+  "spoiler_text": "",
+  "tags": [],
+  "uri": "https://pleroma.example.org/objects/cab0836d-9814-46cd-a0ea-529da9db5fcb",
+  "url": "https://pleroma.example.org/notice/9ihuiSL1405I65TmEq",
+  "visibility": "direct"
+}
+```
+
+## `/api/pleroma/admin/statuses/:id`
+### Change the scope of an individual reported status
+- Method `PUT`
+- Params:
+  - `id`
+  - `sensitive`: optional, valid values are `true` or `false`
+  - `visibility`: optional, valid values are `public`, `private` and `unlisted`
+- Response: 
+  - On failure: 
+    - 400 Bad Request `"Unsupported visibility"`
+    - 403 Forbidden `{"error": "error_msg"}` 
+    - 404 Not Found `"Not found"`
+  - On success: JSON, Mastodon Status entity
+
+## `/api/pleroma/admin/statuses/:id`
+### Delete an individual reported status
+- Method `DELETE`
+- Params:
+  - `id`
+- Response: 
+  - On failure: 
+    - 403 Forbidden `{"error": "error_msg"}` 
+    - 404 Not Found `"Not found"`
+  - On success: 200 OK `{}`
index 470f71b7cf71c9c289aedec2d9573be878031966..c2af5c0125ca869245f6e0841c0ad4bfb63f01ec 100644 (file)
@@ -286,7 +286,8 @@ This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls start
 * ``sts``: Whether to additionally send a `Strict-Transport-Security` header
 * ``sts_max_age``: The maximum age for the `Strict-Transport-Security` header if sent
 * ``ct_max_age``: The maximum age for the `Expect-CT` header if sent
-* ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`.
+* ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`
+* ``report_uri``: Adds the specified url to `report-uri` and `report-to` group in CSP header.
 
 ## :mrf_user_allowlist
 
index 4af0747fea3d083a5de701379a6768db78f5e32c..045dc7c0501816a450af42388a9f3d165a7b1826 100644 (file)
@@ -1,30 +1,30 @@
 # Introduction to Pleroma
 ## What is Pleroma?
-Pleroma is a federated social networking platform, compatible with GNU social, Mastodon and other OStatus and ActivityPub implementations. It is free software licensed under the AGPLv3.  
-It actually consists of two components: a backend, named simply Pleroma, and a user-facing frontend, named Pleroma-FE. It also includes the Mastodon frontend, if that's your thing.  
-It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other.  
+Pleroma is a federated social networking platform, compatible with GNU social, Mastodon and other OStatus and ActivityPub implementations. It is free software licensed under the AGPLv3.
+It actually consists of two components: a backend, named simply Pleroma, and a user-facing frontend, named Pleroma-FE. It also includes the Mastodon frontend, if that's your thing.
+It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other.
 One account on a instance is enough to talk to the entire fediverse!
-  
+
 ## How can I use it?
 
-Pleroma instances are already widely deployed, a list can be found here:  
+Pleroma instances are already widely deployed, a list can be found here:
 http://distsn.org/pleroma-instances.html
 
-If you don't feel like joining an existing instance, but instead prefer to deploy your own instance, that's easy too!  
-Installation instructions can be found here:  
+If you don't feel like joining an existing instance, but instead prefer to deploy your own instance, that's easy too!
+Installation instructions can be found here:
 [main Pleroma wiki](/)
-  
+
 ## I got an account, now what?
-Great! Now you can explore the fediverse!  
-- Open the login page for your Pleroma instance (for ex. https://pleroma.soykaf.com) and login with your username and password.  
-(If you don't have one yet, click on Register) :slightly_smiling_face:  
+Great! Now you can explore the fediverse!
+- Open the login page for your Pleroma instance (for ex. https://pleroma.soykaf.com) and login with your username and password.
+(If you don't have one yet, click on Register) :slightly_smiling_face:
 
 At this point you will have two columns in front of you.
 
 ### Left column
-- first block: here you can see your avatar, your nickname a bio, and statistics (Statuses, Following, Followers).  
-Under that you have a text form which allows you to post new statuses. The icon on the left is for uploading media files and attach them to your post. The number under the text form is a character counter, every instance can have a different character limit (the default is 5000).  
-If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person. :slight_smile:   
+- first block: here you can see your avatar, your nickname a bio, and statistics (Statuses, Following, Followers).
+Under that you have a text form which allows you to post new statuses. The icon on the left is for uploading media files and attach them to your post. The number under the text form is a character counter, every instance can have a different character limit (the default is 5000).
+If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person. :slight_smile:
 To post your status, simply press Submit.
 
 - second block: Here you can switch between the different timelines:
@@ -38,7 +38,7 @@ To post your status, simply press Submit.
 - fourth block: This is the Notifications block, here you will get notified whenever somebody mentions you, follows you, repeats or favorites one of your statuses.
 
 ### Right column
-This is where the interesting stuff happens! :slight_smile:   
+This is where the interesting stuff happens! :slight_smile:
 Depending on the timeline you will see different statuses, but each status has a standard structure:
 - Icon + name + link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the replied-to status).
 - A + button on the right allows you to Expand/Collapse an entire discussion thread. It also updates in realtime!
@@ -47,9 +47,9 @@ Depending on the timeline you will see different statuses, but each status has a
 - Four buttons (left to right): Reply, Repeat, Favorite, Delete.
 
 ## Mastodon interface
-If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too! :smile:  
-Just add a "/web" after your instance url (for ex. https://pleroma.soycaf.com/web) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC! :fireworks:  
-For more information on the Mastodon interface, please look here:  
+If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too! :smile:
+Just add a "/web" after your instance url (for ex. https://pleroma.soycaf.com/web) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC! :fireworks:
+For more information on the Mastodon interface, please look here:
 https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md
 
 Remember, what you see is only the frontend part of Mastodon, the backend is still Pleroma.
index ab9a3a7ff95eb9d43ff357ae2c64c538a4de1abe..f650b447dde848fa381729fa6406ccc38e05ecde 100644 (file)
@@ -4,6 +4,9 @@
 
 defmodule Mix.Tasks.Pleroma.Database do
   alias Mix.Tasks.Pleroma.Common
+  alias Pleroma.Conversation
+  alias Pleroma.Repo
+  alias Pleroma.User
   require Logger
   use Mix.Task
 
@@ -19,6 +22,14 @@ defmodule Mix.Tasks.Pleroma.Database do
 
     Options:
     - `--vacuum` - run `VACUUM FULL` after the embedded objects are replaced with their references
+
+  ## Create a conversation for all existing DMs. Can be safely re-run.
+
+      mix pleroma.database bump_all_conversations
+
+  ## Remove duplicated items from following and update followers count for all users
+
+      mix pleroma.database update_users_following_followers_counts
   """
   def run(["remove_embedded_objects" | args]) do
     {options, [], []} =
@@ -32,7 +43,7 @@ defmodule Mix.Tasks.Pleroma.Database do
     Common.start_pleroma()
     Logger.info("Removing embedded objects")
 
-    Pleroma.Repo.query!(
+    Repo.query!(
       "update activities set data = jsonb_set(data, '{object}'::text[], data->'object'->'id') where data->'object'->>'id' is not null;",
       [],
       timeout: :infinity
@@ -41,11 +52,24 @@ defmodule Mix.Tasks.Pleroma.Database do
     if Keyword.get(options, :vacuum) do
       Logger.info("Runnning VACUUM FULL")
 
-      Pleroma.Repo.query!(
+      Repo.query!(
         "vacuum full;",
         [],
         timeout: :infinity
       )
     end
   end
+
+  def run(["bump_all_conversations"]) do
+    Common.start_pleroma()
+    Conversation.bump_for_all_activities()
+  end
+
+  def run(["update_users_following_followers_counts"]) do
+    Common.start_pleroma()
+
+    users = Repo.all(User)
+    Enum.each(users, &User.remove_duplicated_following/1)
+    Enum.each(users, &User.update_follower_count/1)
+  end
 end
index d130ff8c960e3ba919514490ffb2f22a2b7f35a2..25fc40ea7b9eca5cde32b3b278421aca83d560d2 100644 (file)
@@ -77,6 +77,10 @@ defmodule Mix.Tasks.Pleroma.User do
   ## Delete tags from a user.
 
       mix pleroma.user untag NICKNAME TAGS
+
+  ## Toggle confirmation of the user's account.
+
+      mix pleroma.user toggle_confirmed NICKNAME
   """
   def run(["new", nickname, email | rest]) do
     {options, [], []} =
@@ -388,6 +392,21 @@ defmodule Mix.Tasks.Pleroma.User do
     end
   end
 
+  def run(["toggle_confirmed", nickname]) do
+    Common.start_pleroma()
+
+    with %User{} = user <- User.get_cached_by_nickname(nickname) do
+      {:ok, user} = User.toggle_confirmation(user)
+
+      message = if user.info.confirmation_pending, do: "needs", else: "doesn't need"
+
+      Mix.shell().info("#{nickname} #{message} confirmation.")
+    else
+      _ ->
+        Mix.shell().error("No local user #{nickname}")
+    end
+  end
+
   defp set_moderator(user, value) do
     info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value})
 
index 4a09194786f3f58ca4c998fd67d99cb0a8dc4f40..4e54b15baf072d35a233fb069014c0abe65391af 100644 (file)
@@ -60,21 +60,24 @@ defmodule Pleroma.Activity do
     timestamps()
   end
 
-  def with_preloaded_object(query) do
-    query
-    |> join(
-      :inner,
-      [activity],
-      o in Object,
+  def with_joined_object(query) do
+    join(query, :inner, [activity], o in Object,
       on:
         fragment(
           "(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
           o.data,
           activity.data,
           activity.data
-        )
+        ),
+      as: :object
     )
-    |> preload([activity, object], object: object)
+  end
+
+  def with_preloaded_object(query) do
+    query
+    |> has_named_binding?(:object)
+    |> if(do: query, else: with_joined_object(query))
+    |> preload([activity, object: object], object: object)
   end
 
   def with_preloaded_bookmark(query, %User{} = user) do
@@ -108,7 +111,7 @@ defmodule Pleroma.Activity do
 
   def change(struct, params \\ %{}) do
     struct
-    |> cast(params, [:data])
+    |> cast(params, [:data, :recipients])
     |> validate_required([:data])
     |> unique_constraint(:ap_id, name: :activities_unique_apid_index)
   end
index 106fe5d18f4a634875ae6b3696b6ad93b3dbfc2f..f34be961f15065d37d86b6a7c83adc52349b1c4e 100644 (file)
@@ -95,7 +95,6 @@ defmodule Pleroma.BBS.Handler do
     activities =
       [user.ap_id | user.following]
       |> ActivityPub.fetch_activities(params)
-      |> ActivityPub.contain_timeline(user)
 
     Enum.each(activities, fn activity ->
       puts_activity(activity)
index 0db1959889ab08cb568f64646b9c48c79969bf24..238c1acf201dda42301b7cb8c4332fcb80e59ea0 100644 (file)
@@ -45,7 +45,7 @@ defmodule Pleroma.Conversation do
   2. Create a participation for all the people involved who don't have one already
   3. Bump all relevant participations to 'unread'
   """
-  def create_or_bump_for(activity) do
+  def create_or_bump_for(activity, opts \\ []) do
     with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity),
          "Create" <- activity.data["type"],
          object <- Pleroma.Object.normalize(activity),
@@ -58,7 +58,7 @@ defmodule Pleroma.Conversation do
       participations =
         Enum.map(users, fn user ->
           {:ok, participation} =
-            Participation.create_for_user_and_conversation(user, conversation)
+            Participation.create_for_user_and_conversation(user, conversation, opts)
 
           participation
         end)
@@ -72,4 +72,21 @@ defmodule Pleroma.Conversation do
       e -> {:error, e}
     end
   end
+
+  @doc """
+  This is only meant to be run by a mix task. It creates conversations/participations for all direct messages in the database.
+  """
+  def bump_for_all_activities do
+    stream =
+      Pleroma.Web.ActivityPub.ActivityPub.fetch_direct_messages_query()
+      |> Repo.stream()
+
+    Repo.transaction(
+      fn ->
+        stream
+        |> Enum.each(fn a -> create_or_bump_for(a, read: true) end)
+      end,
+      timeout: :infinity
+    )
+  end
 end
index 61021fb18104bfc07e0c706f52859602d6a82811..2a11f9069940d221aec41bb39d6f758fa4dd71aa 100644 (file)
@@ -22,15 +22,17 @@ defmodule Pleroma.Conversation.Participation do
 
   def creation_cng(struct, params) do
     struct
-    |> cast(params, [:user_id, :conversation_id])
+    |> cast(params, [:user_id, :conversation_id, :read])
     |> validate_required([:user_id, :conversation_id])
   end
 
-  def create_for_user_and_conversation(user, conversation) do
+  def create_for_user_and_conversation(user, conversation, opts \\ []) do
+    read = !!opts[:read]
+
     %__MODULE__{}
-    |> creation_cng(%{user_id: user.id, conversation_id: conversation.id})
+    |> creation_cng(%{user_id: user.id, conversation_id: conversation.id, read: read})
     |> Repo.insert(
-      on_conflict: [set: [read: false, updated_at: NaiveDateTime.utc_now()]],
+      on_conflict: [set: [read: read, updated_at: NaiveDateTime.utc_now()]],
       returning: true,
       conflict_target: [:user_id, :conversation_id]
     )
index df0f72f964d597d22915d1b2033352b0a19d898a..d0e254362ba60c386582fc3c4f5835da3b36712a 100644 (file)
@@ -29,7 +29,7 @@ defmodule Pleroma.Emails.AdminEmail do
       end
 
     statuses_html =
-      if length(statuses) > 0 do
+      if is_list(statuses) && length(statuses) > 0 do
         statuses_list_html =
           statuses
           |> Enum.map(fn
index 79efc29f0f73803463d8f94ccf79e463a3fbba4a..90457dadf8641f9c100aa067ba8678dc0ef55c60 100644 (file)
@@ -38,7 +38,8 @@ defmodule Pleroma.Filter do
     query =
       from(
         f in Pleroma.Filter,
-        where: f.user_id == ^user_id
+        where: f.user_id == ^user_id,
+        order_by: [desc: :id]
       )
 
     Repo.all(query)
index a476f1d49ab2ed02e98b0a1aba677b37f845a541..485ddfbc72ef03263199aaa4d4abd6c16829daa1 100644 (file)
@@ -20,8 +20,9 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
 
   defp headers do
     referrer_policy = Config.get([:http_security, :referrer_policy])
+    report_uri = Config.get([:http_security, :report_uri])
 
-    [
+    headers = [
       {"x-xss-protection", "1; mode=block"},
       {"x-permitted-cross-domain-policies", "none"},
       {"x-frame-options", "DENY"},
@@ -30,12 +31,27 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
       {"x-download-options", "noopen"},
       {"content-security-policy", csp_string() <> ";"}
     ]
+
+    if report_uri do
+      report_group = %{
+        "group" => "csp-endpoint",
+        "max-age" => 10_886_400,
+        "endpoints" => [
+          %{"url" => report_uri}
+        ]
+      }
+
+      headers ++ [{"reply-to", Jason.encode!(report_group)}]
+    else
+      headers
+    end
   end
 
   defp csp_string do
     scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
     static_url = Pleroma.Web.Endpoint.static_url()
     websocket_url = Pleroma.Web.Endpoint.websocket_url()
+    report_uri = Config.get([:http_security, :report_uri])
 
     connect_src = "connect-src 'self' #{static_url} #{websocket_url}"
 
@@ -53,7 +69,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
         "script-src 'self'"
       end
 
-    [
+    main_part = [
       "default-src 'none'",
       "base-uri 'self'",
       "frame-ancestors 'none'",
@@ -63,11 +79,14 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
       "font-src 'self'",
       "manifest-src 'self'",
       connect_src,
-      script_src,
-      if scheme == "https" do
-        "upgrade-insecure-requests"
-      end
+      script_src
     ]
+
+    report = if report_uri, do: ["report-uri #{report_uri}; report-to csp-endpoint"], else: []
+
+    insecure = if scheme == "https", do: ["upgrade-insecure-requests"], else: []
+
+    (main_part ++ report ++ insecure)
     |> Enum.join("; ")
   end
 
index c6a562a6143f22318df27cdf6fea6fbcbbc51f4e..28da310ee1d443acad326e568e5af3f3b013dfcf 100644 (file)
@@ -55,7 +55,7 @@ defmodule Pleroma.User do
     field(:last_refreshed_at, :naive_datetime_usec)
     has_many(:notifications, Notification)
     has_many(:registrations, Registration)
-    embeds_one(:info, Pleroma.User.Info)
+    embeds_one(:info, User.Info)
 
     timestamps()
   end
@@ -166,7 +166,7 @@ defmodule Pleroma.User do
 
   def update_changeset(struct, params \\ %{}) do
     struct
-    |> cast(params, [:bio, :name, :avatar])
+    |> cast(params, [:bio, :name, :avatar, :following])
     |> unique_constraint(:nickname)
     |> validate_format(:nickname, local_nickname_regex())
     |> validate_length(:bio, max: 5000)
@@ -233,7 +233,7 @@ defmodule Pleroma.User do
       |> validate_confirmation(:password)
       |> unique_constraint(:email)
       |> unique_constraint(:nickname)
-      |> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames]))
+      |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
       |> validate_format(:nickname, local_nickname_regex())
       |> validate_format(:email, @email_regex)
       |> validate_length(:bio, max: 1000)
@@ -278,7 +278,7 @@ defmodule Pleroma.User do
     with {:ok, user} <- Repo.insert(changeset),
          {:ok, user} <- autofollow_users(user),
          {:ok, user} <- set_cache(user),
-         {:ok, _} <- Pleroma.User.WelcomeMessage.post_welcome_message_to_user(user),
+         {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
          {:ok, _} <- try_send_confirmation_email(user) do
       {:ok, user}
     end
@@ -709,6 +709,18 @@ defmodule Pleroma.User do
     end
   end
 
+  def remove_duplicated_following(%User{following: following} = user) do
+    uniq_following = Enum.uniq(following)
+
+    if length(following) == length(uniq_following) do
+      {:ok, user}
+    else
+      user
+      |> update_changeset(%{following: uniq_following})
+      |> update_and_set_cache()
+    end
+  end
+
   @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
   def get_users_from_set(ap_ids, local_only \\ true) do
     criteria = %{ap_id: ap_ids, deactivated: false}
@@ -1132,7 +1144,6 @@ defmodule Pleroma.User do
     stream =
       ap_id
       |> Activity.query_by_actor()
-      |> Activity.with_preloaded_object()
       |> Repo.stream()
 
     Repo.transaction(fn -> Enum.each(stream, &delete_activity(&1)) end, timeout: :infinity)
@@ -1378,4 +1389,17 @@ defmodule Pleroma.User do
   def showing_reblogs?(%User{} = user, %User{} = target) do
     target.ap_id not in user.info.muted_reblogs
   end
+
+  @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
+  def toggle_confirmation(%User{} = user) do
+    need_confirmation? = !user.info.confirmation_pending
+
+    info_changeset =
+      User.Info.confirmation_changeset(user.info, need_confirmation: need_confirmation?)
+
+    user
+    |> change()
+    |> put_embed(:info, info_changeset)
+    |> update_and_set_cache()
+  end
 end
index 5a50ee639a383aa7e499214d22e33158f61373bd..5f0cefc00ddb2f4610c9c1f92b5bb87e52e510f9 100644 (file)
@@ -212,7 +212,7 @@ defmodule Pleroma.User.Info do
     ])
   end
 
-  @spec confirmation_changeset(Info.t(), keyword()) :: Ecto.Changerset.t()
+  @spec confirmation_changeset(Info.t(), keyword()) :: Changeset.t()
   def confirmation_changeset(info, opts) do
     need_confirmation? = Keyword.get(opts, :need_confirmation)
 
index b1ce5ae5218f4f6b1100678e7e6ce988bfb12f97..048b9202b3df80c83d6200da950082fc5e340f90 100644 (file)
@@ -539,8 +539,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
             )
         )
 
-      Ecto.Adapters.SQL.to_sql(:all, Repo, query)
-
       query
     else
       Logger.error("Could not restrict visibility to #{visibility}")
@@ -556,8 +554,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
           fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
       )
 
-    Ecto.Adapters.SQL.to_sql(:all, Repo, query)
-
     query
   end
 
@@ -568,6 +564,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_visibility(query, _visibility), do: query
 
+  defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}) do
+    query =
+      from(
+        a in query,
+        where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
+      )
+
+    query
+  end
+
+  defp restrict_thread_visibility(query, _), do: query
+
   def fetch_user_activities(user, reading_user, params \\ %{}) do
     params =
       params
@@ -694,6 +702,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_type(query, _), do: query
 
+  defp restrict_state(query, %{"state" => state}) do
+    from(activity in query, where: fragment("?->>'state' = ?", activity.data, ^state))
+  end
+
+  defp restrict_state(query, _), do: query
+
   defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
     from(
       activity in query,
@@ -749,8 +763,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     blocks = info.blocks || []
     domain_blocks = info.domain_blocks || []
 
+    query =
+      if has_named_binding?(query, :object), do: query, else: Activity.with_joined_object(query)
+
     from(
-      activity in query,
+      [activity, object: o] in query,
       where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
       where: fragment("not (? && ?)", activity.recipients, ^blocks),
       where:
@@ -760,7 +777,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
           activity.data,
           ^blocks
         ),
-      where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks)
+      where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks),
+      where: fragment("not (split_part(?->>'actor', '/', 3) = ANY(?))", o.data, ^domain_blocks)
     )
   end
 
@@ -840,11 +858,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> restrict_local(opts)
     |> restrict_actor(opts)
     |> restrict_type(opts)
+    |> restrict_state(opts)
     |> restrict_favorited_by(opts)
     |> restrict_blocked(opts)
     |> restrict_muted(opts)
     |> restrict_media(opts)
     |> restrict_visibility(opts)
+    |> restrict_thread_visibility(opts)
     |> restrict_replies(opts)
     |> restrict_reblogs(opts)
     |> restrict_pinned(opts)
@@ -983,11 +1003,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     contain_broken_threads(activity, user)
   end
 
-  # do post-processing on a timeline
-  def contain_timeline(timeline, user) do
-    timeline
-    |> Enum.filter(fn activity ->
-      contain_activity(activity, user)
-    end)
+  def fetch_direct_messages_query do
+    Activity
+    |> restrict_type(%{"type" => "Create"})
+    |> restrict_visibility(%{visibility: "direct"})
+    |> order_by([activity], asc: activity.id)
   end
 end
index 2f105700bc7f57ffe855a21d780a029c39468d47..50426e920be268a7d4d04b2572aa0a5948f93e64 100644 (file)
@@ -55,7 +55,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
     object =
       if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_nsfw]), actor_host) do
         tags = (child_object["tag"] || []) ++ ["nsfw"]
-        child_object = Map.put(child_object, "tags", tags)
+        child_object = Map.put(child_object, "tag", tags)
         child_object = Map.put(child_object, "sensitive", true)
         Map.put(object, "object", child_object)
       else
index b52be30e72c2553b375cf1b17ca4b6064247b650..6683b8d8e6a294b6ab19797f5f4d495fdfd08e1a 100644 (file)
@@ -31,7 +31,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
 
     object =
       object
-      |> Map.put("tags", tags)
+      |> Map.put("tag", tags)
       |> Map.put("sensitive", true)
 
     message = Map.put(message, "object", object)
index c4cc77a9510fb124029539a5f515301eb271c6f4..49d1610a7d9b429087aac48bcd7d16dc20d83ce4 100644 (file)
@@ -11,7 +11,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   alias Pleroma.Object.Containment
   alias Pleroma.Repo
   alias Pleroma.User
-  alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.ActivityPub.Visibility
index 236d1b4aca4f9c2b9447f68fe2af03867b1c96c1..ca8a0844be1e4840b7d384d64a4a815e2adaaba6 100644 (file)
@@ -20,6 +20,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   require Logger
 
   @supported_object_types ["Article", "Note", "Video", "Page"]
+  @supported_report_states ~w(open closed resolved)
+  @valid_visibilities ~w(public unlisted private direct)
 
   # Some implementations send the actor URI as the actor field, others send the entire actor object,
   # so figure out what the actor's URI is based on what we have.
@@ -670,7 +672,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
       "actor" => params.actor.ap_id,
       "content" => params.content,
       "object" => object,
-      "context" => params.context
+      "context" => params.context,
+      "state" => "open"
     }
     |> Map.merge(additional)
   end
@@ -713,4 +716,77 @@ defmodule Pleroma.Web.ActivityPub.Utils do
       end
     end
   end
+
+  #### Report-related helpers
+
+  def update_report_state(%Activity{} = activity, state) when state in @supported_report_states do
+    with new_data <- Map.put(activity.data, "state", state),
+         changeset <- Changeset.change(activity, data: new_data),
+         {:ok, activity} <- Repo.update(changeset) do
+      {:ok, activity}
+    end
+  end
+
+  def update_report_state(_, _), do: {:error, "Unsupported state"}
+
+  def update_activity_visibility(activity, visibility) when visibility in @valid_visibilities do
+    [to, cc, recipients] =
+      activity
+      |> get_updated_targets(visibility)
+      |> Enum.map(&Enum.uniq/1)
+
+    object_data =
+      activity.object.data
+      |> Map.put("to", to)
+      |> Map.put("cc", cc)
+
+    {:ok, object} =
+      activity.object
+      |> Object.change(%{data: object_data})
+      |> Object.update_and_set_cache()
+
+    activity_data =
+      activity.data
+      |> Map.put("to", to)
+      |> Map.put("cc", cc)
+
+    activity
+    |> Map.put(:object, object)
+    |> Activity.change(%{data: activity_data, recipients: recipients})
+    |> Repo.update()
+  end
+
+  def update_activity_visibility(_, _), do: {:error, "Unsupported visibility"}
+
+  defp get_updated_targets(
+         %Activity{data: %{"to" => to} = data, recipients: recipients},
+         visibility
+       ) do
+    cc = Map.get(data, "cc", [])
+    follower_address = User.get_cached_by_ap_id(data["actor"]).follower_address
+    public = "https://www.w3.org/ns/activitystreams#Public"
+
+    case visibility do
+      "public" ->
+        to = [public | List.delete(to, follower_address)]
+        cc = [follower_address | List.delete(cc, public)]
+        recipients = [public | recipients]
+        [to, cc, recipients]
+
+      "private" ->
+        to = [follower_address | List.delete(to, public)]
+        cc = List.delete(cc, public)
+        recipients = List.delete(recipients, public)
+        [to, cc, recipients]
+
+      "unlisted" ->
+        to = [follower_address | List.delete(to, public)]
+        cc = [public | List.delete(cc, follower_address)]
+        recipients = recipients ++ [follower_address, public]
+        [to, cc, recipients]
+
+      _ ->
+        [to, cc, recipients]
+    end
+  end
 end
index b38ee0442db93daf83f47c7be7cd2d9f7900335a..93b50ee473b7cd9a4e27fb51105ee6efa85c42a0 100644 (file)
@@ -1,6 +1,7 @@
 defmodule Pleroma.Web.ActivityPub.Visibility do
   alias Pleroma.Activity
   alias Pleroma.Object
+  alias Pleroma.Repo
   alias Pleroma.User
 
   def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
@@ -13,11 +14,12 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
   end
 
   def is_private?(activity) do
-    unless is_public?(activity) do
-      follower_address = User.get_cached_by_ap_id(activity.data["actor"]).follower_address
-      Enum.any?(activity.data["to"], &(&1 == follower_address))
+    with false <- is_public?(activity),
+         %User{follower_address: follower_address} <-
+           User.get_cached_by_ap_id(activity.data["actor"]) do
+      follower_address in activity.data["to"]
     else
-      false
+      _ -> false
     end
   end
 
@@ -38,25 +40,14 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
     visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
   end
 
-  # guard
-  def entire_thread_visible_for_user?(nil, _user), do: false
+  def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do
+    {:ok, %{rows: [[result]]}} =
+      Ecto.Adapters.SQL.query(Repo, "SELECT thread_visibility($1, $2)", [
+        user.ap_id,
+        activity.data["id"]
+      ])
 
-  # XXX: Probably even more inefficient than the previous implementation intended to be a placeholder untill https://git.pleroma.social/pleroma/pleroma/merge_requests/971 is in develop
-  # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
-
-  def entire_thread_visible_for_user?(
-        %Activity{} = tail,
-        # %Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,
-        user
-      ) do
-    case Object.normalize(tail) do
-      %{data: %{"inReplyTo" => parent_id}} when is_binary(parent_id) ->
-        parent = Activity.get_in_reply_to_activity(tail)
-        visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user)
-
-      _ ->
-        visible_for_user?(tail, user)
-    end
+    result
   end
 
   def get_visibility(object) do
index e00b33aba698585f43adc1254096231ac6180afe..de2a13c015c80d172ca224583d485f47a0819da5 100644 (file)
@@ -4,11 +4,16 @@
 
 defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   use Pleroma.Web, :controller
+  alias Pleroma.Activity
   alias Pleroma.User
   alias Pleroma.UserInviteToken
+  alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Relay
   alias Pleroma.Web.AdminAPI.AccountView
+  alias Pleroma.Web.AdminAPI.ReportView
   alias Pleroma.Web.AdminAPI.Search
+  alias Pleroma.Web.CommonAPI
+  alias Pleroma.Web.MastodonAPI.StatusView
 
   import Pleroma.Web.ControllerHelper, only: [json_response: 3]
 
@@ -287,12 +292,88 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     |> json(token.token)
   end
 
+  def list_reports(conn, params) do
+    params =
+      params
+      |> Map.put("type", "Flag")
+      |> Map.put("skip_preload", true)
+
+    reports =
+      []
+      |> ActivityPub.fetch_activities(params)
+      |> Enum.reverse()
+
+    conn
+    |> put_view(ReportView)
+    |> render("index.json", %{reports: reports})
+  end
+
+  def report_show(conn, %{"id" => id}) do
+    with %Activity{} = report <- Activity.get_by_id(id) do
+      conn
+      |> put_view(ReportView)
+      |> render("show.json", %{report: report})
+    else
+      _ -> {:error, :not_found}
+    end
+  end
+
+  def report_update_state(conn, %{"id" => id, "state" => state}) do
+    with {:ok, report} <- CommonAPI.update_report_state(id, state) do
+      conn
+      |> put_view(ReportView)
+      |> render("show.json", %{report: report})
+    end
+  end
+
+  def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
+    with false <- is_nil(params["status"]),
+         %Activity{} <- Activity.get_by_id(id) do
+      params =
+        params
+        |> Map.put("in_reply_to_status_id", id)
+        |> Map.put("visibility", "direct")
+
+      {:ok, activity} = CommonAPI.post(user, params)
+
+      conn
+      |> put_view(StatusView)
+      |> render("status.json", %{activity: activity})
+    else
+      true ->
+        {:param_cast, nil}
+
+      nil ->
+        {:error, :not_found}
+    end
+  end
+
+  def status_update(conn, %{"id" => id} = params) do
+    with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
+      conn
+      |> put_view(StatusView)
+      |> render("status.json", %{activity: activity})
+    end
+  end
+
+  def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+    with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
+      json(conn, %{})
+    end
+  end
+
   def errors(conn, {:error, :not_found}) do
     conn
     |> put_status(404)
     |> json("Not found")
   end
 
+  def errors(conn, {:error, reason}) do
+    conn
+    |> put_status(400)
+    |> json(reason)
+  end
+
   def errors(conn, {:param_cast, _}) do
     conn
     |> put_status(400)
diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex
new file mode 100644 (file)
index 0000000..47a73dc
--- /dev/null
@@ -0,0 +1,41 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.ReportView do
+  use Pleroma.Web, :view
+  alias Pleroma.Activity
+  alias Pleroma.User
+  alias Pleroma.Web.CommonAPI.Utils
+  alias Pleroma.Web.MastodonAPI.AccountView
+  alias Pleroma.Web.MastodonAPI.StatusView
+
+  def render("index.json", %{reports: reports}) do
+    %{
+      reports: render_many(reports, __MODULE__, "show.json", as: :report)
+    }
+  end
+
+  def render("show.json", %{report: report}) do
+    user = User.get_cached_by_ap_id(report.data["actor"])
+    created_at = Utils.to_masto_date(report.data["published"])
+
+    [account_ap_id | status_ap_ids] = report.data["object"]
+    account = User.get_cached_by_ap_id(account_ap_id)
+
+    statuses =
+      Enum.map(status_ap_ids, fn ap_id ->
+        Activity.get_by_ap_id_with_object(ap_id)
+      end)
+
+    %{
+      id: report.id,
+      account: AccountView.render("account.json", %{user: account}),
+      actor: AccountView.render("account.json", %{user: user}),
+      content: report.data["content"],
+      created_at: created_at,
+      statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}),
+      state: report.data["state"]
+    }
+  end
+end
index 97134cd1937064979c818074115fd24a9362f229..a08075bc2c4168fa258a7e87422522256500f20e 100644 (file)
@@ -71,6 +71,9 @@ defmodule Pleroma.Web.CommonAPI do
          {:ok, _} <- unpin(activity_id, user),
          {:ok, delete} <- ActivityPub.delete(object) do
       {:ok, delete}
+    else
+      _ ->
+        {:error, "Could not delete"}
     end
   end
 
@@ -318,6 +321,60 @@ defmodule Pleroma.Web.CommonAPI do
     end
   end
 
+  def update_report_state(activity_id, state) do
+    with %Activity{} = activity <- Activity.get_by_id(activity_id),
+         {:ok, activity} <- Utils.update_report_state(activity, state) do
+      {:ok, activity}
+    else
+      nil ->
+        {:error, :not_found}
+
+      {:error, reason} ->
+        {:error, reason}
+
+      _ ->
+        {:error, "Could not update state"}
+    end
+  end
+
+  def update_activity_scope(activity_id, opts \\ %{}) do
+    with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
+         {:ok, activity} <- toggle_sensitive(activity, opts),
+         {:ok, activity} <- set_visibility(activity, opts) do
+      {:ok, activity}
+    else
+      nil ->
+        {:error, :not_found}
+
+      {:error, reason} ->
+        {:error, reason}
+    end
+  end
+
+  defp toggle_sensitive(activity, %{"sensitive" => sensitive}) when sensitive in ~w(true false) do
+    toggle_sensitive(activity, %{"sensitive" => String.to_existing_atom(sensitive)})
+  end
+
+  defp toggle_sensitive(%Activity{object: object} = activity, %{"sensitive" => sensitive})
+       when is_boolean(sensitive) do
+    new_data = Map.put(object.data, "sensitive", sensitive)
+
+    {:ok, object} =
+      object
+      |> Object.change(%{data: new_data})
+      |> Object.update_and_set_cache()
+
+    {:ok, Map.put(activity, :object, object)}
+  end
+
+  defp toggle_sensitive(activity, _), do: {:ok, activity}
+
+  defp set_visibility(activity, %{"visibility" => visibility}) do
+    Utils.update_activity_visibility(activity, visibility)
+  end
+
+  defp set_visibility(activity, _), do: {:ok, activity}
+
   def hide_reblogs(user, muted) do
     ap_id = muted.ap_id
 
index ba6ed67ef38d493d3070e82283cce8dc459137f2..a463c1f98937c59ac1f9ab2b82c70c899d3c4c2c 100644 (file)
@@ -246,13 +246,11 @@ defmodule Pleroma.Web.CommonAPI.Utils do
       "tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
     }
 
-    if in_reply_to do
-      in_reply_to_object = Object.normalize(in_reply_to)
-
-      object
-      |> Map.put("inReplyTo", in_reply_to_object.data["id"])
+    with false <- is_nil(in_reply_to),
+         %Object{} = in_reply_to_object <- Object.normalize(in_reply_to) do
+      Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
     else
-      object
+      _ -> object
     end
   end
 
index fb4e8548da10a5f48ceafa93d5faa9f06cbecc0a..70f870244fbb5f7af1283985620004f3791522f7 100644 (file)
@@ -52,9 +52,9 @@ defmodule Pleroma.Web.Federator.Publisher do
   @doc """
   Relays an activity to all specified peers.
   """
-  @callback publish(Pleroma.User.t(), Pleroma.Activity.t()) :: :ok | {:error, any()}
+  @callback publish(User.t(), Activity.t()) :: :ok | {:error, any()}
 
-  @spec publish(Pleroma.User.t(), Pleroma.Activity.t()) :: :ok
+  @spec publish(User.t(), Activity.t()) :: :ok
   def publish(%User{} = user, %Activity{} = activity) do
     Config.get([:instance, :federation_publisher_modules])
     |> Enum.each(fn module ->
@@ -70,9 +70,9 @@ defmodule Pleroma.Web.Federator.Publisher do
   @doc """
   Gathers links used by an outgoing federation module for WebFinger output.
   """
-  @callback gather_webfinger_links(Pleroma.User.t()) :: list()
+  @callback gather_webfinger_links(User.t()) :: list()
 
-  @spec gather_webfinger_links(Pleroma.User.t()) :: list()
+  @spec gather_webfinger_links(User.t()) :: list()
   def gather_webfinger_links(%User{} = user) do
     Config.get([:instance, :federation_publisher_modules])
     |> Enum.reduce([], fn module, links ->
index 87e597074c1b7c8c961603aff51368e07f7039e1..1b776fbca1219cc545bbb6304a865035da7e6a58 100644 (file)
@@ -303,7 +303,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     activities =
       [user.ap_id | user.following]
       |> ActivityPub.fetch_activities(params)
-      |> ActivityPub.contain_timeline(user)
       |> Enum.reverse()
 
     conn
@@ -1223,7 +1222,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     accounts
     |> Enum.each(fn account_id ->
       with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
-           %User{} = followed <- Pleroma.User.get_cached_by_id(account_id) do
+           %User{} = followed <- User.get_cached_by_id(account_id) do
         Pleroma.List.unfollow(list, followed)
       end
     end)
index 779b9a3824043aeb690451c68f79f4bd7c061a18..134c07b7eb57228e2b8b6bbfda16b9f4bae2a915 100644 (file)
@@ -40,7 +40,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
     follow_activity = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, target)
 
     requested =
-      if follow_activity do
+      if follow_activity && !User.following?(target, user) do
         follow_activity.data["state"] == "pending"
       else
         false
index b47688de1d276524daf11820f086b9ef35980a59..18973413ea16b607fee74c8ae88ab76f3ef75831 100644 (file)
@@ -20,7 +20,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
     field(:scopes, {:array, :string}, default: [])
     field(:valid_until, :naive_datetime_usec)
     field(:used, :boolean, default: false)
-    belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId)
+    belongs_to(:user, User, type: Pleroma.FlakeId)
     belongs_to(:app, App)
 
     timestamps()
index ef047d565558614ffebf25f4852b758f07b39699..66c95c2e98cc044eb4c3ffbd97fa7475ef8ea00e 100644 (file)
@@ -22,7 +22,7 @@ defmodule Pleroma.Web.OAuth.Token do
     field(:refresh_token, :string)
     field(:scopes, {:array, :string}, default: [])
     field(:valid_until, :naive_datetime_usec)
-    belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId)
+    belongs_to(:user, User, type: Pleroma.FlakeId)
     belongs_to(:app, App)
 
     timestamps()
index 7fef82f82a3eb613d37e6b63d321155b91fe5079..6a4e4a1d4e77d098d701352833ccdfc0686ba42d 100644 (file)
@@ -194,6 +194,14 @@ defmodule Pleroma.Web.Router do
 
     get("/users", AdminAPIController, :list_users)
     get("/users/:nickname", AdminAPIController, :user_show)
+
+    get("/reports", AdminAPIController, :list_reports)
+    get("/reports/:id", AdminAPIController, :report_show)
+    put("/reports/:id", AdminAPIController, :report_update_state)
+    post("/reports/:id/respond", AdminAPIController, :report_respond)
+
+    put("/statuses/:id", AdminAPIController, :status_update)
+    delete("/statuses/:id", AdminAPIController, :status_delete)
   end
 
   scope "/", Pleroma.Web.TwitterAPI do
index 3c5a70be99308e36df2ef524d21d58aa8c160a8a..31e86685a2b0172f073ee4438a33031dcd2f01c4 100644 (file)
@@ -101,9 +101,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
       |> Map.put("blocking_user", user)
       |> Map.put("user", user)
 
-    activities =
-      ActivityPub.fetch_activities([user.ap_id | user.following], params)
-      |> ActivityPub.contain_timeline(user)
+    activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
 
     conn
     |> put_view(ActivityView)
index 3a3b98a1052723735ed6e43d892ee7c2a2f1f605..1239b962ad3fd8e9f05a0f4f85d7377884eb195f 100644 (file)
@@ -99,7 +99,7 @@ defmodule Pleroma.Web.WebFinger do
 
       info_cng =
         info
-        |> Pleroma.User.Info.set_keys(pem)
+        |> User.Info.set_keys(pem)
 
       cng =
         Ecto.Changeset.change(user)
diff --git a/mix.exs b/mix.exs
index 1396d0072ca477d53895b131bd78b5b8b6840f4e..95c052c34fd4fb50658f2a4df33bbe1c44b2d671 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -66,7 +66,10 @@ defmodule Pleroma.Mixfile do
       {:plug_cowboy, "~> 2.0"},
       {:phoenix_pubsub, "~> 1.1"},
       {:phoenix_ecto, "~> 4.0"},
-      {:ecto_sql, "~>3.0.5"},
+      {:ecto_sql,
+       git: "https://github.com/elixir-ecto/ecto_sql",
+       ref: "14cb065a74c488d737d973f7a91bc036c6245f78",
+       override: true},
       {:postgrex, ">= 0.13.5"},
       {:gettext, "~> 0.15"},
       {:comeonin, "~> 4.1.1"},
index cd2d2370a042d14ad4758fffa2d32ce4b97acdb7..bacc09787c939c41e1651e501cd751db582cb92a 100644 (file)
--- a/mix.lock
+++ b/mix.lock
   "cowlib": {:hex, :cowlib, "2.7.0", "3ef16e77562f9855a2605900cedb15c1462d76fb1be6a32fc3ae91973ee543d2", [:rebar3], [], "hexpm"},
   "credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
   "crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
-  "db_connection": {:hex, :db_connection, "2.0.5", "ddb2ba6761a08b2bb9ca0e7d260e8f4dd39067426d835c24491a321b7f92a4da", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
+  "db_connection": {:hex, :db_connection, "2.0.6", "bde2f85d047969c5b5800cb8f4b3ed6316c8cb11487afedac4aa5f93fd39abfa", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
   "decimal": {:hex, :decimal, "1.7.0", "30d6b52c88541f9a66637359ddf85016df9eb266170d53105f02e4a67e00c5aa", [:mix], [], "hexpm"},
   "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
   "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"},
-  "ecto": {:hex, :ecto, "3.0.7", "44dda84ac6b17bbbdeb8ac5dfef08b7da253b37a453c34ab1a98de7f7e5fec7f", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
-  "ecto_sql": {:hex, :ecto_sql, "3.0.5", "7e44172b4f7aca4469f38d7f6a3da394dbf43a1bcf0ca975e958cb957becd74e", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0.6", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
+  "ecto": {:hex, :ecto, "3.1.4", "69d852da7a9f04ede725855a35ede48d158ca11a404fe94f8b2fb3b2162cd3c9", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
+  "ecto_sql": {:git, "https://github.com/elixir-ecto/ecto_sql", "14cb065a74c488d737d973f7a91bc036c6245f78", [ref: "14cb065a74c488d737d973f7a91bc036c6245f78"]},
   "esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"},
   "eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"},
   "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
@@ -66,7 +66,7 @@
   "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
   "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
   "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"},
-  "postgrex": {:hex, :postgrex, "0.14.1", "63247d4a5ad6b9de57a0bac5d807e1c32d41e39c04b8a4156a26c63bcd8a2e49", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
+  "postgrex": {:hex, :postgrex, "0.14.3", "5754dee2fdf6e9e508cbf49ab138df964278700b764177e8f3871e658b345a1e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
   "prometheus": {:hex, :prometheus, "4.2.2", "a830e77b79dc6d28183f4db050a7cac926a6c58f1872f9ef94a35cd989aceef8", [:mix, :rebar3], [], "hexpm"},
   "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.1", "6c768ea9654de871e5b32fab2eac348467b3021604ebebbcbd8bcbe806a65ed5", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
   "prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
   "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
   "swoosh": {:hex, :swoosh, "0.20.0", "9a6c13822c9815993c03b6f8fccc370fcffb3c158d9754f67b1fdee6b3a5d928", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.12", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"},
   "syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]},
-  "telemetry": {:hex, :telemetry, "0.3.0", "099a7f3ce31e4780f971b4630a3c22ec66d22208bc090fe33a2a3a6a67754a73", [:rebar3], [], "hexpm"},
+  "telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"},
   "tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
   "timex": {:hex, :timex, "3.5.0", "b0a23167da02d0fe4f1a4e104d1f929a00d348502b52432c05de875d0b9cffa5", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
   "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
-  "tzdata": {:hex, :tzdata, "0.5.17", "50793e3d85af49736701da1a040c415c97dc1caf6464112fd9bd18f425d3053b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
+  "tzdata": {:hex, :tzdata, "0.5.20", "304b9e98a02840fb32a43ec111ffbe517863c8566eb04a061f1c4dbb90b4d84c", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
   "ueberauth": {:hex, :ueberauth, "0.6.1", "9e90d3337dddf38b1ca2753aca9b1e53d8a52b890191cdc55240247c89230412", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
   "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
   "unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
diff --git a/priv/repo/migrations/20190511191044_set_default_state_to_reports.exs b/priv/repo/migrations/20190511191044_set_default_state_to_reports.exs
new file mode 100644 (file)
index 0000000..0d3d253
--- /dev/null
@@ -0,0 +1,19 @@
+defmodule Pleroma.Repo.Migrations.SetDefaultStateToReports do
+  use Ecto.Migration
+
+  def up do
+    execute """
+      UPDATE activities AS a
+      SET data = jsonb_set(data, '{state}', '"open"', true)
+      WHERE data->>'type' = 'Flag'
+    """
+  end
+
+  def down do
+    execute """
+      UPDATE activities AS a
+      SET data = data #- '{state}'
+      WHERE data->>'type' = 'Flag'
+    """
+  end
+end
diff --git a/priv/repo/migrations/20190515222404_add_thread_visibility_function.exs b/priv/repo/migrations/20190515222404_add_thread_visibility_function.exs
new file mode 100644 (file)
index 0000000..dc9abc9
--- /dev/null
@@ -0,0 +1,73 @@
+defmodule Pleroma.Repo.Migrations.AddThreadVisibilityFunction do
+  use Ecto.Migration
+  @disable_ddl_transaction true
+
+  def up do
+    statement = """
+    CREATE OR REPLACE FUNCTION thread_visibility(actor varchar, activity_id varchar) RETURNS boolean AS $$
+    DECLARE
+      public varchar := 'https://www.w3.org/ns/activitystreams#Public';
+      child objects%ROWTYPE;
+      activity activities%ROWTYPE;
+      actor_user users%ROWTYPE;
+      author_fa varchar;
+      valid_recipients varchar[];
+    BEGIN
+      --- Fetch our actor.
+      SELECT * INTO actor_user FROM users WHERE users.ap_id = actor;
+
+      --- Fetch our initial activity.
+      SELECT * INTO activity FROM activities WHERE activities.data->>'id' = activity_id;
+
+      LOOP
+        --- Ensure that we have an activity before continuing.
+        --- If we don't, the thread is not satisfiable.
+        IF activity IS NULL THEN
+          RETURN false;
+        END IF;
+
+        --- We only care about Create activities.
+        IF activity.data->>'type' != 'Create' THEN
+          RETURN true;
+        END IF;
+
+        --- Normalize the child object into child.
+        SELECT * INTO child FROM objects
+        INNER JOIN activities ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
+        WHERE COALESCE(activity.data->'object'->>'id', activity.data->>'object') = objects.data->>'id';
+
+        --- Fetch the author's AS2 following collection.
+        SELECT COALESCE(users.follower_address, '') INTO author_fa FROM users WHERE users.ap_id = activity.actor;
+
+        --- Prepare valid recipients array.
+        valid_recipients := ARRAY[actor, public];
+        IF ARRAY[author_fa] && actor_user.following THEN
+          valid_recipients := valid_recipients || author_fa;
+        END IF;
+
+        --- Check visibility.
+        IF NOT valid_recipients && activity.recipients THEN
+          --- activity not visible, break out of the loop
+          RETURN false;
+        END IF;
+
+        --- If there's a parent, load it and do this all over again.
+        IF (child.data->'inReplyTo' IS NOT NULL) AND (child.data->'inReplyTo' != 'null'::jsonb) THEN
+          SELECT * INTO activity FROM activities
+          INNER JOIN objects ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
+          WHERE child.data->>'inReplyTo' = objects.data->>'id';
+        ELSE
+          RETURN true;
+        END IF;
+      END LOOP;
+    END;
+    $$ LANGUAGE plpgsql IMMUTABLE;
+    """
+
+    execute(statement)
+  end
+
+  def down do
+    execute("drop function thread_visibility(actor varchar, activity_id varchar)")
+  end
+end
index 864b2eb03066ec28f3244f6d6626f63fcd89a040..5903d10ff05b1ffd94b4d4594952a1b393283b6f 100644 (file)
@@ -11,6 +11,26 @@ defmodule Pleroma.ConversationTest do
 
   import Pleroma.Factory
 
+  test "it goes through old direct conversations" do
+    user = insert(:user)
+    other_user = insert(:user)
+
+    {:ok, _activity} =
+      CommonAPI.post(user, %{"visibility" => "direct", "status" => "hey @#{other_user.nickname}"})
+
+    Repo.delete_all(Conversation)
+    Repo.delete_all(Conversation.Participation)
+
+    refute Repo.one(Conversation)
+
+    Conversation.bump_for_all_activities()
+
+    assert Repo.one(Conversation)
+    [participation, _p2] = Repo.all(Conversation.Participation)
+
+    assert participation.read
+  end
+
   test "it creates a conversation for given ap_id" do
     assert {:ok, %Conversation{} = conversation} =
              Conversation.create_for_ap_id("https://some_ap_id")
index 06f4f6e50d57241d349b17c2805ab7d8860646c1..5e70111605f3f738152d662035153bc0d39264c1 100644 (file)
@@ -125,7 +125,7 @@ defmodule Pleroma.FormatterTest do
       archaeme =
         insert(:user, %{
           nickname: "archa_eme_",
-          info: %Pleroma.User.Info{source_data: %{"url" => "https://archeme/@archa_eme_"}}
+          info: %User.Info{source_data: %{"url" => "https://archeme/@archa_eme_"}}
         })
 
       archaeme_remote = insert(:user, %{nickname: "archaeme@archae.me"})
index 0cbb7e4b11fcfd6f57bb752ad583c840ad7c91a0..7dfd50c1febd9a6a6f5ace616c853725bb16cf39 100644 (file)
@@ -7,77 +7,96 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
   alias Pleroma.Config
   alias Plug.Conn
 
-  test "it sends CSP headers when enabled", %{conn: conn} do
-    Config.put([:http_security, :enabled], true)
-
-    conn =
-      conn
-      |> get("/api/v1/instance")
-
-    refute Conn.get_resp_header(conn, "x-xss-protection") == []
-    refute Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
-    refute Conn.get_resp_header(conn, "x-frame-options") == []
-    refute Conn.get_resp_header(conn, "x-content-type-options") == []
-    refute Conn.get_resp_header(conn, "x-download-options") == []
-    refute Conn.get_resp_header(conn, "referrer-policy") == []
-    refute Conn.get_resp_header(conn, "content-security-policy") == []
-  end
+  describe "http security enabled" do
+    setup do
+      enabled = Config.get([:http_securiy, :enabled])
 
-  test "it does not send CSP headers when disabled", %{conn: conn} do
-    Config.put([:http_security, :enabled], false)
+      Config.put([:http_security, :enabled], true)
 
-    conn =
-      conn
-      |> get("/api/v1/instance")
+      on_exit(fn ->
+        Config.put([:http_security, :enabled], enabled)
+      end)
 
-    assert Conn.get_resp_header(conn, "x-xss-protection") == []
-    assert Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
-    assert Conn.get_resp_header(conn, "x-frame-options") == []
-    assert Conn.get_resp_header(conn, "x-content-type-options") == []
-    assert Conn.get_resp_header(conn, "x-download-options") == []
-    assert Conn.get_resp_header(conn, "referrer-policy") == []
-    assert Conn.get_resp_header(conn, "content-security-policy") == []
-  end
+      :ok
+    end
 
-  test "it sends STS headers when enabled", %{conn: conn} do
-    Config.put([:http_security, :enabled], true)
-    Config.put([:http_security, :sts], true)
+    test "it sends CSP headers when enabled", %{conn: conn} do
+      conn = get(conn, "/api/v1/instance")
 
-    conn =
-      conn
-      |> get("/api/v1/instance")
+      refute Conn.get_resp_header(conn, "x-xss-protection") == []
+      refute Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
+      refute Conn.get_resp_header(conn, "x-frame-options") == []
+      refute Conn.get_resp_header(conn, "x-content-type-options") == []
+      refute Conn.get_resp_header(conn, "x-download-options") == []
+      refute Conn.get_resp_header(conn, "referrer-policy") == []
+      refute Conn.get_resp_header(conn, "content-security-policy") == []
+    end
 
-    refute Conn.get_resp_header(conn, "strict-transport-security") == []
-    refute Conn.get_resp_header(conn, "expect-ct") == []
-  end
+    test "it sends STS headers when enabled", %{conn: conn} do
+      Config.put([:http_security, :sts], true)
 
-  test "it does not send STS headers when disabled", %{conn: conn} do
-    Config.put([:http_security, :enabled], true)
-    Config.put([:http_security, :sts], false)
+      conn = get(conn, "/api/v1/instance")
 
-    conn =
-      conn
-      |> get("/api/v1/instance")
+      refute Conn.get_resp_header(conn, "strict-transport-security") == []
+      refute Conn.get_resp_header(conn, "expect-ct") == []
+    end
 
-    assert Conn.get_resp_header(conn, "strict-transport-security") == []
-    assert Conn.get_resp_header(conn, "expect-ct") == []
-  end
+    test "it does not send STS headers when disabled", %{conn: conn} do
+      Config.put([:http_security, :sts], false)
+
+      conn = get(conn, "/api/v1/instance")
+
+      assert Conn.get_resp_header(conn, "strict-transport-security") == []
+      assert Conn.get_resp_header(conn, "expect-ct") == []
+    end
+
+    test "referrer-policy header reflects configured value", %{conn: conn} do
+      conn = get(conn, "/api/v1/instance")
+
+      assert Conn.get_resp_header(conn, "referrer-policy") == ["same-origin"]
 
-  test "referrer-policy header reflects configured value", %{conn: conn} do
-    Config.put([:http_security, :enabled], true)
+      Config.put([:http_security, :referrer_policy], "no-referrer")
 
-    conn =
-      conn
-      |> get("/api/v1/instance")
+      conn =
+        build_conn()
+        |> get("/api/v1/instance")
 
-    assert Conn.get_resp_header(conn, "referrer-policy") == ["same-origin"]
+      assert Conn.get_resp_header(conn, "referrer-policy") == ["no-referrer"]
+    end
 
-    Config.put([:http_security, :referrer_policy], "no-referrer")
+    test "it sends `report-to` & `report-uri` CSP response headers" do
+      conn =
+        build_conn()
+        |> get("/api/v1/instance")
 
-    conn =
-      build_conn()
-      |> get("/api/v1/instance")
+      [csp] = Conn.get_resp_header(conn, "content-security-policy")
 
-    assert Conn.get_resp_header(conn, "referrer-policy") == ["no-referrer"]
+      assert csp =~ ~r|report-uri https://endpoint.com; report-to csp-endpoint;|
+
+      [reply_to] = Conn.get_resp_header(conn, "reply-to")
+
+      assert reply_to ==
+               "{\"endpoints\":[{\"url\":\"https://endpoint.com\"}],\"group\":\"csp-endpoint\",\"max-age\":10886400}"
+    end
+  end
+
+  test "it does not send CSP headers when disabled", %{conn: conn} do
+    enabled = Config.get([:http_securiy, :enabled])
+
+    Config.put([:http_security, :enabled], false)
+
+    on_exit(fn ->
+      Config.put([:http_security, :enabled], enabled)
+    end)
+
+    conn = get(conn, "/api/v1/instance")
+
+    assert Conn.get_resp_header(conn, "x-xss-protection") == []
+    assert Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
+    assert Conn.get_resp_header(conn, "x-frame-options") == []
+    assert Conn.get_resp_header(conn, "x-content-type-options") == []
+    assert Conn.get_resp_header(conn, "x-download-options") == []
+    assert Conn.get_resp_header(conn, "referrer-policy") == []
+    assert Conn.get_resp_header(conn, "content-security-policy") == []
   end
 end
index 8b0b06772a6273dce9e75781ce3f7e92f3b28e27..02f5300583d7d48b101e904b1892f9e2ff81a9e4 100644 (file)
@@ -3,7 +3,7 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do
-  use Pleroma.Web.ConnCase, async: true
+  use Pleroma.Web.ConnCase
 
   alias Pleroma.Plugs.LegacyAuthenticationPlug
   alias Pleroma.User
index 5382289c7cc717003e54d424ee991e1fc49ed84e..85085a1fa0c2ab7473a9b830dfcdcef9fb350c30 100644 (file)
@@ -1,23 +1,24 @@
 defmodule Pleroma.RepoTest do
   use Pleroma.DataCase
   import Pleroma.Factory
+  alias Pleroma.User
 
   describe "find_resource/1" do
     test "returns user" do
       user = insert(:user)
-      query = from(t in Pleroma.User, where: t.id == ^user.id)
+      query = from(t in User, where: t.id == ^user.id)
       assert Repo.find_resource(query) == {:ok, user}
     end
 
     test "returns not_found" do
-      query = from(t in Pleroma.User, where: t.id == ^"9gBuXNpD2NyDmmxxdw")
+      query = from(t in User, where: t.id == ^"9gBuXNpD2NyDmmxxdw")
       assert Repo.find_resource(query) == {:error, :not_found}
     end
   end
 
   describe "get_assoc/2" do
     test "get assoc from preloaded data" do
-      user = %Pleroma.User{name: "Agent Smith"}
+      user = %User{name: "Agent Smith"}
       token = %Pleroma.Web.OAuth.Token{insert(:oauth_token) | user: user}
       assert Repo.get_assoc(token, :user) == {:ok, user}
     end
index 2a2954ad615ea0993b1479f3f81b6c697bfdc267..be6247ca4735567e40c143d2abbb7475d18c8596 100644 (file)
@@ -4,6 +4,7 @@
 
 defmodule Pleroma.Factory do
   use ExMachina.Ecto, repo: Pleroma.Repo
+  alias Pleroma.User
 
   def participation_factory do
     conversation = insert(:conversation)
@@ -23,7 +24,7 @@ defmodule Pleroma.Factory do
   end
 
   def user_factory do
-    user = %Pleroma.User{
+    user = %User{
       name: sequence(:name, &"Test ãƒ†ã‚¹ãƒˆ User #{&1}"),
       email: sequence(:email, &"user#{&1}@example.com"),
       nickname: sequence(:nickname, &"nick#{&1}"),
@@ -34,16 +35,16 @@ defmodule Pleroma.Factory do
 
     %{
       user
-      | ap_id: Pleroma.User.ap_id(user),
-        follower_address: Pleroma.User.ap_followers(user),
-        following: [Pleroma.User.ap_id(user)]
+      | ap_id: User.ap_id(user),
+        follower_address: User.ap_followers(user),
+        following: [User.ap_id(user)]
     }
   end
 
   def note_factory(attrs \\ %{}) do
     text = sequence(:text, &"This is :moominmamma: note #{&1}")
 
-    user = insert(:user)
+    user = attrs[:user] || insert(:user)
 
     data = %{
       "type" => "Note",
@@ -113,7 +114,8 @@ defmodule Pleroma.Factory do
   end
 
   def note_activity_factory(attrs \\ %{}) do
-    note = attrs[:note] || insert(:note)
+    user = attrs[:user] || insert(:user)
+    note = attrs[:note] || insert(:note, user: user)
 
     data = %{
       "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
diff --git a/test/tasks/database_test.exs b/test/tasks/database_test.exs
new file mode 100644 (file)
index 0000000..579130b
--- /dev/null
@@ -0,0 +1,49 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Pleroma.DatabaseTest do
+  alias Pleroma.Repo
+  alias Pleroma.User
+  use Pleroma.DataCase
+
+  import Pleroma.Factory
+
+  setup_all do
+    Mix.shell(Mix.Shell.Process)
+
+    on_exit(fn ->
+      Mix.shell(Mix.Shell.IO)
+    end)
+
+    :ok
+  end
+
+  describe "running update_users_following_followers_counts" do
+    test "following and followers count are updated" do
+      [user, user2] = insert_pair(:user)
+      {:ok, %User{following: following, info: info} = user} = User.follow(user, user2)
+
+      assert length(following) == 2
+      assert info.follower_count == 0
+
+      info_cng = Ecto.Changeset.change(info, %{follower_count: 3})
+
+      {:ok, user} =
+        user
+        |> Ecto.Changeset.change(%{following: following ++ following})
+        |> Ecto.Changeset.put_embed(:info, info_cng)
+        |> Repo.update()
+
+      assert length(user.following) == 4
+      assert user.info.follower_count == 3
+
+      assert :ok == Mix.Tasks.Pleroma.Database.run(["update_users_following_followers_counts"])
+
+      user = User.get_by_id(user.id)
+
+      assert length(user.following) == 2
+      assert user.info.follower_count == 0
+    end
+  end
+end
index eaf4ecf8449bfe1881a1c99cae5d729d3f8c4e8d..260ce0d954973868afe0424425a62aaff538096c 100644 (file)
@@ -3,6 +3,7 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Mix.Tasks.Pleroma.UserTest do
+  alias Pleroma.Repo
   alias Pleroma.User
   use Pleroma.DataCase
 
@@ -338,4 +339,31 @@ defmodule Mix.Tasks.Pleroma.UserTest do
       assert message == "User #{nickname} statuses deleted."
     end
   end
+
+  describe "running toggle_confirmed" do
+    test "user is confirmed" do
+      %{id: id, nickname: nickname} = insert(:user, info: %{confirmation_pending: false})
+
+      assert :ok = Mix.Tasks.Pleroma.User.run(["toggle_confirmed", nickname])
+      assert_received {:mix_shell, :info, [message]}
+      assert message == "#{nickname} needs confirmation."
+
+      user = Repo.get(User, id)
+      assert user.info.confirmation_pending
+      assert user.info.confirmation_token
+    end
+
+    test "user is not confirmed" do
+      %{id: id, nickname: nickname} =
+        insert(:user, info: %{confirmation_pending: true, confirmation_token: "some token"})
+
+      assert :ok = Mix.Tasks.Pleroma.User.run(["toggle_confirmed", nickname])
+      assert_received {:mix_shell, :info, [message]}
+      assert message == "#{nickname} doesn't need confirmation."
+
+      user = Repo.get(User, id)
+      refute user.info.confirmation_pending
+      refute user.info.confirmation_token
+    end
+  end
 end
index 0b65e89e9bdceaffe3aa86f2477935c5ac6a209a..10e463ff847a6c3e7d100bc2e340d10510a29822 100644 (file)
@@ -277,7 +277,7 @@ defmodule Pleroma.UserTest do
     end
 
     test "it restricts certain nicknames" do
-      [restricted_name | _] = Pleroma.Config.get([Pleroma.User, :restricted_nicknames])
+      [restricted_name | _] = Pleroma.Config.get([User, :restricted_nicknames])
 
       assert is_bitstring(restricted_name)
 
@@ -626,6 +626,37 @@ defmodule Pleroma.UserTest do
     end
   end
 
+  describe "remove duplicates from following list" do
+    test "it removes duplicates" do
+      user = insert(:user)
+      follower = insert(:user)
+
+      {:ok, %User{following: following} = follower} = User.follow(follower, user)
+      assert length(following) == 2
+
+      {:ok, follower} =
+        follower
+        |> User.update_changeset(%{following: following ++ following})
+        |> Repo.update()
+
+      assert length(follower.following) == 4
+
+      {:ok, follower} = User.remove_duplicated_following(follower)
+      assert length(follower.following) == 2
+    end
+
+    test "it does nothing when following is uniq" do
+      user = insert(:user)
+      follower = insert(:user)
+
+      {:ok, follower} = User.follow(follower, user)
+      assert length(follower.following) == 2
+
+      {:ok, follower} = User.remove_duplicated_following(follower)
+      assert length(follower.following) == 2
+    end
+  end
+
   describe "follow_import" do
     test "it imports user followings from list" do
       [user1, user2, user3] = insert_list(3, :user)
@@ -873,7 +904,6 @@ defmodule Pleroma.UserTest do
 
       assert [activity] ==
                ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
-               |> ActivityPub.contain_timeline(user2)
 
       {:ok, _user} = User.deactivate(user)
 
@@ -882,7 +912,6 @@ defmodule Pleroma.UserTest do
 
       assert [] ==
                ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
-               |> ActivityPub.contain_timeline(user2)
     end
   end
 
@@ -1194,14 +1223,32 @@ defmodule Pleroma.UserTest do
     follower2 = insert(:user)
     follower3 = insert(:user)
 
-    {:ok, follower} = Pleroma.User.follow(follower, user)
-    {:ok, _follower2} = Pleroma.User.follow(follower2, user)
-    {:ok, _follower3} = Pleroma.User.follow(follower3, user)
+    {:ok, follower} = User.follow(follower, user)
+    {:ok, _follower2} = User.follow(follower2, user)
+    {:ok, _follower3} = User.follow(follower3, user)
 
-    {:ok, _} = Pleroma.User.block(user, follower)
+    {:ok, _} = User.block(user, follower)
 
     user_show = Pleroma.Web.TwitterAPI.UserView.render("show.json", %{user: user})
 
     assert Map.get(user_show, "followers_count") == 2
   end
+
+  describe "toggle_confirmation/1" do
+    test "if user is confirmed" do
+      user = insert(:user, info: %{confirmation_pending: false})
+      {:ok, user} = User.toggle_confirmation(user)
+
+      assert user.info.confirmation_pending
+      assert user.info.confirmation_token
+    end
+
+    test "if user is unconfirmed" do
+      user = insert(:user, info: %{confirmation_pending: true, confirmation_token: "some token"})
+      {:ok, user} = User.toggle_confirmation(user)
+
+      refute user.info.confirmation_pending
+      refute user.info.confirmation_token
+    end
+  end
 end
index e38de388b73b4f1e8fbe9bcd4c3d8a6421abfcde..11fd3d244da0af6052944ab0b992f2a1aee359b5 100644 (file)
@@ -462,6 +462,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     refute Enum.member?(activities, activity_three.id)
   end
 
+  test "doesn't return activities from blocked domains" do
+    domain = "dogwhistle.zone"
+    domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
+    note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
+    activity = insert(:note_activity, %{note: note})
+    user = insert(:user)
+    {:ok, user} = User.block_domain(user, domain)
+
+    activities =
+      ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
+
+    refute activity in activities
+
+    followed_user = insert(:user)
+    ActivityPub.follow(user, followed_user)
+    {:ok, repeat_activity, _} = CommonAPI.repeat(activity.id, followed_user)
+
+    activities =
+      ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
+
+    refute repeat_activity in activities
+  end
+
   test "doesn't return muted activities" do
     activity_one = insert(:note_activity)
     activity_two = insert(:note_activity)
@@ -960,17 +983,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
           "in_reply_to_status_id" => private_activity_2.id
         })
 
-      activities = ActivityPub.fetch_activities([user1.ap_id | user1.following])
+      activities =
+        ActivityPub.fetch_activities([user1.ap_id | user1.following])
+        |> Enum.map(fn a -> a.id end)
 
       private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
 
-      assert [public_activity, private_activity_1, private_activity_3] == activities
+      assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
 
       assert length(activities) == 3
 
-      activities = ActivityPub.contain_timeline(activities, user1)
+      activities =
+        ActivityPub.fetch_activities([user1.ap_id | user1.following], %{"user" => user1})
+        |> Enum.map(fn a -> a.id end)
 
-      assert [public_activity, private_activity_1] == activities
+      assert [public_activity.id, private_activity_1.id] == activities
       assert length(activities) == 2
     end
   end
diff --git a/test/web/activity_pub/mrf/simple_policy_test.exs b/test/web/activity_pub/mrf/simple_policy_test.exs
new file mode 100644 (file)
index 0000000..1e05119
--- /dev/null
@@ -0,0 +1,192 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
+  use Pleroma.DataCase
+  import Pleroma.Factory
+  alias Pleroma.Config
+  alias Pleroma.Web.ActivityPub.MRF.SimplePolicy
+
+  setup do
+    orig = Config.get!(:mrf_simple)
+
+    Config.put(:mrf_simple,
+      media_removal: [],
+      media_nsfw: [],
+      federated_timeline_removal: [],
+      reject: [],
+      accept: []
+    )
+
+    on_exit(fn ->
+      Config.put(:mrf_simple, orig)
+    end)
+  end
+
+  describe "when :media_removal" do
+    test "is empty" do
+      Config.put([:mrf_simple, :media_removal], [])
+      media_message = build_media_message()
+      local_message = build_local_message()
+
+      assert SimplePolicy.filter(media_message) == {:ok, media_message}
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+    end
+
+    test "has a matching host" do
+      Config.put([:mrf_simple, :media_removal], ["remote.instance"])
+      media_message = build_media_message()
+      local_message = build_local_message()
+
+      assert SimplePolicy.filter(media_message) ==
+               {:ok,
+                media_message
+                |> Map.put("object", Map.delete(media_message["object"], "attachment"))}
+
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+    end
+  end
+
+  describe "when :media_nsfw" do
+    test "is empty" do
+      Config.put([:mrf_simple, :media_nsfw], [])
+      media_message = build_media_message()
+      local_message = build_local_message()
+
+      assert SimplePolicy.filter(media_message) == {:ok, media_message}
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+    end
+
+    test "has a matching host" do
+      Config.put([:mrf_simple, :media_nsfw], ["remote.instance"])
+      media_message = build_media_message()
+      local_message = build_local_message()
+
+      assert SimplePolicy.filter(media_message) ==
+               {:ok,
+                media_message
+                |> put_in(["object", "tag"], ["foo", "nsfw"])
+                |> put_in(["object", "sensitive"], true)}
+
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+    end
+  end
+
+  defp build_media_message do
+    %{
+      "actor" => "https://remote.instance/users/bob",
+      "type" => "Create",
+      "object" => %{
+        "attachment" => [%{}],
+        "tag" => ["foo"],
+        "sensitive" => false
+      }
+    }
+  end
+
+  describe "when :federated_timeline_removal" do
+    test "is empty" do
+      Config.put([:mrf_simple, :federated_timeline_removal], [])
+      {_, ftl_message} = build_ftl_actor_and_message()
+      local_message = build_local_message()
+
+      assert SimplePolicy.filter(ftl_message) == {:ok, ftl_message}
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+    end
+
+    test "has a matching host" do
+      {actor, ftl_message} = build_ftl_actor_and_message()
+
+      ftl_message_actor_host =
+        ftl_message
+        |> Map.fetch!("actor")
+        |> URI.parse()
+        |> Map.fetch!(:host)
+
+      Config.put([:mrf_simple, :federated_timeline_removal], [ftl_message_actor_host])
+      local_message = build_local_message()
+
+      assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
+      assert actor.follower_address in ftl_message["to"]
+      refute actor.follower_address in ftl_message["cc"]
+      refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
+      assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"]
+
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+    end
+  end
+
+  defp build_ftl_actor_and_message do
+    actor = insert(:user)
+
+    {actor,
+     %{
+       "actor" => actor.ap_id,
+       "to" => ["https://www.w3.org/ns/activitystreams#Public", "http://foo.bar/baz"],
+       "cc" => [actor.follower_address, "http://foo.bar/qux"]
+     }}
+  end
+
+  describe "when :reject" do
+    test "is empty" do
+      Config.put([:mrf_simple, :reject], [])
+
+      remote_message = build_remote_message()
+
+      assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
+    end
+
+    test "has a matching host" do
+      Config.put([:mrf_simple, :reject], ["remote.instance"])
+
+      remote_message = build_remote_message()
+
+      assert SimplePolicy.filter(remote_message) == {:reject, nil}
+    end
+  end
+
+  describe "when :accept" do
+    test "is empty" do
+      Config.put([:mrf_simple, :accept], [])
+
+      local_message = build_local_message()
+      remote_message = build_remote_message()
+
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+      assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
+    end
+
+    test "is not empty but it doesn't have a matching host" do
+      Config.put([:mrf_simple, :accept], ["non.matching.remote"])
+
+      local_message = build_local_message()
+      remote_message = build_remote_message()
+
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+      assert SimplePolicy.filter(remote_message) == {:reject, nil}
+    end
+
+    test "has a matching host" do
+      Config.put([:mrf_simple, :accept], ["remote.instance"])
+
+      local_message = build_local_message()
+      remote_message = build_remote_message()
+
+      assert SimplePolicy.filter(local_message) == {:ok, local_message}
+      assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
+    end
+  end
+
+  defp build_local_message do
+    %{
+      "actor" => "#{Pleroma.Web.base_url()}/users/alice",
+      "to" => [],
+      "cc" => []
+    }
+  end
+
+  defp build_remote_message do
+    %{"actor" => "https://remote.instance/users/bob"}
+  end
+end
index 9c03c8be2ecf0c43ae89a0856282bda591cfe571..e2584f635f881e15d8c12a2cd59c7cadb76f85f9 100644 (file)
@@ -96,6 +96,16 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
     refute Visibility.visible_for_user?(direct, unrelated)
   end
 
+  test "doesn't die when the user doesn't exist",
+       %{
+         direct: direct,
+         user: user
+       } do
+    Repo.delete(user)
+    Cachex.clear(:user_cache)
+    refute Visibility.is_private?(direct)
+  end
+
   test "get_visibility", %{
     public: public,
     private: private,
index 6c1897b5a9354336d00fe2df857abf0a68ff0331..ca12c72153931131f93c853128d95208a596a873 100644 (file)
@@ -5,8 +5,10 @@
 defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
   use Pleroma.Web.ConnCase
 
+  alias Pleroma.Activity
   alias Pleroma.User
   alias Pleroma.UserInviteToken
+  alias Pleroma.Web.CommonAPI
   import Pleroma.Factory
 
   describe "/api/pleroma/admin/users" do
@@ -949,4 +951,329 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
              }
     end
   end
+
+  describe "GET /api/pleroma/admin/reports/:id" do
+    setup %{conn: conn} do
+      admin = insert(:user, info: %{is_admin: true})
+
+      %{conn: assign(conn, :user, admin)}
+    end
+
+    test "returns report by its id", %{conn: conn} do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          "account_id" => target_user.id,
+          "comment" => "I feel offended",
+          "status_ids" => [activity.id]
+        })
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports/#{report_id}")
+        |> json_response(:ok)
+
+      assert response["id"] == report_id
+    end
+
+    test "returns 404 when report id is invalid", %{conn: conn} do
+      conn = get(conn, "/api/pleroma/admin/reports/test")
+
+      assert json_response(conn, :not_found) == "Not found"
+    end
+  end
+
+  describe "PUT /api/pleroma/admin/reports/:id" do
+    setup %{conn: conn} do
+      admin = insert(:user, info: %{is_admin: true})
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          "account_id" => target_user.id,
+          "comment" => "I feel offended",
+          "status_ids" => [activity.id]
+        })
+
+      %{conn: assign(conn, :user, admin), id: report_id}
+    end
+
+    test "mark report as resolved", %{conn: conn, id: id} do
+      response =
+        conn
+        |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "resolved"})
+        |> json_response(:ok)
+
+      assert response["state"] == "resolved"
+    end
+
+    test "closes report", %{conn: conn, id: id} do
+      response =
+        conn
+        |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "closed"})
+        |> json_response(:ok)
+
+      assert response["state"] == "closed"
+    end
+
+    test "returns 400 when state is unknown", %{conn: conn, id: id} do
+      conn =
+        conn
+        |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "test"})
+
+      assert json_response(conn, :bad_request) == "Unsupported state"
+    end
+
+    test "returns 404 when report is not exist", %{conn: conn} do
+      conn =
+        conn
+        |> put("/api/pleroma/admin/reports/test", %{"state" => "closed"})
+
+      assert json_response(conn, :not_found) == "Not found"
+    end
+  end
+
+  describe "GET /api/pleroma/admin/reports" do
+    setup %{conn: conn} do
+      admin = insert(:user, info: %{is_admin: true})
+
+      %{conn: assign(conn, :user, admin)}
+    end
+
+    test "returns empty response when no reports created", %{conn: conn} do
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports")
+        |> json_response(:ok)
+
+      assert Enum.empty?(response["reports"])
+    end
+
+    test "returns reports", %{conn: conn} do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          "account_id" => target_user.id,
+          "comment" => "I feel offended",
+          "status_ids" => [activity.id]
+        })
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports")
+        |> json_response(:ok)
+
+      [report] = response["reports"]
+
+      assert length(response["reports"]) == 1
+      assert report["id"] == report_id
+    end
+
+    test "returns reports with specified state", %{conn: conn} do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: first_report_id}} =
+        CommonAPI.report(reporter, %{
+          "account_id" => target_user.id,
+          "comment" => "I feel offended",
+          "status_ids" => [activity.id]
+        })
+
+      {:ok, %{id: second_report_id}} =
+        CommonAPI.report(reporter, %{
+          "account_id" => target_user.id,
+          "comment" => "I don't like this user"
+        })
+
+      CommonAPI.update_report_state(second_report_id, "closed")
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports", %{
+          "state" => "open"
+        })
+        |> json_response(:ok)
+
+      [open_report] = response["reports"]
+
+      assert length(response["reports"]) == 1
+      assert open_report["id"] == first_report_id
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports", %{
+          "state" => "closed"
+        })
+        |> json_response(:ok)
+
+      [closed_report] = response["reports"]
+
+      assert length(response["reports"]) == 1
+      assert closed_report["id"] == second_report_id
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports", %{
+          "state" => "resolved"
+        })
+        |> json_response(:ok)
+
+      assert Enum.empty?(response["reports"])
+    end
+
+    test "returns 403 when requested by a non-admin" do
+      user = insert(:user)
+
+      conn =
+        build_conn()
+        |> assign(:user, user)
+        |> get("/api/pleroma/admin/reports")
+
+      assert json_response(conn, :forbidden) == %{"error" => "User is not admin."}
+    end
+
+    test "returns 403 when requested by anonymous" do
+      conn =
+        build_conn()
+        |> get("/api/pleroma/admin/reports")
+
+      assert json_response(conn, :forbidden) == %{"error" => "Invalid credentials."}
+    end
+  end
+
+  describe "POST /api/pleroma/admin/reports/:id/respond" do
+    setup %{conn: conn} do
+      admin = insert(:user, info: %{is_admin: true})
+
+      %{conn: assign(conn, :user, admin)}
+    end
+
+    test "returns created dm", %{conn: conn} do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          "account_id" => target_user.id,
+          "comment" => "I feel offended",
+          "status_ids" => [activity.id]
+        })
+
+      response =
+        conn
+        |> post("/api/pleroma/admin/reports/#{report_id}/respond", %{
+          "status" => "I will check it out"
+        })
+        |> json_response(:ok)
+
+      recipients = Enum.map(response["mentions"], & &1["username"])
+
+      assert conn.assigns[:user].nickname in recipients
+      assert reporter.nickname in recipients
+      assert response["content"] == "I will check it out"
+      assert response["visibility"] == "direct"
+    end
+
+    test "returns 400 when status is missing", %{conn: conn} do
+      conn = post(conn, "/api/pleroma/admin/reports/test/respond")
+
+      assert json_response(conn, :bad_request) == "Invalid parameters"
+    end
+
+    test "returns 404 when report id is invalid", %{conn: conn} do
+      conn =
+        post(conn, "/api/pleroma/admin/reports/test/respond", %{
+          "status" => "foo"
+        })
+
+      assert json_response(conn, :not_found) == "Not found"
+    end
+  end
+
+  describe "PUT /api/pleroma/admin/statuses/:id" do
+    setup %{conn: conn} do
+      admin = insert(:user, info: %{is_admin: true})
+      activity = insert(:note_activity)
+
+      %{conn: assign(conn, :user, admin), id: activity.id}
+    end
+
+    test "toggle sensitive flag", %{conn: conn, id: id} do
+      response =
+        conn
+        |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "true"})
+        |> json_response(:ok)
+
+      assert response["sensitive"]
+
+      response =
+        conn
+        |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "false"})
+        |> json_response(:ok)
+
+      refute response["sensitive"]
+    end
+
+    test "change visibility flag", %{conn: conn, id: id} do
+      response =
+        conn
+        |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "public"})
+        |> json_response(:ok)
+
+      assert response["visibility"] == "public"
+
+      response =
+        conn
+        |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "private"})
+        |> json_response(:ok)
+
+      assert response["visibility"] == "private"
+
+      response =
+        conn
+        |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "unlisted"})
+        |> json_response(:ok)
+
+      assert response["visibility"] == "unlisted"
+    end
+
+    test "returns 400 when visibility is unknown", %{conn: conn, id: id} do
+      conn =
+        conn
+        |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "test"})
+
+      assert json_response(conn, :bad_request) == "Unsupported visibility"
+    end
+  end
+
+  describe "DELETE /api/pleroma/admin/statuses/:id" do
+    setup %{conn: conn} do
+      admin = insert(:user, info: %{is_admin: true})
+      activity = insert(:note_activity)
+
+      %{conn: assign(conn, :user, admin), id: activity.id}
+    end
+
+    test "deletes status", %{conn: conn, id: id} do
+      conn
+      |> delete("/api/pleroma/admin/statuses/#{id}")
+      |> json_response(:ok)
+
+      refute Activity.get_by_id(id)
+    end
+
+    test "returns error when status is not exist", %{conn: conn} do
+      conn =
+        conn
+        |> delete("/api/pleroma/admin/statuses/test")
+
+      assert json_response(conn, :bad_request) == "Could not delete"
+    end
+  end
 end
index 84744e5af7ab9b6c3404311e9c47c1ca841c68a4..58305d09b7ba0ded8bcb0f6f442fd516450ff420 100644 (file)
@@ -272,10 +272,41 @@ defmodule Pleroma.Web.CommonAPITest do
                data: %{
                  "type" => "Flag",
                  "content" => ^comment,
-                 "object" => [^target_ap_id, ^activity_ap_id]
+                 "object" => [^target_ap_id, ^activity_ap_id],
+                 "state" => "open"
                }
              } = flag_activity
     end
+
+    test "updates report state" do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %Activity{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          "account_id" => target_user.id,
+          "comment" => "I feel offended",
+          "status_ids" => [activity.id]
+        })
+
+      {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
+
+      assert report.data["state"] == "resolved"
+    end
+
+    test "does not update report state when state is unsupported" do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %Activity{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          "account_id" => target_user.id,
+          "comment" => "I feel offended",
+          "status_ids" => [activity.id]
+        })
+
+      assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
+    end
   end
 
   describe "reblog muting" do
@@ -290,14 +321,14 @@ defmodule Pleroma.Web.CommonAPITest do
     test "add a reblog mute", %{muter: muter, muted: muted} do
       {:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
 
-      assert Pleroma.User.showing_reblogs?(muter, muted) == false
+      assert User.showing_reblogs?(muter, muted) == false
     end
 
     test "remove a reblog mute", %{muter: muter, muted: muted} do
       {:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
       {:ok, muter} = CommonAPI.show_reblogs(muter, muted)
 
-      assert Pleroma.User.showing_reblogs?(muter, muted) == true
+      assert User.showing_reblogs?(muter, muted) == true
     end
   end
 end
index 40e7739e71a03bd03114473bb7d8150fe4e529f2..cbff141c84244604acce2b06677bbe57d3761bcf 100644 (file)
@@ -446,7 +446,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
   end
 
   test "verify_credentials default scope unlisted", %{conn: conn} do
-    user = insert(:user, %{info: %Pleroma.User.Info{default_scope: "unlisted"}})
+    user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}})
 
     conn =
       conn
@@ -1322,7 +1322,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
 
   describe "locked accounts" do
     test "/api/v1/follow_requests works" do
-      user = insert(:user, %{info: %Pleroma.User.Info{locked: true}})
+      user = insert(:user, %{info: %User.Info{locked: true}})
       other_user = insert(:user)
 
       {:ok, _activity} = ActivityPub.follow(other_user, user)
@@ -1367,7 +1367,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     end
 
     test "verify_credentials", %{conn: conn} do
-      user = insert(:user, %{info: %Pleroma.User.Info{default_scope: "private"}})
+      user = insert(:user, %{info: %User.Info{default_scope: "private"}})
 
       conn =
         conn
@@ -1379,7 +1379,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     end
 
     test "/api/v1/follow_requests/:id/reject works" do
-      user = insert(:user, %{info: %Pleroma.User.Info{locked: true}})
+      user = insert(:user, %{info: %User.Info{locked: true}})
       other_user = insert(:user)
 
       {:ok, _activity} = ActivityPub.follow(other_user, user)
@@ -2129,7 +2129,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
         |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
         |> json_response(:ok)
 
-      assert length(anonymous_response) == 0
+      assert Enum.empty?(anonymous_response)
     end
 
     test "does not return others' favorited DM when user is not one of recipients", %{
@@ -2153,7 +2153,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
         |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
         |> json_response(:ok)
 
-      assert length(response) == 0
+      assert Enum.empty?(response)
     end
 
     test "paginates favorites using since_id and max_id", %{
index 2916caf8d937fcb209439e6a789943ad665d56a6..f6be16862e34bb6864b2780390b1fa0625176189 100644 (file)
@@ -355,7 +355,7 @@ defmodule Pleroma.Web.OStatusTest do
 
       {:ok, user} = OStatus.find_or_make_user(uri)
 
-      user = Pleroma.User.get_cached_by_id(user.id)
+      user = User.get_cached_by_id(user.id)
       assert user.name == "Constance Variable"
       assert user.nickname == "lambadalambda@social.heldscal.la"
       assert user.local == false
@@ -374,7 +374,7 @@ defmodule Pleroma.Web.OStatusTest do
       {:ok, user} = OStatus.find_or_make_user(uri)
 
       assert user.info ==
-               %Pleroma.User.Info{
+               %User.Info{
                  id: user.info.id,
                  ap_enabled: false,
                  background: %{},
@@ -407,7 +407,7 @@ defmodule Pleroma.Web.OStatusTest do
       {:ok, user} = OStatus.find_or_make_user(uri)
       old_name = user.name
       old_bio = user.bio
-      change = Ecto.Changeset.change(user, %{avatar: nil, bio: nil, old_name: nil})
+      change = Ecto.Changeset.change(user, %{avatar: nil, bio: nil, name: nil})
 
       {:ok, user} = Repo.update(change)
       refute user.avatar