Merge remote-tracking branch 'pleroma/develop' into feature/addressable-lists
This commit is contained in:
commit
557f0e33a7
|
@ -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.
|
- A [job queue](https://git.pleroma.social/pleroma/pleroma_job_queue) for federation, emails, web push, etc.
|
||||||
- [Prometheus](https://prometheus.io/) metrics
|
- [Prometheus](https://prometheus.io/) metrics
|
||||||
- Support for Mastodon's remote interaction
|
- 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 remove_embedded_objects`
|
||||||
|
- Mix Tasks: `mix pleroma.database update_users_following_followers_counts`
|
||||||
|
- Mix Tasks: `mix pleroma.user toggle_confirmed`
|
||||||
- Federation: Support for reports
|
- Federation: Support for reports
|
||||||
- Configuration: `safe_dm_mentions` option
|
- Configuration: `safe_dm_mentions` option
|
||||||
- Configuration: `link_name` option
|
- Configuration: `link_name` option
|
||||||
- Configuration: `fetch_initial_posts` option
|
- Configuration: `fetch_initial_posts` option
|
||||||
- Configuration: `notify_email` option
|
- Configuration: `notify_email` option
|
||||||
- Configuration: Media proxy `whitelist` option
|
- Configuration: Media proxy `whitelist` option
|
||||||
|
- Configuration: `report_uri` option
|
||||||
- Pleroma API: User subscriptions
|
- Pleroma API: User subscriptions
|
||||||
- Pleroma API: Healthcheck endpoint
|
- Pleroma API: Healthcheck endpoint
|
||||||
- Admin API: Endpoints for listing/revoking invite tokens
|
- Admin API: Endpoints for listing/revoking invite tokens
|
||||||
- Admin API: Endpoints for making users follow/unfollow each other
|
- Admin API: Endpoints for making users follow/unfollow each other
|
||||||
- Admin API: added filters (role, tags, email, name) for users endpoint
|
- 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/
|
- 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: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/)
|
||||||
- Mastodon API: `/api/v1/notifications/destroy_multiple` (glitch-soc extension)
|
- Mastodon API: `/api/v1/notifications/destroy_multiple` (glitch-soc extension)
|
||||||
|
|
|
@ -48,7 +48,8 @@
|
||||||
|
|
||||||
config :pleroma, Pleroma.Repo,
|
config :pleroma, Pleroma.Repo,
|
||||||
types: Pleroma.PostgresTypes,
|
types: Pleroma.PostgresTypes,
|
||||||
telemetry_event: [Pleroma.Repo.Instrumenter]
|
telemetry_event: [Pleroma.Repo.Instrumenter],
|
||||||
|
migration_lock: nil
|
||||||
|
|
||||||
config :pleroma, Pleroma.Captcha,
|
config :pleroma, Pleroma.Captcha,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
|
|
@ -61,6 +61,8 @@
|
||||||
|
|
||||||
config :pleroma, :app_account_creation, max_requests: 5
|
config :pleroma, :app_account_creation, max_requests: 5
|
||||||
|
|
||||||
|
config :pleroma, :http_security, report_uri: "https://endpoint.com"
|
||||||
|
|
||||||
try do
|
try do
|
||||||
import_config "test.secret.exs"
|
import_config "test.secret.exs"
|
||||||
rescue
|
rescue
|
||||||
|
|
|
@ -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`
|
- 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:
|
- Response:
|
||||||
|
|
||||||
```JSON
|
```json
|
||||||
{
|
{
|
||||||
"page_size": integer,
|
"page_size": integer,
|
||||||
"count": integer,
|
"count": integer,
|
||||||
|
@ -92,7 +92,7 @@ Authentication is required and the user must be an admin.
|
||||||
- `nickname`
|
- `nickname`
|
||||||
- Response: User’s object
|
- Response: User’s object
|
||||||
|
|
||||||
```JSON
|
```json
|
||||||
{
|
{
|
||||||
"deactivated": bool,
|
"deactivated": bool,
|
||||||
"id": integer,
|
"id": integer,
|
||||||
|
@ -124,7 +124,7 @@ Authentication is required and the user must be an admin.
|
||||||
- Params: none
|
- Params: none
|
||||||
- Response:
|
- Response:
|
||||||
|
|
||||||
```JSON
|
```json
|
||||||
{
|
{
|
||||||
"is_moderator": bool,
|
"is_moderator": bool,
|
||||||
"is_admin": bool
|
"is_admin": bool
|
||||||
|
@ -141,7 +141,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
- Params: none
|
- Params: none
|
||||||
- Response:
|
- Response:
|
||||||
|
|
||||||
```JSON
|
```json
|
||||||
{
|
{
|
||||||
"is_moderator": bool,
|
"is_moderator": bool,
|
||||||
"is_admin": bool
|
"is_admin": bool
|
||||||
|
@ -223,7 +223,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
- Params: none
|
- Params: none
|
||||||
- Response:
|
- Response:
|
||||||
|
|
||||||
```JSON
|
```json
|
||||||
{
|
{
|
||||||
|
|
||||||
"invites": [
|
"invites": [
|
||||||
|
@ -250,7 +250,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
- `token`
|
- `token`
|
||||||
- Response:
|
- Response:
|
||||||
|
|
||||||
```JSON
|
```json
|
||||||
{
|
{
|
||||||
"id": integer,
|
"id": integer,
|
||||||
"token": string,
|
"token": string,
|
||||||
|
@ -280,3 +280,280 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
- Methods: `GET`
|
- Methods: `GET`
|
||||||
- Params: none
|
- Params: none
|
||||||
- Response: password reset token (base64 string)
|
- 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 `{}`
|
||||||
|
|
|
@ -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``: Whether to additionally send a `Strict-Transport-Security` header
|
||||||
* ``sts_max_age``: The maximum age for the `Strict-Transport-Security` header if sent
|
* ``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
|
* ``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
|
## :mrf_user_allowlist
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
defmodule Mix.Tasks.Pleroma.Database do
|
defmodule Mix.Tasks.Pleroma.Database do
|
||||||
alias Mix.Tasks.Pleroma.Common
|
alias Mix.Tasks.Pleroma.Common
|
||||||
|
alias Pleroma.Conversation
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
require Logger
|
require Logger
|
||||||
use Mix.Task
|
use Mix.Task
|
||||||
|
|
||||||
|
@ -19,6 +22,14 @@ defmodule Mix.Tasks.Pleroma.Database do
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- `--vacuum` - run `VACUUM FULL` after the embedded objects are replaced with their references
|
- `--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
|
def run(["remove_embedded_objects" | args]) do
|
||||||
{options, [], []} =
|
{options, [], []} =
|
||||||
|
@ -32,7 +43,7 @@ def run(["remove_embedded_objects" | args]) do
|
||||||
Common.start_pleroma()
|
Common.start_pleroma()
|
||||||
Logger.info("Removing embedded objects")
|
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;",
|
"update activities set data = jsonb_set(data, '{object}'::text[], data->'object'->'id') where data->'object'->>'id' is not null;",
|
||||||
[],
|
[],
|
||||||
timeout: :infinity
|
timeout: :infinity
|
||||||
|
@ -41,11 +52,24 @@ def run(["remove_embedded_objects" | args]) do
|
||||||
if Keyword.get(options, :vacuum) do
|
if Keyword.get(options, :vacuum) do
|
||||||
Logger.info("Runnning VACUUM FULL")
|
Logger.info("Runnning VACUUM FULL")
|
||||||
|
|
||||||
Pleroma.Repo.query!(
|
Repo.query!(
|
||||||
"vacuum full;",
|
"vacuum full;",
|
||||||
[],
|
[],
|
||||||
timeout: :infinity
|
timeout: :infinity
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
@ -77,6 +77,10 @@ defmodule Mix.Tasks.Pleroma.User do
|
||||||
## Delete tags from a user.
|
## Delete tags from a user.
|
||||||
|
|
||||||
mix pleroma.user untag NICKNAME TAGS
|
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
|
def run(["new", nickname, email | rest]) do
|
||||||
{options, [], []} =
|
{options, [], []} =
|
||||||
|
@ -388,6 +392,21 @@ def run(["delete_activities", nickname]) do
|
||||||
end
|
end
|
||||||
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
|
defp set_moderator(user, value) do
|
||||||
info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value})
|
info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value})
|
||||||
|
|
||||||
|
|
|
@ -60,21 +60,24 @@ defmodule Pleroma.Activity do
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_preloaded_object(query) do
|
def with_joined_object(query) do
|
||||||
query
|
join(query, :inner, [activity], o in Object,
|
||||||
|> join(
|
|
||||||
:inner,
|
|
||||||
[activity],
|
|
||||||
o in Object,
|
|
||||||
on:
|
on:
|
||||||
fragment(
|
fragment(
|
||||||
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
|
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
|
||||||
o.data,
|
o.data,
|
||||||
activity.data,
|
activity.data,
|
||||||
activity.data
|
activity.data
|
||||||
|
),
|
||||||
|
as: :object
|
||||||
)
|
)
|
||||||
)
|
end
|
||||||
|> preload([activity, object], object: object)
|
|
||||||
|
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
|
end
|
||||||
|
|
||||||
def with_preloaded_bookmark(query, %User{} = user) do
|
def with_preloaded_bookmark(query, %User{} = user) do
|
||||||
|
@ -108,7 +111,7 @@ def get_bookmark(_, _), do: nil
|
||||||
|
|
||||||
def change(struct, params \\ %{}) do
|
def change(struct, params \\ %{}) do
|
||||||
struct
|
struct
|
||||||
|> cast(params, [:data])
|
|> cast(params, [:data, :recipients])
|
||||||
|> validate_required([:data])
|
|> validate_required([:data])
|
||||||
|> unique_constraint(:ap_id, name: :activities_unique_apid_index)
|
|> unique_constraint(:ap_id, name: :activities_unique_apid_index)
|
||||||
end
|
end
|
||||||
|
|
|
@ -95,7 +95,6 @@ def handle_command(state, "home") do
|
||||||
activities =
|
activities =
|
||||||
[user.ap_id | user.following]
|
[user.ap_id | user.following]
|
||||||
|> ActivityPub.fetch_activities(params)
|
|> ActivityPub.fetch_activities(params)
|
||||||
|> ActivityPub.contain_timeline(user)
|
|
||||||
|
|
||||||
Enum.each(activities, fn activity ->
|
Enum.each(activities, fn activity ->
|
||||||
puts_activity(activity)
|
puts_activity(activity)
|
||||||
|
|
|
@ -45,7 +45,7 @@ def get_for_ap_id(ap_id) do
|
||||||
2. Create a participation for all the people involved who don't have one already
|
2. Create a participation for all the people involved who don't have one already
|
||||||
3. Bump all relevant participations to 'unread'
|
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),
|
with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity),
|
||||||
"Create" <- activity.data["type"],
|
"Create" <- activity.data["type"],
|
||||||
object <- Pleroma.Object.normalize(activity),
|
object <- Pleroma.Object.normalize(activity),
|
||||||
|
@ -58,7 +58,7 @@ def create_or_bump_for(activity) do
|
||||||
participations =
|
participations =
|
||||||
Enum.map(users, fn user ->
|
Enum.map(users, fn user ->
|
||||||
{:ok, participation} =
|
{:ok, participation} =
|
||||||
Participation.create_for_user_and_conversation(user, conversation)
|
Participation.create_for_user_and_conversation(user, conversation, opts)
|
||||||
|
|
||||||
participation
|
participation
|
||||||
end)
|
end)
|
||||||
|
@ -72,4 +72,21 @@ def create_or_bump_for(activity) do
|
||||||
e -> {:error, e}
|
e -> {:error, e}
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
@ -22,15 +22,17 @@ defmodule Pleroma.Conversation.Participation do
|
||||||
|
|
||||||
def creation_cng(struct, params) do
|
def creation_cng(struct, params) do
|
||||||
struct
|
struct
|
||||||
|> cast(params, [:user_id, :conversation_id])
|
|> cast(params, [:user_id, :conversation_id, :read])
|
||||||
|> validate_required([:user_id, :conversation_id])
|
|> validate_required([:user_id, :conversation_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_for_user_and_conversation(user, conversation) do
|
def create_for_user_and_conversation(user, conversation, opts \\ []) do
|
||||||
|
read = !!opts[:read]
|
||||||
|
|
||||||
%__MODULE__{}
|
%__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(
|
|> Repo.insert(
|
||||||
on_conflict: [set: [read: false, updated_at: NaiveDateTime.utc_now()]],
|
on_conflict: [set: [read: read, updated_at: NaiveDateTime.utc_now()]],
|
||||||
returning: true,
|
returning: true,
|
||||||
conflict_target: [:user_id, :conversation_id]
|
conflict_target: [:user_id, :conversation_id]
|
||||||
)
|
)
|
||||||
|
|
|
@ -29,7 +29,7 @@ def report(to, reporter, account, statuses, comment) do
|
||||||
end
|
end
|
||||||
|
|
||||||
statuses_html =
|
statuses_html =
|
||||||
if length(statuses) > 0 do
|
if is_list(statuses) && length(statuses) > 0 do
|
||||||
statuses_list_html =
|
statuses_list_html =
|
||||||
statuses
|
statuses
|
||||||
|> Enum.map(fn
|
|> Enum.map(fn
|
||||||
|
|
|
@ -38,7 +38,8 @@ def get_filters(%User{id: user_id} = _user) do
|
||||||
query =
|
query =
|
||||||
from(
|
from(
|
||||||
f in Pleroma.Filter,
|
f in Pleroma.Filter,
|
||||||
where: f.user_id == ^user_id
|
where: f.user_id == ^user_id,
|
||||||
|
order_by: [desc: :id]
|
||||||
)
|
)
|
||||||
|
|
||||||
Repo.all(query)
|
Repo.all(query)
|
||||||
|
|
|
@ -20,8 +20,9 @@ def call(conn, _options) do
|
||||||
|
|
||||||
defp headers do
|
defp headers do
|
||||||
referrer_policy = Config.get([:http_security, :referrer_policy])
|
referrer_policy = Config.get([:http_security, :referrer_policy])
|
||||||
|
report_uri = Config.get([:http_security, :report_uri])
|
||||||
|
|
||||||
[
|
headers = [
|
||||||
{"x-xss-protection", "1; mode=block"},
|
{"x-xss-protection", "1; mode=block"},
|
||||||
{"x-permitted-cross-domain-policies", "none"},
|
{"x-permitted-cross-domain-policies", "none"},
|
||||||
{"x-frame-options", "DENY"},
|
{"x-frame-options", "DENY"},
|
||||||
|
@ -30,12 +31,27 @@ defp headers do
|
||||||
{"x-download-options", "noopen"},
|
{"x-download-options", "noopen"},
|
||||||
{"content-security-policy", csp_string() <> ";"}
|
{"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
|
end
|
||||||
|
|
||||||
defp csp_string do
|
defp csp_string do
|
||||||
scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
|
scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
|
||||||
static_url = Pleroma.Web.Endpoint.static_url()
|
static_url = Pleroma.Web.Endpoint.static_url()
|
||||||
websocket_url = Pleroma.Web.Endpoint.websocket_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}"
|
connect_src = "connect-src 'self' #{static_url} #{websocket_url}"
|
||||||
|
|
||||||
|
@ -53,7 +69,7 @@ defp csp_string do
|
||||||
"script-src 'self'"
|
"script-src 'self'"
|
||||||
end
|
end
|
||||||
|
|
||||||
[
|
main_part = [
|
||||||
"default-src 'none'",
|
"default-src 'none'",
|
||||||
"base-uri 'self'",
|
"base-uri 'self'",
|
||||||
"frame-ancestors 'none'",
|
"frame-ancestors 'none'",
|
||||||
|
@ -63,11 +79,14 @@ defp csp_string do
|
||||||
"font-src 'self'",
|
"font-src 'self'",
|
||||||
"manifest-src 'self'",
|
"manifest-src 'self'",
|
||||||
connect_src,
|
connect_src,
|
||||||
script_src,
|
script_src
|
||||||
if scheme == "https" do
|
|
||||||
"upgrade-insecure-requests"
|
|
||||||
end
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
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("; ")
|
|> Enum.join("; ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ defmodule Pleroma.User do
|
||||||
field(:last_refreshed_at, :naive_datetime_usec)
|
field(:last_refreshed_at, :naive_datetime_usec)
|
||||||
has_many(:notifications, Notification)
|
has_many(:notifications, Notification)
|
||||||
has_many(:registrations, Registration)
|
has_many(:registrations, Registration)
|
||||||
embeds_one(:info, Pleroma.User.Info)
|
embeds_one(:info, User.Info)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
@ -166,7 +166,7 @@ def remote_user_creation(params) do
|
||||||
|
|
||||||
def update_changeset(struct, params \\ %{}) do
|
def update_changeset(struct, params \\ %{}) do
|
||||||
struct
|
struct
|
||||||
|> cast(params, [:bio, :name, :avatar])
|
|> cast(params, [:bio, :name, :avatar, :following])
|
||||||
|> unique_constraint(:nickname)
|
|> unique_constraint(:nickname)
|
||||||
|> validate_format(:nickname, local_nickname_regex())
|
|> validate_format(:nickname, local_nickname_regex())
|
||||||
|> validate_length(:bio, max: 5000)
|
|> validate_length(:bio, max: 5000)
|
||||||
|
@ -233,7 +233,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||||
|> validate_confirmation(:password)
|
|> validate_confirmation(:password)
|
||||||
|> unique_constraint(:email)
|
|> unique_constraint(:email)
|
||||||
|> unique_constraint(:nickname)
|
|> 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(:nickname, local_nickname_regex())
|
||||||
|> validate_format(:email, @email_regex)
|
|> validate_format(:email, @email_regex)
|
||||||
|> validate_length(:bio, max: 1000)
|
|> validate_length(:bio, max: 1000)
|
||||||
|
@ -278,7 +278,7 @@ def register(%Ecto.Changeset{} = changeset) do
|
||||||
with {:ok, user} <- Repo.insert(changeset),
|
with {:ok, user} <- Repo.insert(changeset),
|
||||||
{:ok, user} <- autofollow_users(user),
|
{:ok, user} <- autofollow_users(user),
|
||||||
{:ok, user} <- set_cache(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, _} <- try_send_confirmation_email(user) do
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
end
|
end
|
||||||
|
@ -709,6 +709,18 @@ def update_follower_count(%User{} = user) do
|
||||||
end
|
end
|
||||||
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()]
|
@spec get_users_from_set([String.t()], boolean()) :: [User.t()]
|
||||||
def get_users_from_set(ap_ids, local_only \\ true) do
|
def get_users_from_set(ap_ids, local_only \\ true) do
|
||||||
criteria = %{ap_id: ap_ids, deactivated: false}
|
criteria = %{ap_id: ap_ids, deactivated: false}
|
||||||
|
@ -1132,7 +1144,6 @@ def delete_user_activities(%User{ap_id: ap_id} = user) do
|
||||||
stream =
|
stream =
|
||||||
ap_id
|
ap_id
|
||||||
|> Activity.query_by_actor()
|
|> Activity.query_by_actor()
|
||||||
|> Activity.with_preloaded_object()
|
|
||||||
|> Repo.stream()
|
|> Repo.stream()
|
||||||
|
|
||||||
Repo.transaction(fn -> Enum.each(stream, &delete_activity(&1)) end, timeout: :infinity)
|
Repo.transaction(fn -> Enum.each(stream, &delete_activity(&1)) end, timeout: :infinity)
|
||||||
|
@ -1378,4 +1389,17 @@ def all_superusers do
|
||||||
def showing_reblogs?(%User{} = user, %User{} = target) do
|
def showing_reblogs?(%User{} = user, %User{} = target) do
|
||||||
target.ap_id not in user.info.muted_reblogs
|
target.ap_id not in user.info.muted_reblogs
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -212,7 +212,7 @@ def profile_update(info, params) do
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec confirmation_changeset(Info.t(), keyword()) :: Ecto.Changerset.t()
|
@spec confirmation_changeset(Info.t(), keyword()) :: Changeset.t()
|
||||||
def confirmation_changeset(info, opts) do
|
def confirmation_changeset(info, opts) do
|
||||||
need_confirmation? = Keyword.get(opts, :need_confirmation)
|
need_confirmation? = Keyword.get(opts, :need_confirmation)
|
||||||
|
|
||||||
|
|
|
@ -539,8 +539,6 @@ defp restrict_visibility(query, %{visibility: visibility})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
Ecto.Adapters.SQL.to_sql(:all, Repo, query)
|
|
||||||
|
|
||||||
query
|
query
|
||||||
else
|
else
|
||||||
Logger.error("Could not restrict visibility to #{visibility}")
|
Logger.error("Could not restrict visibility to #{visibility}")
|
||||||
|
@ -556,8 +554,6 @@ defp restrict_visibility(query, %{visibility: visibility})
|
||||||
fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
|
fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
|
||||||
)
|
)
|
||||||
|
|
||||||
Ecto.Adapters.SQL.to_sql(:all, Repo, query)
|
|
||||||
|
|
||||||
query
|
query
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -568,6 +564,18 @@ defp restrict_visibility(_query, %{visibility: visibility})
|
||||||
|
|
||||||
defp restrict_visibility(query, _visibility), do: query
|
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
|
def fetch_user_activities(user, reading_user, params \\ %{}) do
|
||||||
params =
|
params =
|
||||||
params
|
params
|
||||||
|
@ -694,6 +702,12 @@ defp restrict_type(query, %{"type" => type}) do
|
||||||
|
|
||||||
defp restrict_type(query, _), do: query
|
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
|
defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
|
||||||
from(
|
from(
|
||||||
activity in query,
|
activity in query,
|
||||||
|
@ -749,8 +763,11 @@ defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
|
||||||
blocks = info.blocks || []
|
blocks = info.blocks || []
|
||||||
domain_blocks = info.domain_blocks || []
|
domain_blocks = info.domain_blocks || []
|
||||||
|
|
||||||
|
query =
|
||||||
|
if has_named_binding?(query, :object), do: query, else: Activity.with_joined_object(query)
|
||||||
|
|
||||||
from(
|
from(
|
||||||
activity in query,
|
[activity, object: o] in query,
|
||||||
where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
|
where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
|
||||||
where: fragment("not (? && ?)", activity.recipients, ^blocks),
|
where: fragment("not (? && ?)", activity.recipients, ^blocks),
|
||||||
where:
|
where:
|
||||||
|
@ -760,7 +777,8 @@ defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
|
||||||
activity.data,
|
activity.data,
|
||||||
^blocks
|
^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
|
end
|
||||||
|
|
||||||
|
@ -840,11 +858,13 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
||||||
|> restrict_local(opts)
|
|> restrict_local(opts)
|
||||||
|> restrict_actor(opts)
|
|> restrict_actor(opts)
|
||||||
|> restrict_type(opts)
|
|> restrict_type(opts)
|
||||||
|
|> restrict_state(opts)
|
||||||
|> restrict_favorited_by(opts)
|
|> restrict_favorited_by(opts)
|
||||||
|> restrict_blocked(opts)
|
|> restrict_blocked(opts)
|
||||||
|> restrict_muted(opts)
|
|> restrict_muted(opts)
|
||||||
|> restrict_media(opts)
|
|> restrict_media(opts)
|
||||||
|> restrict_visibility(opts)
|
|> restrict_visibility(opts)
|
||||||
|
|> restrict_thread_visibility(opts)
|
||||||
|> restrict_replies(opts)
|
|> restrict_replies(opts)
|
||||||
|> restrict_reblogs(opts)
|
|> restrict_reblogs(opts)
|
||||||
|> restrict_pinned(opts)
|
|> restrict_pinned(opts)
|
||||||
|
@ -983,11 +1003,10 @@ def contain_activity(%Activity{} = activity, %User{} = user) do
|
||||||
contain_broken_threads(activity, user)
|
contain_broken_threads(activity, user)
|
||||||
end
|
end
|
||||||
|
|
||||||
# do post-processing on a timeline
|
def fetch_direct_messages_query do
|
||||||
def contain_timeline(timeline, user) do
|
Activity
|
||||||
timeline
|
|> restrict_type(%{"type" => "Create"})
|
||||||
|> Enum.filter(fn activity ->
|
|> restrict_visibility(%{visibility: "direct"})
|
||||||
contain_activity(activity, user)
|
|> order_by([activity], asc: activity.id)
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -55,7 +55,7 @@ defp check_media_nsfw(
|
||||||
object =
|
object =
|
||||||
if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_nsfw]), actor_host) do
|
if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_nsfw]), actor_host) do
|
||||||
tags = (child_object["tag"] || []) ++ ["nsfw"]
|
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)
|
child_object = Map.put(child_object, "sensitive", true)
|
||||||
Map.put(object, "object", child_object)
|
Map.put(object, "object", child_object)
|
||||||
else
|
else
|
||||||
|
|
|
@ -31,7 +31,7 @@ defp process_tag(
|
||||||
|
|
||||||
object =
|
object =
|
||||||
object
|
object
|
||||||
|> Map.put("tags", tags)
|
|> Map.put("tag", tags)
|
||||||
|> Map.put("sensitive", true)
|
|> Map.put("sensitive", true)
|
||||||
|
|
||||||
message = Map.put(message, "object", object)
|
message = Map.put(message, "object", object)
|
||||||
|
|
|
@ -11,7 +11,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
alias Pleroma.Object.Containment
|
alias Pleroma.Object.Containment
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
|
|
@ -20,6 +20,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@supported_object_types ["Article", "Note", "Video", "Page"]
|
@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,
|
# 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.
|
# so figure out what the actor's URI is based on what we have.
|
||||||
|
@ -670,7 +672,8 @@ def make_flag_data(params, additional) do
|
||||||
"actor" => params.actor.ap_id,
|
"actor" => params.actor.ap_id,
|
||||||
"content" => params.content,
|
"content" => params.content,
|
||||||
"object" => object,
|
"object" => object,
|
||||||
"context" => params.context
|
"context" => params.context,
|
||||||
|
"state" => "open"
|
||||||
}
|
}
|
||||||
|> Map.merge(additional)
|
|> Map.merge(additional)
|
||||||
end
|
end
|
||||||
|
@ -713,4 +716,77 @@ def fetch_ordered_collection(from, pages_left, acc \\ []) do
|
||||||
end
|
end
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.Visibility do
|
defmodule Pleroma.Web.ActivityPub.Visibility do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
|
def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
|
||||||
|
@ -13,11 +14,12 @@ def is_public?(data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_private?(activity) do
|
def is_private?(activity) do
|
||||||
unless is_public?(activity) do
|
with false <- is_public?(activity),
|
||||||
follower_address = User.get_cached_by_ap_id(activity.data["actor"]).follower_address
|
%User{follower_address: follower_address} <-
|
||||||
Enum.any?(activity.data["to"], &(&1 == follower_address))
|
User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||||
|
follower_address in activity.data["to"]
|
||||||
else
|
else
|
||||||
false
|
_ -> false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -38,25 +40,14 @@ def visible_for_user?(activity, user) do
|
||||||
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
|
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
|
||||||
end
|
end
|
||||||
|
|
||||||
# guard
|
def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do
|
||||||
def entire_thread_visible_for_user?(nil, _user), do: false
|
{: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
|
result
|
||||||
# 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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_visibility(object) do
|
def get_visibility(object) do
|
||||||
|
|
|
@ -4,11 +4,16 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
alias Pleroma.Activity
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.UserInviteToken
|
alias Pleroma.UserInviteToken
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Relay
|
alias Pleroma.Web.ActivityPub.Relay
|
||||||
alias Pleroma.Web.AdminAPI.AccountView
|
alias Pleroma.Web.AdminAPI.AccountView
|
||||||
|
alias Pleroma.Web.AdminAPI.ReportView
|
||||||
alias Pleroma.Web.AdminAPI.Search
|
alias Pleroma.Web.AdminAPI.Search
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
|
||||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||||
|
|
||||||
|
@ -287,12 +292,88 @@ def get_password_reset(conn, %{"nickname" => nickname}) do
|
||||||
|> json(token.token)
|
|> json(token.token)
|
||||||
end
|
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
|
def errors(conn, {:error, :not_found}) do
|
||||||
conn
|
conn
|
||||||
|> put_status(404)
|
|> put_status(404)
|
||||||
|> json("Not found")
|
|> json("Not found")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def errors(conn, {:error, reason}) do
|
||||||
|
conn
|
||||||
|
|> put_status(400)
|
||||||
|
|> json(reason)
|
||||||
|
end
|
||||||
|
|
||||||
def errors(conn, {:param_cast, _}) do
|
def errors(conn, {:param_cast, _}) do
|
||||||
conn
|
conn
|
||||||
|> put_status(400)
|
|> put_status(400)
|
||||||
|
|
|
@ -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
|
|
@ -71,6 +71,9 @@ def delete(activity_id, user) do
|
||||||
{:ok, _} <- unpin(activity_id, user),
|
{:ok, _} <- unpin(activity_id, user),
|
||||||
{:ok, delete} <- ActivityPub.delete(object) do
|
{:ok, delete} <- ActivityPub.delete(object) do
|
||||||
{:ok, delete}
|
{:ok, delete}
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
{:error, "Could not delete"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -318,6 +321,60 @@ def report(user, data) do
|
||||||
end
|
end
|
||||||
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
|
def hide_reblogs(user, muted) do
|
||||||
ap_id = muted.ap_id
|
ap_id = muted.ap_id
|
||||||
|
|
||||||
|
|
|
@ -246,13 +246,11 @@ def make_note_data(
|
||||||
"tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
|
"tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
|
||||||
}
|
}
|
||||||
|
|
||||||
if in_reply_to do
|
with false <- is_nil(in_reply_to),
|
||||||
in_reply_to_object = Object.normalize(in_reply_to)
|
%Object{} = in_reply_to_object <- Object.normalize(in_reply_to) do
|
||||||
|
Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
|
||||||
object
|
|
||||||
|> Map.put("inReplyTo", in_reply_to_object.data["id"])
|
|
||||||
else
|
else
|
||||||
object
|
_ -> object
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -52,9 +52,9 @@ def perform(type, _, _) do
|
||||||
@doc """
|
@doc """
|
||||||
Relays an activity to all specified peers.
|
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
|
def publish(%User{} = user, %Activity{} = activity) do
|
||||||
Config.get([:instance, :federation_publisher_modules])
|
Config.get([:instance, :federation_publisher_modules])
|
||||||
|> Enum.each(fn module ->
|
|> Enum.each(fn module ->
|
||||||
|
@ -70,9 +70,9 @@ def publish(%User{} = user, %Activity{} = activity) do
|
||||||
@doc """
|
@doc """
|
||||||
Gathers links used by an outgoing federation module for WebFinger output.
|
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
|
def gather_webfinger_links(%User{} = user) do
|
||||||
Config.get([:instance, :federation_publisher_modules])
|
Config.get([:instance, :federation_publisher_modules])
|
||||||
|> Enum.reduce([], fn module, links ->
|
|> Enum.reduce([], fn module, links ->
|
||||||
|
|
|
@ -303,7 +303,6 @@ def home_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
activities =
|
activities =
|
||||||
[user.ap_id | user.following]
|
[user.ap_id | user.following]
|
||||||
|> ActivityPub.fetch_activities(params)
|
|> ActivityPub.fetch_activities(params)
|
||||||
|> ActivityPub.contain_timeline(user)
|
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
@ -1223,7 +1222,7 @@ def remove_from_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_id
|
||||||
accounts
|
accounts
|
||||||
|> Enum.each(fn account_id ->
|
|> Enum.each(fn account_id ->
|
||||||
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
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)
|
Pleroma.List.unfollow(list, followed)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -40,7 +40,7 @@ def render("relationship.json", %{user: %User{} = user, target: %User{} = target
|
||||||
follow_activity = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, target)
|
follow_activity = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, target)
|
||||||
|
|
||||||
requested =
|
requested =
|
||||||
if follow_activity do
|
if follow_activity && !User.following?(target, user) do
|
||||||
follow_activity.data["state"] == "pending"
|
follow_activity.data["state"] == "pending"
|
||||||
else
|
else
|
||||||
false
|
false
|
||||||
|
|
|
@ -20,7 +20,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
||||||
field(:scopes, {:array, :string}, default: [])
|
field(:scopes, {:array, :string}, default: [])
|
||||||
field(:valid_until, :naive_datetime_usec)
|
field(:valid_until, :naive_datetime_usec)
|
||||||
field(:used, :boolean, default: false)
|
field(:used, :boolean, default: false)
|
||||||
belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId)
|
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||||
belongs_to(:app, App)
|
belongs_to(:app, App)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
|
|
|
@ -22,7 +22,7 @@ defmodule Pleroma.Web.OAuth.Token do
|
||||||
field(:refresh_token, :string)
|
field(:refresh_token, :string)
|
||||||
field(:scopes, {:array, :string}, default: [])
|
field(:scopes, {:array, :string}, default: [])
|
||||||
field(:valid_until, :naive_datetime_usec)
|
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)
|
belongs_to(:app, App)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
|
|
|
@ -194,6 +194,14 @@ defmodule Pleroma.Web.Router do
|
||||||
|
|
||||||
get("/users", AdminAPIController, :list_users)
|
get("/users", AdminAPIController, :list_users)
|
||||||
get("/users/:nickname", AdminAPIController, :user_show)
|
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
|
end
|
||||||
|
|
||||||
scope "/", Pleroma.Web.TwitterAPI do
|
scope "/", Pleroma.Web.TwitterAPI do
|
||||||
|
|
|
@ -101,9 +101,7 @@ def friends_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> Map.put("blocking_user", user)
|
|> Map.put("blocking_user", user)
|
||||||
|> Map.put("user", user)
|
|> Map.put("user", user)
|
||||||
|
|
||||||
activities =
|
activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
||||||
ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
|
||||||
|> ActivityPub.contain_timeline(user)
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(ActivityView)
|
|> put_view(ActivityView)
|
||||||
|
|
|
@ -99,7 +99,7 @@ def ensure_keys_present(user) do
|
||||||
|
|
||||||
info_cng =
|
info_cng =
|
||||||
info
|
info
|
||||||
|> Pleroma.User.Info.set_keys(pem)
|
|> User.Info.set_keys(pem)
|
||||||
|
|
||||||
cng =
|
cng =
|
||||||
Ecto.Changeset.change(user)
|
Ecto.Changeset.change(user)
|
||||||
|
|
5
mix.exs
5
mix.exs
|
@ -66,7 +66,10 @@ defp deps do
|
||||||
{:plug_cowboy, "~> 2.0"},
|
{:plug_cowboy, "~> 2.0"},
|
||||||
{:phoenix_pubsub, "~> 1.1"},
|
{:phoenix_pubsub, "~> 1.1"},
|
||||||
{:phoenix_ecto, "~> 4.0"},
|
{: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"},
|
{:postgrex, ">= 0.13.5"},
|
||||||
{:gettext, "~> 0.15"},
|
{:gettext, "~> 0.15"},
|
||||||
{:comeonin, "~> 4.1.1"},
|
{:comeonin, "~> 4.1.1"},
|
||||||
|
|
12
mix.lock
12
mix.lock
|
@ -16,12 +16,12 @@
|
||||||
"cowlib": {:hex, :cowlib, "2.7.0", "3ef16e77562f9855a2605900cedb15c1462d76fb1be6a32fc3ae91973ee543d2", [:rebar3], [], "hexpm"},
|
"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"},
|
"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"]},
|
"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"},
|
"decimal": {:hex, :decimal, "1.7.0", "30d6b52c88541f9a66637359ddf85016df9eb266170d53105f02e4a67e00c5aa", [:mix], [], "hexpm"},
|
||||||
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
|
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
|
||||||
"earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [: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": {: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": {: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_sql": {:git, "https://github.com/elixir-ecto/ecto_sql", "14cb065a74c488d737d973f7a91bc036c6245f78", [ref: "14cb065a74c488d737d973f7a91bc036c6245f78"]},
|
||||||
"esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"},
|
"esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"},
|
||||||
"eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"},
|
"eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"},
|
||||||
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [: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"},
|
"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"},
|
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
|
||||||
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "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": {: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_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"},
|
"prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
@ -79,11 +79,11 @@
|
||||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "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"},
|
"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"]},
|
"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"},
|
"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"},
|
"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"},
|
"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"},
|
"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"},
|
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
|
||||||
"unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
|
"unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -11,6 +11,26 @@ defmodule Pleroma.ConversationTest do
|
||||||
|
|
||||||
import Pleroma.Factory
|
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
|
test "it creates a conversation for given ap_id" do
|
||||||
assert {:ok, %Conversation{} = conversation} =
|
assert {:ok, %Conversation{} = conversation} =
|
||||||
Conversation.create_for_ap_id("https://some_ap_id")
|
Conversation.create_for_ap_id("https://some_ap_id")
|
||||||
|
|
|
@ -125,7 +125,7 @@ test "gives a replacement for user links, using local nicknames in user links te
|
||||||
archaeme =
|
archaeme =
|
||||||
insert(:user, %{
|
insert(:user, %{
|
||||||
nickname: "archa_eme_",
|
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"})
|
archaeme_remote = insert(:user, %{nickname: "archaeme@archae.me"})
|
||||||
|
|
|
@ -7,12 +7,21 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Plug.Conn
|
alias Plug.Conn
|
||||||
|
|
||||||
test "it sends CSP headers when enabled", %{conn: conn} do
|
describe "http security enabled" do
|
||||||
|
setup do
|
||||||
|
enabled = Config.get([:http_securiy, :enabled])
|
||||||
|
|
||||||
Config.put([:http_security, :enabled], true)
|
Config.put([:http_security, :enabled], true)
|
||||||
|
|
||||||
conn =
|
on_exit(fn ->
|
||||||
conn
|
Config.put([:http_security, :enabled], enabled)
|
||||||
|> get("/api/v1/instance")
|
end)
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it sends CSP headers when enabled", %{conn: conn} do
|
||||||
|
conn = get(conn, "/api/v1/instance")
|
||||||
|
|
||||||
refute Conn.get_resp_header(conn, "x-xss-protection") == []
|
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-permitted-cross-domain-policies") == []
|
||||||
|
@ -23,52 +32,26 @@ test "it sends CSP headers when enabled", %{conn: conn} do
|
||||||
refute Conn.get_resp_header(conn, "content-security-policy") == []
|
refute Conn.get_resp_header(conn, "content-security-policy") == []
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it does not send CSP headers when disabled", %{conn: conn} do
|
|
||||||
Config.put([:http_security, :enabled], false)
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> get("/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
|
|
||||||
|
|
||||||
test "it sends STS headers when enabled", %{conn: conn} do
|
test "it sends STS headers when enabled", %{conn: conn} do
|
||||||
Config.put([:http_security, :enabled], true)
|
|
||||||
Config.put([:http_security, :sts], true)
|
Config.put([:http_security, :sts], true)
|
||||||
|
|
||||||
conn =
|
conn = get(conn, "/api/v1/instance")
|
||||||
conn
|
|
||||||
|> get("/api/v1/instance")
|
|
||||||
|
|
||||||
refute Conn.get_resp_header(conn, "strict-transport-security") == []
|
refute Conn.get_resp_header(conn, "strict-transport-security") == []
|
||||||
refute Conn.get_resp_header(conn, "expect-ct") == []
|
refute Conn.get_resp_header(conn, "expect-ct") == []
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it does not send STS headers when disabled", %{conn: conn} do
|
test "it does not send STS headers when disabled", %{conn: conn} do
|
||||||
Config.put([:http_security, :enabled], true)
|
|
||||||
Config.put([:http_security, :sts], false)
|
Config.put([:http_security, :sts], false)
|
||||||
|
|
||||||
conn =
|
conn = get(conn, "/api/v1/instance")
|
||||||
conn
|
|
||||||
|> get("/api/v1/instance")
|
|
||||||
|
|
||||||
assert Conn.get_resp_header(conn, "strict-transport-security") == []
|
assert Conn.get_resp_header(conn, "strict-transport-security") == []
|
||||||
assert Conn.get_resp_header(conn, "expect-ct") == []
|
assert Conn.get_resp_header(conn, "expect-ct") == []
|
||||||
end
|
end
|
||||||
|
|
||||||
test "referrer-policy header reflects configured value", %{conn: conn} do
|
test "referrer-policy header reflects configured value", %{conn: conn} do
|
||||||
Config.put([:http_security, :enabled], true)
|
conn = get(conn, "/api/v1/instance")
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/instance")
|
|
||||||
|
|
||||||
assert Conn.get_resp_header(conn, "referrer-policy") == ["same-origin"]
|
assert Conn.get_resp_header(conn, "referrer-policy") == ["same-origin"]
|
||||||
|
|
||||||
|
@ -80,4 +63,40 @@ test "referrer-policy header reflects configured value", %{conn: conn} do
|
||||||
|
|
||||||
assert Conn.get_resp_header(conn, "referrer-policy") == ["no-referrer"]
|
assert Conn.get_resp_header(conn, "referrer-policy") == ["no-referrer"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it sends `report-to` & `report-uri` CSP response headers" do
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> get("/api/v1/instance")
|
||||||
|
|
||||||
|
[csp] = Conn.get_resp_header(conn, "content-security-policy")
|
||||||
|
|
||||||
|
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
|
end
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do
|
defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do
|
||||||
use Pleroma.Web.ConnCase, async: true
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
alias Pleroma.Plugs.LegacyAuthenticationPlug
|
alias Pleroma.Plugs.LegacyAuthenticationPlug
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
|
@ -1,23 +1,24 @@
|
||||||
defmodule Pleroma.RepoTest do
|
defmodule Pleroma.RepoTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
describe "find_resource/1" do
|
describe "find_resource/1" do
|
||||||
test "returns user" do
|
test "returns user" do
|
||||||
user = insert(:user)
|
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}
|
assert Repo.find_resource(query) == {:ok, user}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns not_found" do
|
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}
|
assert Repo.find_resource(query) == {:error, :not_found}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "get_assoc/2" do
|
describe "get_assoc/2" do
|
||||||
test "get assoc from preloaded data" 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}
|
token = %Pleroma.Web.OAuth.Token{insert(:oauth_token) | user: user}
|
||||||
assert Repo.get_assoc(token, :user) == {:ok, user}
|
assert Repo.get_assoc(token, :user) == {:ok, user}
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
defmodule Pleroma.Factory do
|
defmodule Pleroma.Factory do
|
||||||
use ExMachina.Ecto, repo: Pleroma.Repo
|
use ExMachina.Ecto, repo: Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
def participation_factory do
|
def participation_factory do
|
||||||
conversation = insert(:conversation)
|
conversation = insert(:conversation)
|
||||||
|
@ -23,7 +24,7 @@ def conversation_factory do
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_factory do
|
def user_factory do
|
||||||
user = %Pleroma.User{
|
user = %User{
|
||||||
name: sequence(:name, &"Test テスト User #{&1}"),
|
name: sequence(:name, &"Test テスト User #{&1}"),
|
||||||
email: sequence(:email, &"user#{&1}@example.com"),
|
email: sequence(:email, &"user#{&1}@example.com"),
|
||||||
nickname: sequence(:nickname, &"nick#{&1}"),
|
nickname: sequence(:nickname, &"nick#{&1}"),
|
||||||
|
@ -34,16 +35,16 @@ def user_factory do
|
||||||
|
|
||||||
%{
|
%{
|
||||||
user
|
user
|
||||||
| ap_id: Pleroma.User.ap_id(user),
|
| ap_id: User.ap_id(user),
|
||||||
follower_address: Pleroma.User.ap_followers(user),
|
follower_address: User.ap_followers(user),
|
||||||
following: [Pleroma.User.ap_id(user)]
|
following: [User.ap_id(user)]
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def note_factory(attrs \\ %{}) do
|
def note_factory(attrs \\ %{}) do
|
||||||
text = sequence(:text, &"This is :moominmamma: note #{&1}")
|
text = sequence(:text, &"This is :moominmamma: note #{&1}")
|
||||||
|
|
||||||
user = insert(:user)
|
user = attrs[:user] || insert(:user)
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
"type" => "Note",
|
"type" => "Note",
|
||||||
|
@ -113,7 +114,8 @@ def direct_note_activity_factory do
|
||||||
end
|
end
|
||||||
|
|
||||||
def note_activity_factory(attrs \\ %{}) do
|
def note_activity_factory(attrs \\ %{}) do
|
||||||
note = attrs[:note] || insert(:note)
|
user = attrs[:user] || insert(:user)
|
||||||
|
note = attrs[:note] || insert(:note, user: user)
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
||||||
|
|
|
@ -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
|
|
@ -3,6 +3,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Mix.Tasks.Pleroma.UserTest do
|
defmodule Mix.Tasks.Pleroma.UserTest do
|
||||||
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
@ -338,4 +339,31 @@ test "activities are deleted" do
|
||||||
assert message == "User #{nickname} statuses deleted."
|
assert message == "User #{nickname} statuses deleted."
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
@ -277,7 +277,7 @@ test "it requires an email, name, nickname and password, bio is optional" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it restricts certain nicknames" do
|
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)
|
assert is_bitstring(restricted_name)
|
||||||
|
|
||||||
|
@ -626,6 +626,37 @@ test "it sets the info->follower_count property" do
|
||||||
end
|
end
|
||||||
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
|
describe "follow_import" do
|
||||||
test "it imports user followings from list" do
|
test "it imports user followings from list" do
|
||||||
[user1, user2, user3] = insert_list(3, :user)
|
[user1, user2, user3] = insert_list(3, :user)
|
||||||
|
@ -873,7 +904,6 @@ test "hide a user's statuses from timelines and notifications" do
|
||||||
|
|
||||||
assert [activity] ==
|
assert [activity] ==
|
||||||
ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
|
ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
|
||||||
|> ActivityPub.contain_timeline(user2)
|
|
||||||
|
|
||||||
{:ok, _user} = User.deactivate(user)
|
{:ok, _user} = User.deactivate(user)
|
||||||
|
|
||||||
|
@ -882,7 +912,6 @@ test "hide a user's statuses from timelines and notifications" do
|
||||||
|
|
||||||
assert [] ==
|
assert [] ==
|
||||||
ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
|
ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
|
||||||
|> ActivityPub.contain_timeline(user2)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1194,14 +1223,32 @@ test "follower count is updated when a follower is blocked" do
|
||||||
follower2 = insert(:user)
|
follower2 = insert(:user)
|
||||||
follower3 = insert(:user)
|
follower3 = insert(:user)
|
||||||
|
|
||||||
{:ok, follower} = Pleroma.User.follow(follower, user)
|
{:ok, follower} = User.follow(follower, user)
|
||||||
{:ok, _follower2} = Pleroma.User.follow(follower2, user)
|
{:ok, _follower2} = User.follow(follower2, user)
|
||||||
{:ok, _follower3} = Pleroma.User.follow(follower3, 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})
|
user_show = Pleroma.Web.TwitterAPI.UserView.render("show.json", %{user: user})
|
||||||
|
|
||||||
assert Map.get(user_show, "followers_count") == 2
|
assert Map.get(user_show, "followers_count") == 2
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -462,6 +462,29 @@ test "doesn't return announce activities concerning blocked users" do
|
||||||
refute Enum.member?(activities, activity_three.id)
|
refute Enum.member?(activities, activity_three.id)
|
||||||
end
|
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
|
test "doesn't return muted activities" do
|
||||||
activity_one = insert(:note_activity)
|
activity_one = insert(:note_activity)
|
||||||
activity_two = insert(:note_activity)
|
activity_two = insert(:note_activity)
|
||||||
|
@ -960,17 +983,21 @@ test "it filters broken threads" do
|
||||||
"in_reply_to_status_id" => private_activity_2.id
|
"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"])
|
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
|
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
|
assert length(activities) == 2
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -96,6 +96,16 @@ test "visible_for_user?", %{
|
||||||
refute Visibility.visible_for_user?(direct, unrelated)
|
refute Visibility.visible_for_user?(direct, unrelated)
|
||||||
end
|
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", %{
|
test "get_visibility", %{
|
||||||
public: public,
|
public: public,
|
||||||
private: private,
|
private: private,
|
||||||
|
|
|
@ -5,8 +5,10 @@
|
||||||
defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
|
defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
|
||||||
use Pleroma.Web.ConnCase
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.UserInviteToken
|
alias Pleroma.UserInviteToken
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
describe "/api/pleroma/admin/users" do
|
describe "/api/pleroma/admin/users" do
|
||||||
|
@ -949,4 +951,329 @@ test "with token" do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
@ -272,10 +272,41 @@ test "creates a report" do
|
||||||
data: %{
|
data: %{
|
||||||
"type" => "Flag",
|
"type" => "Flag",
|
||||||
"content" => ^comment,
|
"content" => ^comment,
|
||||||
"object" => [^target_ap_id, ^activity_ap_id]
|
"object" => [^target_ap_id, ^activity_ap_id],
|
||||||
|
"state" => "open"
|
||||||
}
|
}
|
||||||
} = flag_activity
|
} = flag_activity
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe "reblog muting" do
|
describe "reblog muting" do
|
||||||
|
@ -290,14 +321,14 @@ test "creates a report" do
|
||||||
test "add a reblog mute", %{muter: muter, muted: muted} do
|
test "add a reblog mute", %{muter: muter, muted: muted} do
|
||||||
{:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
|
{:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
|
||||||
|
|
||||||
assert Pleroma.User.showing_reblogs?(muter, muted) == false
|
assert User.showing_reblogs?(muter, muted) == false
|
||||||
end
|
end
|
||||||
|
|
||||||
test "remove a reblog mute", %{muter: muter, muted: muted} do
|
test "remove a reblog mute", %{muter: muter, muted: muted} do
|
||||||
{:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
|
{:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
|
||||||
{:ok, muter} = CommonAPI.show_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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -446,7 +446,7 @@ test "verify_credentials", %{conn: conn} do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "verify_credentials default scope unlisted", %{conn: conn} do
|
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 =
|
||||||
conn
|
conn
|
||||||
|
@ -1322,7 +1322,7 @@ test "returns the relationships for the current user", %{conn: conn} do
|
||||||
|
|
||||||
describe "locked accounts" do
|
describe "locked accounts" do
|
||||||
test "/api/v1/follow_requests works" 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)
|
other_user = insert(:user)
|
||||||
|
|
||||||
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||||
|
@ -1367,7 +1367,7 @@ test "/api/v1/follow_requests/:id/authorize works" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "verify_credentials", %{conn: conn} do
|
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 =
|
||||||
conn
|
conn
|
||||||
|
@ -1379,7 +1379,7 @@ test "verify_credentials", %{conn: conn} do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "/api/v1/follow_requests/:id/reject works" do
|
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)
|
other_user = insert(:user)
|
||||||
|
|
||||||
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||||
|
@ -2129,7 +2129,7 @@ test "returns favorited DM only when user is logged in and he is one of recipien
|
||||||
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|
||||||
|> json_response(:ok)
|
|> json_response(:ok)
|
||||||
|
|
||||||
assert length(anonymous_response) == 0
|
assert Enum.empty?(anonymous_response)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "does not return others' favorited DM when user is not one of recipients", %{
|
test "does not return others' favorited DM when user is not one of recipients", %{
|
||||||
|
@ -2153,7 +2153,7 @@ test "does not return others' favorited DM when user is not one of recipients",
|
||||||
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|
||||||
|> json_response(:ok)
|
|> json_response(:ok)
|
||||||
|
|
||||||
assert length(response) == 0
|
assert Enum.empty?(response)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "paginates favorites using since_id and max_id", %{
|
test "paginates favorites using since_id and max_id", %{
|
||||||
|
|
|
@ -355,7 +355,7 @@ test "tries to use the information in poco fields" do
|
||||||
|
|
||||||
{:ok, user} = OStatus.find_or_make_user(uri)
|
{: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.name == "Constance Variable"
|
||||||
assert user.nickname == "lambadalambda@social.heldscal.la"
|
assert user.nickname == "lambadalambda@social.heldscal.la"
|
||||||
assert user.local == false
|
assert user.local == false
|
||||||
|
@ -374,7 +374,7 @@ test "find_or_make_user sets all the nessary input fields" do
|
||||||
{:ok, user} = OStatus.find_or_make_user(uri)
|
{:ok, user} = OStatus.find_or_make_user(uri)
|
||||||
|
|
||||||
assert user.info ==
|
assert user.info ==
|
||||||
%Pleroma.User.Info{
|
%User.Info{
|
||||||
id: user.info.id,
|
id: user.info.id,
|
||||||
ap_enabled: false,
|
ap_enabled: false,
|
||||||
background: %{},
|
background: %{},
|
||||||
|
@ -407,7 +407,7 @@ test "find_make_or_update_user takes an author element and returns an updated us
|
||||||
{:ok, user} = OStatus.find_or_make_user(uri)
|
{:ok, user} = OStatus.find_or_make_user(uri)
|
||||||
old_name = user.name
|
old_name = user.name
|
||||||
old_bio = user.bio
|
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)
|
{:ok, user} = Repo.update(change)
|
||||||
refute user.avatar
|
refute user.avatar
|
||||||
|
|
Loading…
Reference in New Issue