The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
+ ### Removed
+ - **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media`
+ - **Breaking**: OStatus protocol support
+
+ ### Changed
+ - **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
+ - Replaced [pleroma_job_queue](https://git.pleroma.social/pleroma/pleroma_job_queue) and `Pleroma.Web.Federator.RetryQueue` with [Oban](https://github.com/sorentwo/oban) (see [`docs/config.md`](docs/config.md) on migrating customized worker / retry settings)
+ - Introduced [quantum](https://github.com/quantum-elixir/quantum-core) job scheduler
+ - Enabled `:instance, extended_nickname_format` in the default config
+ - Add `rel="ugc"` to all links in statuses, to prevent SEO spam
+ - Extract RSS functionality from OStatus
+ - MRF (Simple Policy): Also use `:accept`/`:reject` on the actors rather than only their activities
+ <details>
+ <summary>API Changes</summary>
+
+ - **Breaking:** Admin API: Return link alongside with token on password reset
+ - **Breaking:** `/api/pleroma/admin/users/invite_token` now uses `POST`, changed accepted params and returns full invite in json instead of only token string.
+ - Admin API: Return `total` when querying for reports
+ - Mastodon API: Return `pleroma.direct_conversation_id` when creating a direct message (`POST /api/v1/statuses`)
+ - Admin API: Return link alongside with token on password reset
+ - Mastodon API: Add `pleroma.direct_conversation_id` to the status endpoint (`GET /api/v1/statuses/:id`)
+ - Mastodon API: `pleroma.thread_muted` to the Status entity
+ - Mastodon API: Mark the direct conversation as read for the author when they send a new direct message
+ - Mastodon API, streaming: Add `pleroma.direct_conversation_id` to the `conversation` stream event payload.
+ </details>
+
### Added
- Refreshing poll results for remote polls
+ - Authentication: Added rate limit for password-authorized actions / login existence checks
+ - Mix task to re-count statuses for all users (`mix pleroma.count_statuses`)
+ - Support for `X-Forwarded-For` and similar HTTP headers which used by reverse proxies to pass a real user IP address to the backend. Must not be enabled unless your instance is behind at least one reverse proxy (such as Nginx, Apache HTTPD or Varnish Cache).
+ <details>
+ <summary>API Changes</summary>
+
- Job queue stats to the healthcheck page
- Admin API: Add ability to require password reset
- Mastodon API: Account entities now include `follow_requests_count` (planned Mastodon 3.x addition)
- Mastodon API: Add `upload_limit`, `avatar_upload_limit`, `background_upload_limit`, and `banner_upload_limit` to `/api/v1/instance`
- Mastodon API: Add `pleroma.unread_conversation_count` to the Account entity
- OAuth: support for hierarchical permissions / [Mastodon 2.4.3 OAuth permissions](https://docs.joinmastodon.org/api/permissions/)
- - Authentication: Added rate limit for password-authorized actions / login existence checks
- Metadata Link: Atom syndication Feed
- - Mix task to re-count statuses for all users (`mix pleroma.count_statuses`)
+ - Mastodon API: Add `exclude_visibilities` parameter to the timeline and notification endpoints
+ - Admin API: `/users/:nickname/toggle_activation` endpoint is now deprecated in favor of: `/users/activate`, `/users/deactivate`, both accept `nicknames` array
+ - Admin API: `POST/DELETE /api/pleroma/admin/users/:nickname/permission_group/:permission_group` are deprecated in favor of: `POST/DELETE /api/pleroma/admin/users/permission_group/:permission_group` (both accept `nicknames` array), `DELETE /api/pleroma/admin/users` (`nickname` query param or `nickname` sent in JSON body) is deprecated in favor of: `DELETE /api/pleroma/admin/users` (`nicknames` query array param or `nicknames` sent in JSON body).
+ - Admin API: Add `GET /api/pleroma/admin/relay` endpoint - lists all followed relays
+ - Pleroma API: `POST /api/v1/pleroma/conversations/read` to mark all conversations as read
+ - Mastodon API: Add `/api/v1/markers` for managing timeline read markers
+- Admin API: Add ability to fetch reports, grouped by status `GET /api/pleroma/admin/grouped_reports`
### Changed
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
- **Breaking:** Admin API: Return link alongside with token on password reset
+- **Breaking:** Admin API: Changing report state now uses `PATCH` (it was `PUT` before) and allows updating multiple reports at once (API changed)
- Replaced [pleroma_job_queue](https://git.pleroma.social/pleroma/pleroma_job_queue) and `Pleroma.Web.Federator.RetryQueue` with [Oban](https://github.com/sorentwo/oban) (see [`docs/config.md`](docs/config.md) on migrating customized worker / retry settings)
- Introduced [quantum](https://github.com/quantum-elixir/quantum-core) job scheduler
- Admin API: Return `total` when querying for reports
- MRF (Simple Policy): Also use `:accept`/`:reject` on the actors rather than only their activities
- OStatus: Extract RSS functionality
- Mastodon API: Add `pleroma.direct_conversation_id` to the status endpoint (`GET /api/v1/statuses/:id`)
+ - Mastodon API: Mark the direct conversation as read for the author when they send a new direct message
+ - Deprecated `User.Info` embedded schema (fields moved to `User`)
+ - Store status data inside Flag activity
+ </details>
### Fixed
+ - Report emails now include functional links to profiles of remote user accounts
+ <details>
+ <summary>API Changes</summary>
+
- Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`)
- Mastodon API: Inability to get some local users by nickname in `/api/v1/accounts/:id_or_nickname`
- - Added `:instance, extended_nickname_format` setting to the default config
- - Report emails now include functional links to profiles of remote user accounts
+ </details>
+
+ ## [1.1.2] - 2019-10-18
+ ### Fixed
+ - `pleroma_ctl` trying to connect to a running instance when generating the config, which of course doesn't exist.
- ## [1.1.0] - 2019-??-??
+ ## [1.1.1] - 2019-10-18
+ ### Fixed
+ - One of the migrations between 1.0.0 and 1.1.0 wiping user info of the relay user because of unexpected behavior of postgresql's `jsonb_set`, resulting in inability to post in the default configuration. If you were affected, please run the following query in postgres console, the relay user will be recreated automatically:
+ ```
+ delete from users where ap_id = 'https://your.instance.hostname/relay';
+ ```
+ - Bad user search matches
+
+ ## [1.1.0] - 2019-10-14
+ **Breaking:** The stable branch has been changed from `master` to `stable`. If you want to keep using 1.0, the `release/1.0` branch will receive security updates for 6 months after 1.1 release.
+
+ **OTP Note:** `pleroma_ctl` in 1.0 defaults to `master` and doesn't support specifying arbitrary branches, making `./pleroma_ctl update` fail. To fix this, fetch a version of `pleroma_ctl` from 1.1 using the command below and proceed with the update normally:
+ ```
+ curl -Lo ./bin/pleroma_ctl 'https://git.pleroma.social/pleroma/pleroma/raw/develop/rel/files/bin/pleroma_ctl'
+ ```
### Security
- Mastodon API: respect post privacy in `/api/v1/statuses/:id/{favourited,reblogged}_by`
- **Breaking:** GNU Social API with Qvitter extensions support
- Emoji: Remove longfox emojis.
- Remove `Reply-To` header from report emails for admins.
+ - ActivityPub: The `/objects/:uuid/likes` endpoint.
### Changed
- **Breaking:** Configuration: A setting to explicitly disable the mailer was added, defaulting to true, if you are using a mailer add `config :pleroma, Pleroma.Emails.Mailer, enabled: true` to your config
- **Breaking:** Configuration: `/media/` is now removed when `base_url` is configured, append `/media/` to your `base_url` config to keep the old behaviour if desired
- **Breaking:** `/api/pleroma/notifications/read` is moved to `/api/v1/pleroma/notifications/read` and now supports `max_id` and responds with Mastodon API entities.
- - **Breaking:** `/api/pleroma/admin/users/invite_token` now uses `POST`, changed accepted params and returns full invite in json instead of only token string.
- Configuration: added `config/description.exs`, from which `docs/config.md` is generated
- Configuration: OpenGraph and TwitterCard providers enabled by default
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
- - Mastodon API: `pleroma.thread_muted` key in the Status entity
- Federation: Return 403 errors when trying to request pages from a user's follower/following collections if they have `hide_followers`/`hide_follows` set
- NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option
- NodeInfo: Return `mailerEnabled` in `metadata`
- AdminAPI: Add "godmode" while fetching user statuses (i.e. admin can see private statuses)
- Improve digest email template
– Pagination: (optional) return `total` alongside with `items` when paginating
- - Add `rel="ugc"` to all links in statuses, to prevent SEO spam
+ - The `Pleroma.FlakeId` module has been replaced with the `flake_id` library.
### Fixed
- Following from Osada
- Mastodon API: Misskey's endless polls being unable to render
- Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity
- Mastodon API: Notifications endpoint crashing if one notification failed to render
+ - Mastodon API: `exclude_replies` is correctly handled again.
- Mastodon API: Add `account_id`, `type`, `offset`, and `limit` to search API (`/api/v1/search` and `/api/v2/search`)
- Mastodon API, streaming: Fix filtering of notifications based on blocks/mutes/thread mutes
- - ActivityPub C2S: follower/following collection pages being inaccessible even when authentifucated if `hide_followers`/ `hide_follows` was set
- - Existing user id not being preserved on insert conflict
+ - Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`)
+ - Mastodon API: Ensure the `account` field is not empty when rendering Notification entities.
+ - Mastodon API: Inability to get some local users by nickname in `/api/v1/accounts/:id_or_nickname`
+ - Mastodon API: Blocks are now treated consistently between the Streaming API and the Timeline APIs
- Rich Media: Parser failing when no TTL can be found by image TTL setters
- Rich Media: The crawled URL is now spliced into the rich media data.
- ActivityPub S2S: sharedInbox usage has been mostly aligned with the rules in the AP specification.
- - Pleroma.Upload base_url was not automatically whitelisted by MediaProxy. Now your custom CDN or file hosting will be accessed directly as expected.
- - Report email not being sent to admins when the reporter is a remote user
- - Reverse Proxy limiting `max_body_length` was incorrectly defined and only checked `Content-Length` headers which may not be sufficient in some circumstances
+ - ActivityPub C2S: follower/following collection pages being inaccessible even when authentifucated if `hide_followers`/ `hide_follows` was set
- ActivityPub: Deactivated user deletion
- ActivityPub: Fix `/users/:nickname/inbox` crashing without an authenticated user
- MRF: fix ability to follow a relay when AntiFollowbotPolicy was enabled
- - Mastodon API: Blocks are now treated consistently between the Streaming API and the Timeline APIs
- - Mastodon API: `exclude_replies` is correctly handled again.
+ - ActivityPub: Correct addressing of Undo.
+ - ActivityPub: Correct addressing of profile update activities.
+ - ActivityPub: Polls are now refreshed when necessary.
+ - Report emails now include functional links to profiles of remote user accounts
+ - Existing user id not being preserved on insert conflict
+ - Pleroma.Upload base_url was not automatically whitelisted by MediaProxy. Now your custom CDN or file hosting will be accessed directly as expected.
+ - Report email not being sent to admins when the reporter is a remote user
+ - Reverse Proxy limiting `max_body_length` was incorrectly defined and only checked `Content-Length` headers which may not be sufficient in some circumstances
### Added
- Expiring/ephemeral activites. All activities can have expires_at value set, which controls when they should be deleted automatically.
- Mastodon API: Support for the [`tagged` filter](https://github.com/tootsuite/mastodon/pull/9755) in [`GET /api/v1/accounts/:id/statuses`](https://docs.joinmastodon.org/api/rest/accounts/#get-api-v1-accounts-id-statuses)
- Mastodon API, streaming: Add support for passing the token in the `Sec-WebSocket-Protocol` header
- Mastodon API, extension: Ability to reset avatar, profile banner, and background
+ - Mastodon API: Add support for `fields_attributes` API parameter (setting custom fields)
- Mastodon API: Add support for categories for custom emojis by reusing the group feature. <https://github.com/tootsuite/mastodon/pull/11196>
- Mastodon API: Add support for muting/unmuting notifications
- Mastodon API: Add support for the `blocked_by` attribute in the relationship API (`GET /api/v1/accounts/relationships`). <https://github.com/tootsuite/mastodon/pull/10373>
- Mastodon API: added `/auth/password` endpoint for password reset with rate limit.
- Mastodon API: /api/v1/accounts/:id/statuses now supports nicknames or user id
- Mastodon API: Improve support for the user profile custom fields
- - Mastodon API: follower/following counters are nullified when `hide_follows`/`hide_followers` and `hide_follows_count`/`hide_followers_count` are set
+ - Mastodon API: Add support for `fields_attributes` API parameter (setting custom fields)
+ - Mastodon API: Added an endpoint to get multiple statuses by IDs (`GET /api/v1/statuses/?ids[]=1&ids[]=2`)
- Admin API: Return users' tags when querying reports
- Admin API: Return avatar and display name when querying users
- Admin API: Allow querying user by ID
- Pleroma API: Add `/api/v1/pleroma/accounts/confirmation_resend?email=<email>` for resending account confirmation.
- Pleroma API: Email change endpoint.
- Admin API: Added moderation log
- - Support for `X-Forwarded-For` and similar HTTP headers which used by reverse proxies to pass a real user IP address to the backend. Must not be enabled unless your instance is behind at least one reverse proxy (such as Nginx, Apache HTTPD or Varnish Cache).
- Web response cache (currently, enabled for ActivityPub)
- - Mastodon API: Added an endpoint to get multiple statuses by IDs (`GET /api/v1/statuses/?ids[]=1&ids[]=2`)
- - ActivityPub: Add ActivityPub actor's `discoverable` parameter.
- - Admin API: Added moderation log filters (user/start date/end date/search/pagination)
- Reverse Proxy: Do not retry failed requests to limit pressure on the peer
### Changed
Authentication is required and the user must be an admin.
-## `/api/pleroma/admin/users`
+## `GET /api/pleroma/admin/users`
### List users
-- Method `GET`
- Query Params:
- *optional* `query`: **string** search term (e.g. nickname, domain, nickname@domain)
- *optional* `filters`: **string** comma-separated string of filters:
}
```
- ## `DELETE /api/pleroma/admin/users`
+ ## DEPRECATED `DELETE /api/pleroma/admin/users`
### Remove a user
-- Method `DELETE`
- Params:
- `nickname`
- Response: User’s nickname
+ ## `DELETE /api/pleroma/admin/users`
+
+ ### Remove a user
+
+ - Method `DELETE`
+ - Params:
+ - `nicknames`
+ - Response: Array of user nicknames
+
### Create a user
- Method: `POST`
]
- Response: User’s nickname
-## `/api/pleroma/admin/users/follow`
+## `POST /api/pleroma/admin/users/follow`
+
### Make a user follow another user
-- Methods: `POST`
- Params:
- - `follower`: The nickname of the follower
- - `followed`: The nickname of the followed
+ - `follower`: The nickname of the follower
+ - `followed`: The nickname of the followed
- Response:
- - "ok"
+ - "ok"
+
+## `POST /api/pleroma/admin/users/unfollow`
-## `/api/pleroma/admin/users/unfollow`
### Make a user unfollow another user
-- Methods: `POST`
- Params:
- - `follower`: The nickname of the follower
- - `followed`: The nickname of the followed
+ - `follower`: The nickname of the follower
+ - `followed`: The nickname of the followed
- Response:
- - "ok"
+ - "ok"
-## `/api/pleroma/admin/users/:nickname/toggle_activation`
+## `PATCH /api/pleroma/admin/users/:nickname/toggle_activation`
### Toggle user activation
-- Method: `PATCH`
- Params:
- `nickname`
- Response: User’s object
}
```
-## `/api/pleroma/admin/users/tag`
+## `PUT /api/pleroma/admin/users/tag`
### Tag a list of users
-- Method: `PUT`
- Params:
- `nicknames` (array)
- `tags` (array)
+## `DELETE /api/pleroma/admin/users/tag`
+
### Untag a list of users
-- Method: `DELETE`
- Params:
- `nicknames` (array)
- `tags` (array)
-## `/api/pleroma/admin/users/:nickname/permission_group`
+## `GET /api/pleroma/admin/users/:nickname/permission_group`
### Get user user permission groups membership
-- Method: `GET`
- Params: none
- Response:
}
```
-## `/api/pleroma/admin/users/:nickname/permission_group/:permission_group`
+## `GET /api/pleroma/admin/users/:nickname/permission_group/:permission_group`
Note: Available `:permission_group` is currently moderator and admin. 404 is returned when the permission group doesn’t exist.
### Get user user permission groups membership per permission group
-- Method: `GET`
- Params: none
- Response:
}
```
- ## `POST /api/pleroma/admin/users/:nickname/permission_group/:permission_group`
+ ## DEPRECATED `POST /api/pleroma/admin/users/:nickname/permission_group/:permission_group`
- ### Add user in permission group
+ ### Add user to permission group
- Params: none
- Response:
- On failure: `{"error": "…"}`
- - On success: JSON of the `user.info`
+ - On success: JSON of the user
+
+ ## `POST /api/pleroma/admin/users/permission_group/:permission_group`
+
+ ### Add users to permission group
+
+ - Params:
+ - `nicknames`: nicknames array
+ - Response:
+ - On failure: `{"error": "…"}`
+ - On success: JSON of the user
+
+ ## DEPRECATED `DELETE /api/pleroma/admin/users/:nickname/permission_group/:permission_group`
+## `DELETE /api/pleroma/admin/users/:nickname/permission_group/:permission_group`
+
### Remove user from permission group
- Params: none
- Response:
- On failure: `{"error": "…"}`
- - On success: JSON of the `user.info`
+ - On success: JSON of the user
+ - Note: An admin cannot revoke their own admin status.
+
+ ## `DELETE /api/pleroma/admin/users/permission_group/:permission_group`
+
+ ### Remove users from permission group
+
+ - Params:
+ - `nicknames`: nicknames array
+ - Response:
+ - On failure: `{"error": "…"}`
+ - On success: JSON of the user
- Note: An admin cannot revoke their own admin status.
- ## `PUT /api/pleroma/admin/users/:nickname/activation_status`
+ ## `PATCH /api/pleroma/admin/users/activate`
+
+ ### Activate user
+
+ - Params:
+ - `nicknames`: nicknames array
+ - Response:
+
+ ```json
+ {
+ users: [
+ {
+ // user object
+ }
+ ]
+ }
+ ```
+
+ ## `PATCH /api/pleroma/admin/users/deactivate`
+
+ ### Deactivate user
+
+ - Params:
+ - `nicknames`: nicknames array
+ - Response:
+
+ ```json
+ {
+ users: [
+ {
+ // user object
+ }
+ ]
+ }
+ ```
+
+ ## DEPRECATED `PATCH /api/pleroma/admin/users/:nickname/activation_status`
### Active or deactivate a user
- `nickname`
- `status` BOOLEAN field, false value means deactivation.
-## `/api/pleroma/admin/users/:nickname_or_id`
+## `GET /api/pleroma/admin/users/:nickname_or_id`
### Retrive the details of a user
-- Method: `GET`
- Params:
- `nickname` or `id`
- Response:
- On failure: `Not found`
- On success: JSON of the user
-## `/api/pleroma/admin/users/:nickname_or_id/statuses`
+## `GET /api/pleroma/admin/users/:nickname_or_id/statuses`
### Retrive user's latest statuses
-- Method: `GET`
- Params:
- `nickname` or `id`
- *optional* `page_size`: number of statuses to return (default is `20`)
- On failure: `Not found`
- On success: JSON array of user's latest statuses
-## `/api/pleroma/admin/relay`
+## `POST /api/pleroma/admin/relay`
### Follow a Relay
-- Methods: `POST`
- Params:
- `relay_url`
- Response:
- On success: URL of the followed relay
+## `DELETE /api/pleroma/admin/relay`
+
### Unfollow a Relay
-- Methods: `DELETE`
- Params:
- `relay_url`
- Response:
- On success: URL of the unfollowed relay
-## `/api/pleroma/admin/users/invite_token`
+ ## `GET /api/pleroma/admin/relay`
+
+ ### List Relays
+
+ - Params: none
+ - Response:
+ - On success: JSON array of relays
+
+## `POST /api/pleroma/admin/users/invite_token`
### Create an account registration invite token
-- Methods: `POST`
- Params:
- *optional* `max_use` (integer)
- *optional* `expires_at` (date string e.g. "2019-04-07")
}
```
-## `/api/pleroma/admin/users/invites`
+## `GET /api/pleroma/admin/users/invites`
### Get a list of generated invites
-- Methods: `GET`
- Params: none
- Response:
}
```
-## `/api/pleroma/admin/users/revoke_invite`
+## `POST /api/pleroma/admin/users/revoke_invite`
### Revoke invite by token
-- Methods: `POST`
- Params:
- `token`
- Response:
}
```
-
-## `/api/pleroma/admin/users/email_invite`
+## `POST /api/pleroma/admin/users/email_invite`
### Sends registration invite via email
-- Methods: `POST`
- Params:
- `email`
- `name`, optional
-## `/api/pleroma/admin/users/:nickname/password_reset`
+## `GET /api/pleroma/admin/users/:nickname/password_reset`
### Get a password reset token for a given nickname
-- Methods: `GET`
- Params: none
- Response:
}
```
-
-## `/api/pleroma/admin/users/:nickname/force_password_reset`
+## `PATCH /api/pleroma/admin/users/:nickname/force_password_reset`
### Force passord reset for a user with a given nickname
-- Methods: `PATCH`
- Params: none
- Response: none (code `204`)
-## `/api/pleroma/admin/reports`
+## `GET /api/pleroma/admin/reports`
+
### Get a list of reports
-- Method `GET`
+
- Params:
- *optional* `state`: **string** the state of reports. Valid values are `open`, `closed` and `resolved`
- *optional* `limit`: **integer** the number of records to retrieve
```json
{
- "total" : 1,
+ "totalReports" : 1,
"reports": [
{
"account": {
}
```
-## `/api/pleroma/admin/reports/:id`
+## `GET /api/pleroma/admin/grouped_reports`
+
+### Get a list of reports, grouped by status
+
+- Params: none
+- On success: JSON, returns a list of reports, where:
+ - `date`: date of the latest report
+ - `account`: the user who has been reported (see `/api/pleroma/admin/reports` for reference)
+ - `status`: reported status (see `/api/pleroma/admin/reports` for reference)
+ - `actors`: users who had reported this status (see `/api/pleroma/admin/reports` for reference)
+ - `reports`: reports (see `/api/pleroma/admin/reports` for reference)
+
+```json
+ "reports": [
+ {
+ "date": "2019-10-07T12:31:39.615149Z",
+ "account": { ... },
+ "status": { ... },
+ "actors": [{ ... }, { ... }],
+ "reports": [{ ... }]
+ }
+ ]
+```
+
+## `GET /api/pleroma/admin/reports/:id`
+
### Get an individual report
-- Method `GET`
+
- Params:
- `id`
- Response:
- 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`
+## `PATCH /api/pleroma/admin/reports`
+
+### Change the state of one or multiple reports
+
- Params:
- - `id`
- - `state`: required, the new state. Valid values are `open`, `closed` and `resolved`
+
+```json
+ `reports`: [
+ {
+ `id`, // required, report 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)
+ - 400 Bad Request, JSON:
+
+ ```json
+ [
+ {
+ `id`, // report id
+ `error` // error message
+ }
+ ]
+ ```
+
+ - On success: `204`, empty response
+
+## `POST /api/pleroma/admin/reports/:id/respond`
-## `/api/pleroma/admin/reports/:id/respond`
### Respond to a report
-- Method `POST`
+
- Params:
- `id`
- `status`: required, the message
}
```
-## `/api/pleroma/admin/statuses/:id`
+## `PUT /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`
- 404 Not Found `"Not found"`
- On success: JSON, Mastodon Status entity
-## `/api/pleroma/admin/statuses/:id`
+## `DELETE /api/pleroma/admin/statuses/:id`
+
### Delete an individual reported status
-- Method `DELETE`
+
- Params:
- `id`
- Response:
- 404 Not Found `"Not found"`
- On success: 200 OK `{}`
+## `GET /api/pleroma/admin/config/migrate_to_db`
-## `/api/pleroma/admin/config/migrate_to_db`
### Run mix task pleroma.config migrate_to_db
+
Copy settings on key `:pleroma` to DB.
-- Method `GET`
+
- Params: none
- Response:
{}
```
-## `/api/pleroma/admin/config/migrate_from_db`
+## `GET /api/pleroma/admin/config/migrate_from_db`
+
### Run mix task pleroma.config migrate_from_db
+
Copy all settings from DB to `config/prod.exported_from_db.secret.exs` with deletion from DB.
+
- Method `GET`
- Params: none
- Response:
{}
```
-## `/api/pleroma/admin/config`
+## `GET /api/pleroma/admin/config`
+
### List config settings
+
List config settings only works with `:pleroma => :instance => :dynamic_configuration` setting to `true`.
-- Method `GET`
+
- Params: none
- Response:
}
```
-## `/api/pleroma/admin/config`
+## `POST /api/pleroma/admin/config`
+
### Update config settings
+
Updating config settings only works with `:pleroma => :instance => :dynamic_configuration` setting to `true`.
Module name can be passed as string, which starts with `Pleroma`, e.g. `"Pleroma.Upload"`.
Atom keys and values can be passed with `:` in the beginning, e.g. `":upload"`.
- `Pleroma.Upload` -> `:proxy_remote`
- `:instance` -> `:upload_limit`
-- Method `POST`
- Params:
- `configs` => [
- `group` (string)
}
```
-## `/api/pleroma/admin/moderation_log`
+## `GET /api/pleroma/admin/moderation_log`
+
### Get moderation log
-- Method `GET`
+
- Params:
- *optional* `page`: **integer** page number
- *optional* `page_size`: **integer** number of log entries per page (default is `50`)
```
## `POST /api/pleroma/admin/reload_emoji`
+
### Reload the instance's custom emoji
-* Method `POST`
-* Authentication: required
-* Params: None
-* Response: JSON, "ok" and 200 status
+
+- Authentication: required
+- Params: None
+- Response: JSON, "ok" and 200 status
alias Ecto.Changeset
alias Ecto.UUID
alias Pleroma.Activity
+ alias Pleroma.Activity.Queries
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web
+ alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Router.Helpers
require Pleroma.Constants
@supported_object_types ["Article", "Note", "Video", "Page", "Question", "Answer", "Audio"]
+ @strip_status_report_states ~w(closed resolved)
@supported_report_states ~w(open closed resolved)
@valid_visibilities ~w(public unlisted private direct)
def determine_explicit_mentions(_), do: []
- @spec recipient_in_collection(any(), any()) :: boolean()
- defp recipient_in_collection(ap_id, coll) when is_binary(coll), do: ap_id == coll
- defp recipient_in_collection(ap_id, coll) when is_list(coll), do: ap_id in coll
- defp recipient_in_collection(_, _), do: false
+ @spec label_in_collection?(any(), any()) :: boolean()
+ defp label_in_collection?(ap_id, coll) when is_binary(coll), do: ap_id == coll
+ defp label_in_collection?(ap_id, coll) when is_list(coll), do: ap_id in coll
+ defp label_in_collection?(_, _), do: false
+
+ @spec label_in_message?(String.t(), map()) :: boolean()
+ def label_in_message?(label, params),
+ do:
+ [params["to"], params["cc"], params["bto"], params["bcc"]]
+ |> Enum.any?(&label_in_collection?(label, &1))
+
+ @spec unaddressed_message?(map()) :: boolean()
+ def unaddressed_message?(params),
+ do:
+ [params["to"], params["cc"], params["bto"], params["bcc"]]
+ |> Enum.all?(&is_nil(&1))
@spec recipient_in_message(User.t(), User.t(), map()) :: boolean()
- def recipient_in_message(%User{ap_id: ap_id} = recipient, %User{} = actor, params) do
- addresses = [params["to"], params["cc"], params["bto"], params["bcc"]]
-
- cond do
- Enum.any?(addresses, &recipient_in_collection(ap_id, &1)) -> true
- # if the message is unaddressed at all, then assume it is directly addressed
- # to the recipient
- Enum.all?(addresses, &is_nil(&1)) -> true
- # if the message is sent from somebody the user is following, then assume it
- # is addressed to the recipient
- User.following?(recipient, actor) -> true
- true -> false
- end
- end
+ def recipient_in_message(%User{ap_id: ap_id} = recipient, %User{} = actor, params),
+ do:
+ label_in_message?(ap_id, params) || unaddressed_message?(params) ||
+ User.following?(recipient, actor)
defp extract_list(target) when is_binary(target), do: [target]
defp extract_list(lst) when is_list(lst), do: lst
def maybe_splice_recipient(ap_id, params) do
need_splice? =
- !recipient_in_collection(ap_id, params["to"]) &&
- !recipient_in_collection(ap_id, params["cc"])
+ !label_in_collection?(ap_id, params["to"]) &&
+ !label_in_collection?(ap_id, params["cc"])
if need_splice? do
cc_list = extract_list(params["cc"])
%Activity{data: %{"actor" => actor}},
object
) do
- announcements = take_announcements(object)
+ unless actor |> User.get_cached_by_ap_id() |> User.invisible?() do
+ announcements = take_announcements(object)
- with announcements <- Enum.uniq([actor | announcements]) do
- update_element_in_object("announcement", announcements, object)
+ with announcements <- Enum.uniq([actor | announcements]) do
+ update_element_in_object("announcement", announcements, object)
+ end
+ else
+ {:ok, object}
end
end
defp build_flag_object(%{account: account, statuses: statuses} = _) do
[account.ap_id] ++
- Enum.map(statuses || [], fn
- %Activity{} = act -> act.data["id"]
- act when is_map(act) -> act["id"]
- act when is_binary(act) -> act
+ Enum.map(statuses || [], fn act ->
+ id =
+ case act do
+ %Activity{} = act -> act.data["id"]
+ act when is_map(act) -> act["id"]
+ act when is_binary(act) -> act
+ end
+
+ activity = Activity.get_by_ap_id_with_object(id)
+ actor = User.get_by_ap_id(activity.object.data["actor"])
+
+ %{
+ "type" => "Note",
+ "id" => activity.data["id"],
+ "content" => activity.object.data["content"],
+ "published" => activity.object.data["published"],
+ "actor" => AccountView.render("show.json", %{user: actor})
+ }
end)
end
end
#### Report-related helpers
-
+ def get_reports(params, page, page_size) do
+ params =
+ params
+ |> Map.put("type", "Flag")
+ |> Map.put("skip_preload", true)
+ |> Map.put("total", true)
+ |> Map.put("limit", page_size)
+ |> Map.put("offset", (page - 1) * page_size)
+
+ ActivityPub.fetch_activities([], params, :offset)
+ end
+
+ @spec get_reports_grouped_by_status() :: %{
+ required(:groups) => [
+ %{
+ required(:date) => String.t(),
+ required(:account) => %User{},
+ required(:status) => %Activity{},
+ required(:actors) => [%User{}],
+ required(:reports) => [%Activity{}]
+ }
+ ],
+ required(:total) => integer
+ }
+ def get_reports_grouped_by_status do
+ paginated_activities = get_reported_status_ids()
+
+ groups =
+ paginated_activities
+ |> Enum.map(fn entry ->
+ status =
+ Activity
+ |> Queries.by_ap_id(entry[:activity_id])
+ |> Activity.with_preloaded_object(:left)
+ |> Activity.with_preloaded_user_actor()
+ |> Repo.one()
+
+ reports = get_reports_by_status_id(status.data["id"])
+
+ max_date =
+ Enum.max_by(reports, &Pleroma.Web.CommonAPI.Utils.to_masto_date(&1.data["published"])).data[
+ "published"
+ ]
+
+ actors = Enum.map(reports, & &1.user_actor)
+
+ %{
+ date: max_date,
+ account: status.user_actor,
+ status: status,
+ actors: actors,
+ reports: reports
+ }
+ end)
+
+ %{
+ groups: groups
+ }
+ end
+
+ def get_reports_by_status_id(status_id) do
+ from(a in Activity,
+ where: fragment("(?)->>'type' = 'Flag'", a.data),
+ where: fragment("(?)->'object' \\? (?)", a.data, ^status_id)
+ )
+ |> Activity.with_preloaded_user_actor()
+ |> Repo.all()
+ end
+
+ @spec get_reported_status_ids() :: %{
+ required(:items) => [%Activity{}],
+ required(:total) => integer
+ }
+ def get_reported_status_ids do
+ from(a in Activity,
+ where: fragment("(?)->>'type' = 'Flag'", a.data),
+ select: %{
+ date: fragment("max(?->>'published') date", a.data),
+ activity_id:
+ fragment("jsonb_array_elements_text((? #- '{object,0}')->'object') activity_id", a.data)
+ },
+ group_by: fragment("activity_id"),
+ order_by: fragment("date DESC")
+ )
+ |> Repo.all()
+ end
+ def update_report_state(%Activity{} = activity, state)
+ when state in @strip_status_report_states do
+ {:ok, stripped_activity} = strip_report_status_data(activity)
+
+ new_data =
+ activity.data
+ |> Map.put("state", state)
+ |> Map.put("object", stripped_activity.data["object"])
+
+ activity
+ |> Changeset.change(data: new_data)
+ |> Repo.update()
+ end
+
def update_report_state(%Activity{} = activity, state) when state in @supported_report_states do
new_data = Map.put(activity.data, "state", state)
|> Repo.update()
end
+ def update_report_state(activity_ids, state) when state in @supported_report_states do
+ activities_num = length(activity_ids)
+
+ from(a in Activity, where: a.id in ^activity_ids)
+ |> update(set: [data: fragment("jsonb_set(data, '{state}', ?)", ^state)])
+ |> Repo.update_all([])
+ |> case do
+ {^activities_num, _} -> :ok
+ _ -> {:error, activity_ids}
+ end
+ end
+
def update_report_state(_, _), do: {:error, "Unsupported state"}
+ def strip_report_status_data(activity) do
+ [actor | reported_activities] = activity.data["object"]
+ stripped_activities = Enum.map(reported_activities, & &1["id"])
+ new_data = put_in(activity.data, ["object"], [actor | stripped_activities])
+
+ {:ok, %{activity | data: new_data}}
+ end
+
def update_activity_visibility(activity, visibility) when visibility in @valid_visibilities do
[to, cc, recipients] =
activity
alias Pleroma.UserInviteToken
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Relay
+ alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.AdminAPI.Config
alias Pleroma.Web.AdminAPI.ConfigView
:user_delete,
:users_create,
:user_toggle_activation,
+ :user_activate,
+ :user_deactivate,
:tag_users,
:untag_users,
:right_add,
- :right_delete,
- :set_activation_status
+ :right_delete
]
)
ModerationLog.insert_log(%{
actor: admin,
- subject: user,
+ subject: [user],
action: "delete"
})
|> json(nickname)
end
+ def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
+ User.delete(users)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "delete"
+ })
+
+ conn
+ |> json(nicknames)
+ end
+
def user_follow(%{assigns: %{user: admin}} = conn, %{
"follower" => follower_nick,
"followed" => followed_nick
def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
user = User.get_cached_by_nickname(nickname)
- {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
+ {:ok, updated_user} = User.deactivate(user, !user.deactivated)
- action = if user.info.deactivated, do: "activate", else: "deactivate"
+ action = if user.deactivated, do: "activate", else: "deactivate"
ModerationLog.insert_log(%{
actor: admin,
- subject: user,
+ subject: [user],
action: action
})
|> render("show.json", %{user: updated_user})
end
+ def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+ {:ok, updated_users} = User.deactivate(users, false)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "activate"
+ })
+
+ conn
+ |> put_view(AccountView)
+ |> render("index.json", %{users: Keyword.values(updated_users)})
+ end
+
+ def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+ {:ok, updated_users} = User.deactivate(users, true)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "deactivate"
+ })
+
+ conn
+ |> put_view(AccountView)
+ |> render("index.json", %{users: Keyword.values(updated_users)})
+ end
+
def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
with {:ok, _} <- User.tag(nicknames, tags) do
ModerationLog.insert_log(%{
}
with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
+ {:ok, users, count} <- filter_relay_user(users, count),
do:
conn
|> json(
)
end
+ defp filter_relay_user(users, count) do
+ filtered_users = Enum.reject(users, &relay_user?/1)
+ count = if Enum.any?(users, &relay_user?/1), do: length(filtered_users), else: count
+
+ {:ok, filtered_users, count}
+ end
+
+ defp relay_user?(user) do
+ user.ap_id == Relay.relay_ap_id()
+ end
+
@filters ~w(local external active deactivated is_admin is_moderator)
@spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
|> Enum.into(%{}, &{&1, true})
end
+ def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
+ "permission_group" => permission_group,
+ "nicknames" => nicknames
+ })
+ when permission_group in ["moderator", "admin"] do
+ update = %{:"is_#{permission_group}" => true}
+
+ users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
+
+ for u <- users, do: User.admin_api_update(u, update)
+
+ ModerationLog.insert_log(%{
+ action: "grant",
+ actor: admin,
+ subject: users,
+ permission: permission_group
+ })
+
+ json(conn, update)
+ end
+
+ def right_add_multiple(conn, _) do
+ render_error(conn, :not_found, "No such permission_group")
+ end
+
def right_add(%{assigns: %{user: admin}} = conn, %{
"permission_group" => permission_group,
"nickname" => nickname
})
when permission_group in ["moderator", "admin"] do
- info = Map.put(%{}, "is_" <> permission_group, true)
+ fields = %{:"is_#{permission_group}" => true}
{:ok, user} =
nickname
|> User.get_cached_by_nickname()
- |> User.update_info(&User.Info.admin_api_update(&1, info))
+ |> User.admin_api_update(fields)
ModerationLog.insert_log(%{
action: "grant",
actor: admin,
- subject: user,
+ subject: [user],
permission: permission_group
})
- json(conn, info)
+ json(conn, fields)
end
def right_add(conn, _) do
conn
|> json(%{
- is_moderator: user.info.is_moderator,
- is_admin: user.info.is_admin
+ is_moderator: user.is_moderator,
+ is_admin: user.is_admin
})
end
- def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
- render_error(conn, :forbidden, "You can't revoke your own admin status.")
+ def right_delete_multiple(
+ %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
+ %{
+ "permission_group" => permission_group,
+ "nicknames" => nicknames
+ }
+ )
+ when permission_group in ["moderator", "admin"] do
+ with false <- Enum.member?(nicknames, admin_nickname) do
+ update = %{:"is_#{permission_group}" => false}
+
+ users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
+
+ for u <- users, do: User.admin_api_update(u, update)
+
+ ModerationLog.insert_log(%{
+ action: "revoke",
+ actor: admin,
+ subject: users,
+ permission: permission_group
+ })
+
+ json(conn, update)
+ else
+ _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
+ end
+ end
+
+ def right_delete_multiple(conn, _) do
+ render_error(conn, :not_found, "No such permission_group")
end
def right_delete(
}
)
when permission_group in ["moderator", "admin"] do
- info = Map.put(%{}, "is_" <> permission_group, false)
+ fields = %{:"is_#{permission_group}" => false}
{:ok, user} =
nickname
|> User.get_cached_by_nickname()
- |> User.update_info(&User.Info.admin_api_update(&1, info))
+ |> User.admin_api_update(fields)
ModerationLog.insert_log(%{
action: "revoke",
actor: admin,
- subject: user,
+ subject: [user],
permission: permission_group
})
- json(conn, info)
+ json(conn, fields)
end
- def right_delete(conn, _) do
- render_error(conn, :not_found, "No such permission_group")
+ def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
+ render_error(conn, :forbidden, "You can't revoke your own admin status.")
end
- def set_activation_status(%{assigns: %{user: admin}} = conn, %{
- "nickname" => nickname,
- "status" => status
- }) do
- with {:ok, status} <- Ecto.Type.cast(:boolean, status),
- %User{} = user <- User.get_cached_by_nickname(nickname),
- {:ok, _} <- User.deactivate(user, !status) do
- action = if(user.info.deactivated, do: "activate", else: "deactivate")
-
- ModerationLog.insert_log(%{
- actor: admin,
- subject: user,
- action: action
- })
-
- json_response(conn, :no_content, "")
+ def relay_list(conn, _params) do
+ with {:ok, list} <- Relay.list() do
+ json(conn, %{relays: list})
+ else
+ _ ->
+ conn
+ |> put_status(500)
end
end
def list_reports(conn, params) do
{page, page_size} = page_params(params)
- params =
- params
- |> Map.put("type", "Flag")
- |> Map.put("skip_preload", true)
- |> Map.put("total", true)
- |> Map.put("limit", page_size)
- |> Map.put("offset", (page - 1) * page_size)
-
- reports = ActivityPub.fetch_activities([], params, :offset)
+ conn
+ |> put_view(ReportView)
+ |> render("index.json", %{reports: Utils.get_reports(params, page, page_size)})
+ end
+ def list_grouped_reports(conn, _params) do
conn
|> put_view(ReportView)
- |> render("index.json", %{reports: reports})
+ |> render("index_grouped.json", Utils.get_reports_grouped_by_status())
end
def report_show(conn, %{"id" => id}) do
end
end
- def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
- with {:ok, report} <- CommonAPI.update_report_state(id, state) do
- ModerationLog.insert_log(%{
- action: "report_update",
- actor: admin,
- subject: report
- })
+ def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
+ result =
+ reports
+ |> Enum.map(fn report ->
+ with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
+ ModerationLog.insert_log(%{
+ action: "report_update",
+ actor: admin,
+ subject: activity
+ })
+
+ activity
+ else
+ {:error, message} -> %{id: report["id"], error: message}
+ end
+ end)
- conn
- |> put_view(ReportView)
- |> render("show.json", Report.extract_report_info(report))
+ case Enum.any?(result, &Map.has_key?(&1, :error)) do
+ true -> json_response(conn, :bad_request, result)
+ false -> json_response(conn, :no_content, "")
end
end
alias Pleroma.Activity
alias Pleroma.ActivityExpiration
alias Pleroma.Conversation.Participation
+ alias Pleroma.FollowingRelationship
alias Pleroma.Object
alias Pleroma.ThreadMute
alias Pleroma.User
with {:ok, follower} <- User.follow(follower, followed),
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
+ {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept"),
{:ok, _activity} <-
ActivityPub.accept(%{
to: [follower.ap_id],
def reject_follow_request(follower, followed) do
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
+ {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
{:ok, _activity} <-
ActivityPub.reject(%{
to: [follower.ap_id],
# Updates the emojis for a user based on their profile
def update(user) do
emoji = emoji_from_profile(user)
- source_data = user.info |> Map.get(:source_data, %{}) |> Map.put("tag", emoji)
+ source_data = Map.put(user.source_data, "tag", emoji)
user =
- case User.update_info(user, &User.Info.set_source_data(&1, source_data)) do
+ case User.update_source_data(user, source_data) do
{:ok, user} -> user
_ -> user
end
object: %Object{data: %{"type" => "Note"}}
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
true <- Visibility.is_public?(activity),
- {:ok, _user} <- User.update_info(user, &User.Info.add_pinnned_activity(&1, activity)) do
+ {:ok, _user} <- User.add_pinnned_activity(user, activity) do
{:ok, activity}
else
- {:error, %{changes: %{info: %{errors: [pinned_activities: {err, _}]}}}} -> {:error, err}
+ {:error, %{errors: [pinned_activities: {err, _}]}} -> {:error, err}
_ -> {:error, dgettext("errors", "Could not pin")}
end
end
def unpin(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
- {:ok, _user} <- User.update_info(user, &User.Info.remove_pinnned_activity(&1, activity)) do
+ {:ok, _user} <- User.remove_pinnned_activity(user, activity) do
{:ok, activity}
else
- %{errors: [pinned_activities: {err, _}]} -> {:error, err}
+ {:error, %{errors: [pinned_activities: {err, _}]}} -> {:error, err}
_ -> {:error, dgettext("errors", "Could not unpin")}
end
end
end
end
+ def update_report_state(activity_ids, state) when is_list(activity_ids) do
+ case Utils.update_report_state(activity_ids, state) do
+ :ok -> {:ok, activity_ids}
+ _ -> {:error, dgettext("errors", "Could not update state")}
+ end
+ end
+
def update_report_state(activity_id, state) do
with %Activity{} = activity <- Activity.get_by_id(activity_id) do
Utils.update_report_state(activity, state)
defp set_visibility(activity, _), do: {:ok, activity}
def hide_reblogs(user, %{ap_id: ap_id} = _muted) do
- if ap_id not in user.info.muted_reblogs do
- User.update_info(user, &User.Info.add_reblog_mute(&1, ap_id))
+ if ap_id not in user.muted_reblogs do
+ User.add_reblog_mute(user, ap_id)
end
end
def show_reblogs(user, %{ap_id: ap_id} = _muted) do
- if ap_id in user.info.muted_reblogs do
- User.update_info(user, &User.Info.remove_reblog_mute(&1, ap_id))
+ if ap_id in user.muted_reblogs do
+ User.remove_reblog_mute(user, ap_id)
end
end
end
delete("/users", AdminAPIController, :user_delete)
post("/users", AdminAPIController, :users_create)
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
+ patch("/users/activate", AdminAPIController, :user_activate)
+ patch("/users/deactivate", AdminAPIController, :user_deactivate)
put("/users/tag", AdminAPIController, :tag_users)
delete("/users/tag", AdminAPIController, :untag_users)
get("/users/:nickname/permission_group", AdminAPIController, :right_get)
get("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_get)
+
post("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_add)
delete(
:right_delete
)
- put("/users/:nickname/activation_status", AdminAPIController, :set_activation_status)
+ post("/users/permission_group/:permission_group", AdminAPIController, :right_add_multiple)
+
+ delete(
+ "/users/permission_group/:permission_group",
+ AdminAPIController,
+ :right_delete_multiple
+ )
+ get("/relay", AdminAPIController, :relay_list)
post("/relay", AdminAPIController, :relay_follow)
delete("/relay", AdminAPIController, :relay_unfollow)
get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
get("/reports", AdminAPIController, :list_reports)
+ get("/grouped_reports", AdminAPIController, :list_grouped_reports)
get("/reports/:id", AdminAPIController, :report_show)
- put("/reports/:id", AdminAPIController, :report_update_state)
+ patch("/reports", AdminAPIController, :reports_update)
post("/reports/:id/respond", AdminAPIController, :report_respond)
put("/statuses/:id", AdminAPIController, :status_update)
get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses)
get("/conversations/:id", PleromaAPIController, :conversation)
+ post("/conversations/read", PleromaAPIController, :read_conversations)
end
scope [] do
get("/push/subscription", SubscriptionController, :get)
put("/push/subscription", SubscriptionController, :update)
delete("/push/subscription", SubscriptionController, :delete)
+
+ get("/markers", MarkerController, :index)
+ post("/markers", MarkerController, :upsert)
end
scope "/api/web", Pleroma.Web do
get("/users/:nickname/feed", Feed.FeedController, :feed)
get("/users/:nickname", Feed.FeedController, :feed_redirect)
- post("/users/:nickname/salmon", OStatus.OStatusController, :salmon_incoming)
- post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request)
- get("/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation)
- post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming)
-
get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe)
end
get("/:version", Nodeinfo.NodeinfoController, :nodeinfo)
end
+ scope "/", Pleroma.Web do
+ pipe_through(:api)
+
+ get("/web/manifest.json", MastoFEController, :manifest)
+ end
+
scope "/", Pleroma.Web do
pipe_through(:mastodon_html)
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
alias Pleroma.UserInviteToken
+ alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MediaProxy
import Pleroma.Factory
- describe "/api/pleroma/admin/users" do
- test "Delete" do
- admin = insert(:user, info: %{is_admin: true})
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+ :ok
+ end
+
+ describe "DELETE /api/pleroma/admin/users" do
+ test "single user" do
+ admin = insert(:user, is_admin: true)
user = insert(:user)
conn =
log_entry = Repo.one(ModerationLog)
- assert log_entry.data["subject"]["nickname"] == user.nickname
- assert log_entry.data["action"] == "delete"
-
assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} deleted user @#{user.nickname}"
+ "@#{admin.nickname} deleted users: @#{user.nickname}"
assert json_response(conn, 200) == user.nickname
end
+ test "multiple users" do
+ admin = insert(:user, is_admin: true)
+ user_one = insert(:user)
+ user_two = insert(:user)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> put_req_header("accept", "application/json")
+ |> delete("/api/pleroma/admin/users", %{
+ nicknames: [user_one.nickname, user_two.nickname]
+ })
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} deleted users: @#{user_one.nickname}, @#{user_two.nickname}"
+
+ response = json_response(conn, 200)
+ assert response -- [user_one.nickname, user_two.nickname] == []
+ end
+ end
+
+ describe "/api/pleroma/admin/users" do
test "Create" do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
conn =
build_conn()
end
test "Cannot create user with exisiting email" do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
user = insert(:user)
conn =
end
test "Cannot create user with exisiting nickname" do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
user = insert(:user)
conn =
end
test "Multiple user creation works in transaction" do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
user = insert(:user)
conn =
describe "/api/pleroma/admin/users/:nickname" do
test "Show", %{conn: conn} do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
user = insert(:user)
conn =
end
test "when the user doesn't exist", %{conn: conn} do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
user = build(:user)
conn =
describe "/api/pleroma/admin/users/follow" do
test "allows to force-follow another user" do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
user = insert(:user)
follower = insert(:user)
describe "/api/pleroma/admin/users/unfollow" do
test "allows to force-unfollow another user" do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
user = insert(:user)
follower = insert(:user)
describe "PUT /api/pleroma/admin/users/tag" do
setup do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
user1 = insert(:user, %{tags: ["x"]})
user2 = insert(:user, %{tags: ["y"]})
user3 = insert(:user, %{tags: ["unchanged"]})
describe "DELETE /api/pleroma/admin/users/tag" do
setup do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
user1 = insert(:user, %{tags: ["x"]})
user2 = insert(:user, %{tags: ["y", "z"]})
user3 = insert(:user, %{tags: ["unchanged"]})
describe "/api/pleroma/admin/users/:nickname/permission_group" do
test "GET is giving user_info" do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
conn =
build_conn()
end
test "/:right POST, can add to a permission group" do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
user = insert(:user)
conn =
"@#{admin.nickname} made @#{user.nickname} admin"
end
- test "/:right DELETE, can remove from a permission group" do
- admin = insert(:user, info: %{is_admin: true})
- user = insert(:user, info: %{is_admin: true})
+ test "/:right POST, can add to a permission group (multiple)" do
+ admin = insert(:user, is_admin: true)
+ user_one = insert(:user)
+ user_two = insert(:user)
conn =
build_conn()
|> assign(:user, admin)
|> put_req_header("accept", "application/json")
- |> delete("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin")
+ |> post("/api/pleroma/admin/users/permission_group/admin", %{
+ nicknames: [user_one.nickname, user_two.nickname]
+ })
assert json_response(conn, 200) == %{
- "is_admin" => false
+ "is_admin" => true
}
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} revoked admin role from @#{user.nickname}"
+ "@#{admin.nickname} made @#{user_one.nickname}, @#{user_two.nickname} admin"
end
- end
- describe "PUT /api/pleroma/admin/users/:nickname/activation_status" do
- setup %{conn: conn} do
- admin = insert(:user, info: %{is_admin: true})
+ test "/:right DELETE, can remove from a permission group" do
+ admin = insert(:user, is_admin: true)
+ user = insert(:user, is_admin: true)
conn =
- conn
+ build_conn()
|> assign(:user, admin)
|> put_req_header("accept", "application/json")
+ |> delete("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin")
- %{conn: conn, admin: admin}
- end
-
- test "deactivates the user", %{conn: conn, admin: admin} do
- user = insert(:user)
-
- conn =
- conn
- |> put("/api/pleroma/admin/users/#{user.nickname}/activation_status", %{status: false})
-
- user = User.get_cached_by_id(user.id)
- assert user.info.deactivated == true
- assert json_response(conn, :no_content)
+ assert json_response(conn, 200) == %{
+ "is_admin" => false
+ }
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} deactivated user @#{user.nickname}"
+ "@#{admin.nickname} revoked admin role from @#{user.nickname}"
end
- test "activates the user", %{conn: conn, admin: admin} do
- user = insert(:user, info: %{deactivated: true})
+ test "/:right DELETE, can remove from a permission group (multiple)" do
+ admin = insert(:user, is_admin: true)
+ user_one = insert(:user, is_admin: true)
+ user_two = insert(:user, is_admin: true)
conn =
- conn
- |> put("/api/pleroma/admin/users/#{user.nickname}/activation_status", %{status: true})
+ build_conn()
+ |> assign(:user, admin)
+ |> put_req_header("accept", "application/json")
+ |> delete("/api/pleroma/admin/users/permission_group/admin", %{
+ nicknames: [user_one.nickname, user_two.nickname]
+ })
- user = User.get_cached_by_id(user.id)
- assert user.info.deactivated == false
- assert json_response(conn, :no_content)
+ assert json_response(conn, 200) == %{
+ "is_admin" => false
+ }
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} activated user @#{user.nickname}"
- end
-
- test "returns 403 when requested by a non-admin", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> assign(:user, user)
- |> put("/api/pleroma/admin/users/#{user.nickname}/activation_status", %{status: false})
-
- assert json_response(conn, :forbidden)
+ "@#{admin.nickname} revoked admin role from @#{user_one.nickname}, @#{
+ user_two.nickname
+ }"
end
end
describe "POST /api/pleroma/admin/email_invite, with valid config" do
setup do
- [user: insert(:user, info: %{is_admin: true})]
+ [user: insert(:user, is_admin: true)]
end
clear_config([:instance, :registrations_open]) do
describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do
setup do
- [user: insert(:user, info: %{is_admin: true})]
+ [user: insert(:user, is_admin: true)]
end
clear_config([:instance, :registrations_open])
end
test "/api/pleroma/admin/users/:nickname/password_reset" do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
user = insert(:user)
conn =
describe "GET /api/pleroma/admin/users" do
setup do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
conn =
build_conn()
users =
[
%{
- "deactivated" => admin.info.deactivated,
+ "deactivated" => admin.deactivated,
"id" => admin.id,
"nickname" => admin.nickname,
"roles" => %{"admin" => true, "moderator" => false},
"display_name" => HTML.strip_tags(admin.name || admin.nickname)
},
%{
- "deactivated" => user.info.deactivated,
+ "deactivated" => user.deactivated,
"id" => user.id,
"nickname" => user.nickname,
"roles" => %{"admin" => false, "moderator" => false},
"page_size" => 50,
"users" => [
%{
- "deactivated" => user.info.deactivated,
+ "deactivated" => user.deactivated,
"id" => user.id,
"nickname" => user.nickname,
"roles" => %{"admin" => false, "moderator" => false},
"page_size" => 50,
"users" => [
%{
- "deactivated" => user.info.deactivated,
+ "deactivated" => user.deactivated,
"id" => user.id,
"nickname" => user.nickname,
"roles" => %{"admin" => false, "moderator" => false},
"page_size" => 50,
"users" => [
%{
- "deactivated" => user.info.deactivated,
+ "deactivated" => user.deactivated,
"id" => user.id,
"nickname" => user.nickname,
"roles" => %{"admin" => false, "moderator" => false},
"page_size" => 50,
"users" => [
%{
- "deactivated" => user.info.deactivated,
+ "deactivated" => user.deactivated,
"id" => user.id,
"nickname" => user.nickname,
"roles" => %{"admin" => false, "moderator" => false},
"page_size" => 50,
"users" => [
%{
- "deactivated" => user.info.deactivated,
+ "deactivated" => user.deactivated,
"id" => user.id,
"nickname" => user.nickname,
"roles" => %{"admin" => false, "moderator" => false},
"page_size" => 1,
"users" => [
%{
- "deactivated" => user.info.deactivated,
+ "deactivated" => user.deactivated,
"id" => user.id,
"nickname" => user.nickname,
"roles" => %{"admin" => false, "moderator" => false},
"page_size" => 1,
"users" => [
%{
- "deactivated" => user2.info.deactivated,
+ "deactivated" => user2.deactivated,
"id" => user2.id,
"nickname" => user2.nickname,
"roles" => %{"admin" => false, "moderator" => false},
end
test "only local users" do
- admin = insert(:user, info: %{is_admin: true}, nickname: "john")
+ admin = insert(:user, is_admin: true, nickname: "john")
user = insert(:user, nickname: "bob")
insert(:user, nickname: "bobb", local: false)
"page_size" => 50,
"users" => [
%{
- "deactivated" => user.info.deactivated,
+ "deactivated" => user.deactivated,
"id" => user.id,
"nickname" => user.nickname,
"roles" => %{"admin" => false, "moderator" => false},
end
test "only local users with no query", %{admin: old_admin} do
- admin = insert(:user, info: %{is_admin: true}, nickname: "john")
+ admin = insert(:user, is_admin: true, nickname: "john")
user = insert(:user, nickname: "bob")
insert(:user, nickname: "bobb", local: false)
users =
[
%{
- "deactivated" => user.info.deactivated,
+ "deactivated" => user.deactivated,
"id" => user.id,
"nickname" => user.nickname,
"roles" => %{"admin" => false, "moderator" => false},
"display_name" => HTML.strip_tags(user.name || user.nickname)
},
%{
- "deactivated" => admin.info.deactivated,
+ "deactivated" => admin.deactivated,
"id" => admin.id,
"nickname" => admin.nickname,
"roles" => %{"admin" => true, "moderator" => false},
end
test "load only admins", %{conn: conn, admin: admin} do
- second_admin = insert(:user, info: %{is_admin: true})
+ second_admin = insert(:user, is_admin: true)
insert(:user)
insert(:user)
end
test "load only moderators", %{conn: conn} do
- moderator = insert(:user, info: %{is_moderator: true})
+ moderator = insert(:user, is_moderator: true)
insert(:user)
insert(:user)
end
test "it works with multiple filters" do
- admin = insert(:user, nickname: "john", info: %{is_admin: true})
- user = insert(:user, nickname: "bob", local: false, info: %{deactivated: true})
+ admin = insert(:user, nickname: "john", is_admin: true)
+ user = insert(:user, nickname: "bob", local: false, deactivated: true)
- insert(:user, nickname: "ken", local: true, info: %{deactivated: true})
- insert(:user, nickname: "bobb", local: false, info: %{deactivated: false})
+ insert(:user, nickname: "ken", local: true, deactivated: true)
+ insert(:user, nickname: "bobb", local: false, deactivated: false)
conn =
build_conn()
"page_size" => 50,
"users" => [
%{
- "deactivated" => user.info.deactivated,
+ "deactivated" => user.deactivated,
"id" => user.id,
"nickname" => user.nickname,
"roles" => %{"admin" => false, "moderator" => false},
]
}
end
+
+ test "it omits relay user", %{admin: admin} do
+ assert %User{} = Relay.get_actor()
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/users")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [
+ %{
+ "deactivated" => admin.deactivated,
+ "id" => admin.id,
+ "nickname" => admin.nickname,
+ "roles" => %{"admin" => true, "moderator" => false},
+ "local" => true,
+ "tags" => [],
+ "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(admin.name || admin.nickname)
+ }
+ ]
+ }
+ end
+ end
+
+ test "PATCH /api/pleroma/admin/users/activate" do
+ admin = insert(:user, is_admin: true)
+ user_one = insert(:user, deactivated: true)
+ user_two = insert(:user, deactivated: true)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> patch(
+ "/api/pleroma/admin/users/activate",
+ %{nicknames: [user_one.nickname, user_two.nickname]}
+ )
+
+ response = json_response(conn, 200)
+ assert Enum.map(response["users"], & &1["deactivated"]) == [false, false]
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} activated users: @#{user_one.nickname}, @#{user_two.nickname}"
+ end
+
+ test "PATCH /api/pleroma/admin/users/deactivate" do
+ admin = insert(:user, is_admin: true)
+ user_one = insert(:user, deactivated: false)
+ user_two = insert(:user, deactivated: false)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> patch(
+ "/api/pleroma/admin/users/deactivate",
+ %{nicknames: [user_one.nickname, user_two.nickname]}
+ )
+
+ response = json_response(conn, 200)
+ assert Enum.map(response["users"], & &1["deactivated"]) == [true, true]
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}"
end
test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
user = insert(:user)
conn =
assert json_response(conn, 200) ==
%{
- "deactivated" => !user.info.deactivated,
+ "deactivated" => !user.deactivated,
"id" => user.id,
"nickname" => user.nickname,
"roles" => %{"admin" => false, "moderator" => false},
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} deactivated user @#{user.nickname}"
+ "@#{admin.nickname} deactivated users: @#{user.nickname}"
end
describe "POST /api/pleroma/admin/users/invite_token" do
setup do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
conn =
build_conn()
describe "GET /api/pleroma/admin/users/invites" do
setup do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
conn =
build_conn()
describe "POST /api/pleroma/admin/users/revoke_invite" do
test "with token" do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
{:ok, invite} = UserInviteToken.create_invite()
conn =
end
test "with invalid token" do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
conn =
build_conn()
describe "GET /api/pleroma/admin/reports/:id" do
setup %{conn: conn} do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
%{conn: assign(conn, :user, admin)}
end
end
end
- describe "PUT /api/pleroma/admin/reports/:id" do
+ describe "PATCH /api/pleroma/admin/reports" do
setup %{conn: conn} do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
[reporter, target_user] = insert_pair(:user)
activity = insert(:note_activity, user: target_user)
"status_ids" => [activity.id]
})
- %{conn: assign(conn, :user, admin), id: report_id, admin: admin}
+ {:ok, %{id: second_report_id}} =
+ CommonAPI.report(reporter, %{
+ "account_id" => target_user.id,
+ "comment" => "I feel very offended",
+ "status_ids" => [activity.id]
+ })
+
+ %{
+ conn: assign(conn, :user, admin),
+ id: report_id,
+ admin: admin,
+ second_report_id: second_report_id
+ }
end
test "mark report as resolved", %{conn: conn, id: id, admin: admin} do
- response =
- conn
- |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "resolved"})
- |> json_response(:ok)
+ conn
+ |> patch("/api/pleroma/admin/reports", %{
+ "reports" => [
+ %{"state" => "resolved", "id" => id}
+ ]
+ })
+ |> json_response(:no_content)
- assert response["state"] == "resolved"
+ activity = Activity.get_by_id(id)
+ assert activity.data["state"] == "resolved"
log_entry = Repo.one(ModerationLog)
end
test "closes report", %{conn: conn, id: id, admin: admin} do
- response =
- conn
- |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "closed"})
- |> json_response(:ok)
+ conn
+ |> patch("/api/pleroma/admin/reports", %{
+ "reports" => [
+ %{"state" => "closed", "id" => id}
+ ]
+ })
+ |> json_response(:no_content)
- assert response["state"] == "closed"
+ activity = Activity.get_by_id(id)
+ assert activity.data["state"] == "closed"
log_entry = Repo.one(ModerationLog)
test "returns 400 when state is unknown", %{conn: conn, id: id} do
conn =
conn
- |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "test"})
+ |> patch("/api/pleroma/admin/reports", %{
+ "reports" => [
+ %{"state" => "test", "id" => id}
+ ]
+ })
- assert json_response(conn, :bad_request) == "Unsupported state"
+ assert hd(json_response(conn, :bad_request))["error"] == "Unsupported state"
end
test "returns 404 when report is not exist", %{conn: conn} do
conn =
conn
- |> put("/api/pleroma/admin/reports/test", %{"state" => "closed"})
+ |> patch("/api/pleroma/admin/reports", %{
+ "reports" => [
+ %{"state" => "closed", "id" => "test"}
+ ]
+ })
- assert json_response(conn, :not_found) == "Not found"
+ assert hd(json_response(conn, :bad_request))["error"] == "not_found"
+ end
+
+ test "updates state of multiple reports", %{
+ conn: conn,
+ id: id,
+ admin: admin,
+ second_report_id: second_report_id
+ } do
+ conn
+ |> patch("/api/pleroma/admin/reports", %{
+ "reports" => [
+ %{"state" => "resolved", "id" => id},
+ %{"state" => "closed", "id" => second_report_id}
+ ]
+ })
+ |> json_response(:no_content)
+
+ activity = Activity.get_by_id(id)
+ second_activity = Activity.get_by_id(second_report_id)
+ assert activity.data["state"] == "resolved"
+ assert second_activity.data["state"] == "closed"
+
+ [first_log_entry, second_log_entry] = Repo.all(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(first_log_entry) ==
+ "@#{admin.nickname} updated report ##{id} with 'resolved' state"
+
+ assert ModerationLog.get_log_entry_message(second_log_entry) ==
+ "@#{admin.nickname} updated report ##{second_report_id} with 'closed' state"
end
end
describe "GET /api/pleroma/admin/reports" do
setup %{conn: conn} do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
%{conn: assign(conn, :user, admin)}
end
end
end
- #
+ describe "GET /api/pleroma/admin/grouped_reports" do
+ setup %{conn: conn} do
+ admin = insert(:user, info: %{is_admin: true})
+ [reporter, target_user] = insert_pair(:user)
+
+ date1 = (DateTime.to_unix(DateTime.utc_now()) + 1000) |> DateTime.from_unix!()
+ date2 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!()
+ date3 = (DateTime.to_unix(DateTime.utc_now()) + 3000) |> DateTime.from_unix!()
+
+ first_status =
+ insert(:note_activity, user: target_user, data_attrs: %{"published" => date1})
+
+ second_status =
+ insert(:note_activity, user: target_user, data_attrs: %{"published" => date2})
+
+ third_status =
+ insert(:note_activity, user: target_user, data_attrs: %{"published" => date3})
+
+ %{
+ conn: assign(conn, :user, admin),
+ reporter: reporter,
+ target_user: target_user,
+ first_status: first_status,
+ second_status: second_status,
+ third_status: third_status
+ }
+ end
+
+ test "returns reports grouped by status", %{
+ conn: conn,
+ reporter: reporter,
+ target_user: target_user,
+ first_status: first_status,
+ second_status: second_status,
+ third_status: third_status
+ } do
+ {:ok, %{id: _}} =
+ CommonAPI.report(reporter, %{
+ "account_id" => target_user.id,
+ "status_ids" => [first_status.id, second_status.id, third_status.id]
+ })
+
+ {:ok, %{id: _}} =
+ CommonAPI.report(reporter, %{
+ "account_id" => target_user.id,
+ "status_ids" => [first_status.id, second_status.id]
+ })
+
+ {:ok, %{id: _}} =
+ CommonAPI.report(reporter, %{
+ "account_id" => target_user.id,
+ "status_ids" => [first_status.id]
+ })
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/grouped_reports")
+ |> json_response(:ok)
+
+ assert length(response["reports"]) == 3
+ [third_group, second_group, first_group] = response["reports"]
+
+ assert length(third_group["reports"]) == 3
+ assert length(second_group["reports"]) == 2
+ assert length(first_group["reports"]) == 1
+ end
+ end
+
describe "POST /api/pleroma/admin/reports/:id/respond" do
setup %{conn: conn} do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
%{conn: assign(conn, :user, admin), admin: admin}
end
describe "PUT /api/pleroma/admin/statuses/:id" do
setup %{conn: conn} do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
activity = insert(:note_activity)
%{conn: assign(conn, :user, admin), id: activity.id, admin: admin}
describe "DELETE /api/pleroma/admin/statuses/:id" do
setup %{conn: conn} do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
activity = insert(:note_activity)
%{conn: assign(conn, :user, admin), id: activity.id, admin: admin}
describe "GET /api/pleroma/admin/config" do
setup %{conn: conn} do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
%{conn: assign(conn, :user, admin)}
end
describe "POST /api/pleroma/admin/config" do
setup %{conn: conn} do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
temp_file = "config/test.exported_from_db.secret.exs"
describe "config mix tasks run" do
setup %{conn: conn} do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
temp_file = "config/test.exported_from_db.secret.exs"
describe "GET /api/pleroma/admin/users/:nickname/statuses" do
setup do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
user = insert(:user)
date1 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!()
describe "GET /api/pleroma/admin/moderation_log" do
setup %{conn: conn} do
- admin = insert(:user, info: %{is_admin: true})
- moderator = insert(:user, info: %{is_moderator: true})
+ admin = insert(:user, is_admin: true)
+ moderator = insert(:user, is_moderator: true)
%{conn: assign(conn, :user, admin), admin: admin, moderator: moderator}
end
describe "PATCH /users/:nickname/force_password_reset" do
setup %{conn: conn} do
- admin = insert(:user, info: %{is_admin: true})
+ admin = insert(:user, is_admin: true)
user = insert(:user)
%{conn: assign(conn, :user, admin), admin: admin, user: user}
end
test "sets password_reset_pending to true", %{admin: admin, user: user} do
- assert user.info.password_reset_pending == false
+ assert user.password_reset_pending == false
conn =
build_conn()
ObanHelpers.perform_all()
- assert User.get_by_id(user.id).info.password_reset_pending == true
+ assert User.get_by_id(user.id).password_reset_pending == true
+ end
+ end
+
+ describe "relays" do
+ setup %{conn: conn} do
+ admin = insert(:user, is_admin: true)
+
+ %{conn: assign(conn, :user, admin), admin: admin}
+ end
+
+ test "POST /relay", %{admin: admin} do
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> post("/api/pleroma/admin/relay", %{
+ relay_url: "http://mastodon.example.org/users/admin"
+ })
+
+ assert json_response(conn, 200) == "http://mastodon.example.org/users/admin"
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin"
+ end
+
+ test "GET /relay", %{admin: admin} do
+ relay_user = Pleroma.Web.ActivityPub.Relay.get_actor()
+
+ ["http://mastodon.example.org/users/admin", "https://mstdn.io/users/mayuutann"]
+ |> Enum.each(fn ap_id ->
+ {:ok, user} = User.get_or_fetch_by_ap_id(ap_id)
+ User.follow(relay_user, user)
+ end)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/relay")
+
+ assert json_response(conn, 200)["relays"] -- ["mastodon.example.org", "mstdn.io"] == []
+ end
+
+ test "DELETE /relay", %{admin: admin} do
+ build_conn()
+ |> assign(:user, admin)
+ |> post("/api/pleroma/admin/relay", %{
+ relay_url: "http://mastodon.example.org/users/admin"
+ })
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> delete("/api/pleroma/admin/relay", %{
+ relay_url: "http://mastodon.example.org/users/admin"
+ })
+
+ assert json_response(conn, 200) == "http://mastodon.example.org/users/admin"
+
+ [log_entry_one, log_entry_two] = Repo.all(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry_one) ==
+ "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin"
+
+ assert ModerationLog.get_log_entry_message(log_entry_two) ==
+ "@#{admin.nickname} unfollowed relay: http://mastodon.example.org/users/admin"
end
end
end
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
{:ok, activity} = CommonAPI.update(user)
user = User.get_cached_by_ap_id(user.ap_id)
- [firefox] = user.info.source_data["tag"]
+ [firefox] = user.source_data["tag"]
assert firefox["name"] == ":firefox:"
object = Object.normalize(activity)
- assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
+ assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
end
test "it filters out obviously bad tags when accepting a post as Markdown" do
object = Object.normalize(activity)
- assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
+ assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
end
test "it does not allow replies to direct messages that are not direct messages themselves" do
id = activity.id
user = refresh_record(user)
- assert %User{info: %{pinned_activities: [^id]}} = user
+ assert %User{pinned_activities: [^id]} = user
end
test "unlisted statuses can be pinned", %{user: user} do
user = refresh_record(user)
- assert %User{info: %{pinned_activities: []}} = user
+ assert %User{pinned_activities: []} = user
end
test "should unpin when deleting a status", %{user: user, activity: activity} do
user = refresh_record(user)
- assert %User{info: %{pinned_activities: []}} = user
+ assert %User{pinned_activities: []} = user
end
end
"status_ids" => [activity.id]
}
+ note_obj = %{
+ "type" => "Note",
+ "id" => activity_ap_id,
+ "content" => "foobar",
+ "published" => activity.object.data["published"],
+ "actor" => AccountView.render("show.json", %{user: target_user})
+ }
+
assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
assert %Activity{
data: %{
"type" => "Flag",
"content" => ^comment,
- "object" => [^target_ap_id, ^activity_ap_id],
+ "object" => [^target_ap_id, ^note_obj],
"state" => "open"
}
} = flag_activity
{:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
assert report.data["state"] == "resolved"
+
+ [reported_user, activity_id] = report.data["object"]
+
+ assert reported_user == target_user.ap_id
+ assert activity_id == activity.data["id"]
end
test "does not update report state when state is unsupported" do
assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
end
+
+ test "updates state of multiple reports" do
+ [reporter, target_user] = insert_pair(:user)
+ activity = insert(:note_activity, user: target_user)
+
+ {:ok, %Activity{id: first_report_id}} =
+ CommonAPI.report(reporter, %{
+ "account_id" => target_user.id,
+ "comment" => "I feel offended",
+ "status_ids" => [activity.id]
+ })
+
+ {:ok, %Activity{id: second_report_id}} =
+ CommonAPI.report(reporter, %{
+ "account_id" => target_user.id,
+ "comment" => "I feel very offended!",
+ "status_ids" => [activity.id]
+ })
+
+ {:ok, report_ids} =
+ CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
+
+ first_report = Activity.get_by_id(first_report_id)
+ second_report = Activity.get_by_id(second_report_id)
+
+ assert report_ids -- [first_report_id, second_report_id] == []
+ assert first_report.data["state"] == "resolved"
+ assert second_report.data["state"] == "resolved"
+ end
end
describe "reblog muting" do
describe "accept_follow_request/2" do
test "after acceptance, it sets all existing pending follow request states to 'accept'" do
- user = insert(:user, info: %{locked: true})
+ user = insert(:user, locked: true)
follower = insert(:user)
follower_two = insert(:user)
end
test "after rejection, it sets all existing pending follow request states to 'reject'" do
- user = insert(:user, info: %{locked: true})
+ user = insert(:user, locked: true)
follower = insert(:user)
follower_two = insert(:user)