forked from AkkomaGang/akkoma
Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into ecto_sql_update
This commit is contained in:
commit
67af50ec71
69 changed files with 1961 additions and 568 deletions
|
@ -52,6 +52,7 @@ unit-testing:
|
||||||
- mix ecto.create
|
- mix ecto.create
|
||||||
- mix ecto.migrate
|
- mix ecto.migrate
|
||||||
- mix test --trace --preload-modules
|
- mix test --trace --preload-modules
|
||||||
|
- mix coveralls
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
stage: test
|
stage: test
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -98,7 +104,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Mastodon API: Make `irreversible` field default to `false` [`POST /api/v1/filters`]
|
- Mastodon API: Make `irreversible` field default to `false` [`POST /api/v1/filters`]
|
||||||
|
|
||||||
## Removed
|
## Removed
|
||||||
- Configuration: `config :pleroma, :fe` in favor of the more flexible `config :pleroma, :frontend_configurations`
|
- Configuration: `config :pleroma, :fe` in favor of the more flexible `config :pleroma, :frontend_configurations`
|
||||||
|
|
||||||
## [0.9.9999] - 2019-04-05
|
## [0.9.9999] - 2019-04-05
|
||||||
### Security
|
### Security
|
||||||
|
|
|
@ -406,8 +406,7 @@
|
||||||
mailer: 10,
|
mailer: 10,
|
||||||
transmogrifier: 20,
|
transmogrifier: 20,
|
||||||
scheduled_activities: 10,
|
scheduled_activities: 10,
|
||||||
background: 5,
|
background: 5
|
||||||
user: 10
|
|
||||||
|
|
||||||
config :pleroma, :fetch_initial_posts,
|
config :pleroma, :fetch_initial_posts,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
@ -466,6 +465,9 @@
|
||||||
token_expires_in: 600,
|
token_expires_in: 600,
|
||||||
issue_new_refresh_token: true
|
issue_new_refresh_token: true
|
||||||
|
|
||||||
|
config :http_signatures,
|
||||||
|
adapter: Pleroma.Signature
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
# of this file so it overrides the configuration defined above.
|
# of this file so it overrides the configuration defined above.
|
||||||
import_config "#{Mix.env()}.exs"
|
import_config "#{Mix.env()}.exs"
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -106,15 +106,15 @@ Authentication is required and the user must be an admin.
|
||||||
|
|
||||||
- Method: `PUT`
|
- Method: `PUT`
|
||||||
- Params:
|
- Params:
|
||||||
- `nickname`
|
- `nicknames` (array)
|
||||||
- `tags`
|
- `tags` (array)
|
||||||
|
|
||||||
### Untag a list of users
|
### Untag a list of users
|
||||||
|
|
||||||
- Method: `DELETE`
|
- Method: `DELETE`
|
||||||
- Params:
|
- Params:
|
||||||
- `nickname`
|
- `nicknames` (array)
|
||||||
- `tags`
|
- `tags` (array)
|
||||||
|
|
||||||
## `/api/pleroma/admin/users/:nickname/permission_group`
|
## `/api/pleroma/admin/users/:nickname/permission_group`
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ This guide will assume you are on Debian Stretch. This guide should also work wi
|
||||||
* `erlang-tools`
|
* `erlang-tools`
|
||||||
* `erlang-parsetools`
|
* `erlang-parsetools`
|
||||||
* `erlang-eldap`, if you want to enable ldap authenticator
|
* `erlang-eldap`, if you want to enable ldap authenticator
|
||||||
|
* `erlang-ssh`
|
||||||
* `erlang-xmerl`
|
* `erlang-xmerl`
|
||||||
* `git`
|
* `git`
|
||||||
* `build-essential`
|
* `build-essential`
|
||||||
|
@ -49,7 +50,7 @@ sudo dpkg -i /tmp/erlang-solutions_1.0_all.deb
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install elixir erlang-dev erlang-parsetools erlang-xmerl erlang-tools
|
sudo apt install elixir erlang-dev erlang-parsetools erlang-xmerl erlang-tools erlang-ssh
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install PleromaBE
|
### Install PleromaBE
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
- erlang-dev
|
- erlang-dev
|
||||||
- erlang-tools
|
- erlang-tools
|
||||||
- erlang-parsetools
|
- erlang-parsetools
|
||||||
|
- erlang-ssh
|
||||||
- erlang-xmerl (Jessieではバックポートからインストールすること!)
|
- erlang-xmerl (Jessieではバックポートからインストールすること!)
|
||||||
- git
|
- git
|
||||||
- build-essential
|
- build-essential
|
||||||
|
@ -44,7 +45,7 @@ wget -P /tmp/ https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb
|
||||||
|
|
||||||
* ElixirとErlangをインストールします、
|
* ElixirとErlangをインストールします、
|
||||||
```
|
```
|
||||||
apt update && apt install elixir erlang-dev erlang-parsetools erlang-xmerl erlang-tools
|
apt update && apt install elixir erlang-dev erlang-parsetools erlang-xmerl erlang-tools erlang-ssh
|
||||||
```
|
```
|
||||||
|
|
||||||
### Pleroma BE (バックエンド) をインストールします
|
### Pleroma BE (バックエンド) をインストールします
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
# Introduction to Pleroma
|
# Introduction to Pleroma
|
||||||
## What is Pleroma?
|
## What is Pleroma?
|
||||||
Pleroma is a federated social networking platform, compatible with GNU social, Mastodon and other OStatus and ActivityPub implementations. It is free software licensed under the AGPLv3.
|
Pleroma is a federated social networking platform, compatible with GNU social, Mastodon and other OStatus and ActivityPub implementations. It is free software licensed under the AGPLv3.
|
||||||
It actually consists of two components: a backend, named simply Pleroma, and a user-facing frontend, named Pleroma-FE. It also includes the Mastodon frontend, if that's your thing.
|
It actually consists of two components: a backend, named simply Pleroma, and a user-facing frontend, named Pleroma-FE. It also includes the Mastodon frontend, if that's your thing.
|
||||||
It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other.
|
It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other.
|
||||||
One account on a instance is enough to talk to the entire fediverse!
|
One account on a instance is enough to talk to the entire fediverse!
|
||||||
|
|
||||||
## How can I use it?
|
## How can I use it?
|
||||||
|
|
||||||
Pleroma instances are already widely deployed, a list can be found here:
|
Pleroma instances are already widely deployed, a list can be found here:
|
||||||
http://distsn.org/pleroma-instances.html
|
http://distsn.org/pleroma-instances.html
|
||||||
|
|
||||||
If you don't feel like joining an existing instance, but instead prefer to deploy your own instance, that's easy too!
|
If you don't feel like joining an existing instance, but instead prefer to deploy your own instance, that's easy too!
|
||||||
Installation instructions can be found here:
|
Installation instructions can be found here:
|
||||||
[main Pleroma wiki](/)
|
[main Pleroma wiki](/)
|
||||||
|
|
||||||
## I got an account, now what?
|
## I got an account, now what?
|
||||||
Great! Now you can explore the fediverse!
|
Great! Now you can explore the fediverse!
|
||||||
- Open the login page for your Pleroma instance (for ex. https://pleroma.soykaf.com) and login with your username and password.
|
- Open the login page for your Pleroma instance (for ex. https://pleroma.soykaf.com) and login with your username and password.
|
||||||
(If you don't have one yet, click on Register) :slightly_smiling_face:
|
(If you don't have one yet, click on Register) :slightly_smiling_face:
|
||||||
|
|
||||||
At this point you will have two columns in front of you.
|
At this point you will have two columns in front of you.
|
||||||
|
|
||||||
### Left column
|
### Left column
|
||||||
- first block: here you can see your avatar, your nickname a bio, and statistics (Statuses, Following, Followers).
|
- first block: here you can see your avatar, your nickname a bio, and statistics (Statuses, Following, Followers).
|
||||||
Under that you have a text form which allows you to post new statuses. The icon on the left is for uploading media files and attach them to your post. The number under the text form is a character counter, every instance can have a different character limit (the default is 5000).
|
Under that you have a text form which allows you to post new statuses. The icon on the left is for uploading media files and attach them to your post. The number under the text form is a character counter, every instance can have a different character limit (the default is 5000).
|
||||||
If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person. :slight_smile:
|
If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person. :slight_smile:
|
||||||
To post your status, simply press Submit.
|
To post your status, simply press Submit.
|
||||||
|
|
||||||
- second block: Here you can switch between the different timelines:
|
- second block: Here you can switch between the different timelines:
|
||||||
|
@ -38,7 +38,7 @@ To post your status, simply press Submit.
|
||||||
- fourth block: This is the Notifications block, here you will get notified whenever somebody mentions you, follows you, repeats or favorites one of your statuses.
|
- fourth block: This is the Notifications block, here you will get notified whenever somebody mentions you, follows you, repeats or favorites one of your statuses.
|
||||||
|
|
||||||
### Right column
|
### Right column
|
||||||
This is where the interesting stuff happens! :slight_smile:
|
This is where the interesting stuff happens! :slight_smile:
|
||||||
Depending on the timeline you will see different statuses, but each status has a standard structure:
|
Depending on the timeline you will see different statuses, but each status has a standard structure:
|
||||||
- Icon + name + link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the replied-to status).
|
- Icon + name + link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the replied-to status).
|
||||||
- A + button on the right allows you to Expand/Collapse an entire discussion thread. It also updates in realtime!
|
- A + button on the right allows you to Expand/Collapse an entire discussion thread. It also updates in realtime!
|
||||||
|
@ -47,9 +47,9 @@ Depending on the timeline you will see different statuses, but each status has a
|
||||||
- Four buttons (left to right): Reply, Repeat, Favorite, Delete.
|
- Four buttons (left to right): Reply, Repeat, Favorite, Delete.
|
||||||
|
|
||||||
## Mastodon interface
|
## Mastodon interface
|
||||||
If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too! :smile:
|
If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too! :smile:
|
||||||
Just add a "/web" after your instance url (for ex. https://pleroma.soycaf.com/web) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC! :fireworks:
|
Just add a "/web" after your instance url (for ex. https://pleroma.soycaf.com/web) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC! :fireworks:
|
||||||
For more information on the Mastodon interface, please look here:
|
For more information on the Mastodon interface, please look here:
|
||||||
https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md
|
https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md
|
||||||
|
|
||||||
Remember, what you see is only the frontend part of Mastodon, the backend is still Pleroma.
|
Remember, what you see is only the frontend part of Mastodon, the backend is still Pleroma.
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|> preload([activity, object], object: object)
|
end
|
||||||
|
|
||||||
|
def with_preloaded_object(query) do
|
||||||
|
query
|
||||||
|
|> has_named_binding?(:object)
|
||||||
|
|> if(do: query, else: with_joined_object(query))
|
||||||
|
|> preload([activity, object: object], object: object)
|
||||||
end
|
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)
|
||||||
|
|
31
lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex
Normal file
31
lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug do
|
||||||
|
import Plug.Conn
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
def init(options) do
|
||||||
|
options
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(conn, _) do
|
||||||
|
public? = Config.get!([:instance, :public])
|
||||||
|
|
||||||
|
case {public?, conn} do
|
||||||
|
{true, _} ->
|
||||||
|
conn
|
||||||
|
|
||||||
|
{false, %{assigns: %{user: %User{}}}} ->
|
||||||
|
conn
|
||||||
|
|
||||||
|
{false, _} ->
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/json")
|
||||||
|
|> send_resp(403, Jason.encode!(%{error: "This resource requires authentication."}))
|
||||||
|
|> halt
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.HTTPSignatures
|
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
|
41
lib/pleroma/signature.ex
Normal file
41
lib/pleroma/signature.ex
Normal file
|
@ -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.Signature do
|
||||||
|
@behaviour HTTPSignatures.Adapter
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
alias Pleroma.Web.Salmon
|
||||||
|
alias Pleroma.Web.WebFinger
|
||||||
|
|
||||||
|
def fetch_public_key(conn) do
|
||||||
|
with actor_id <- Utils.get_ap_id(conn.params["actor"]),
|
||||||
|
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||||
|
{:ok, public_key}
|
||||||
|
else
|
||||||
|
e ->
|
||||||
|
{:error, e}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def refetch_public_key(conn) do
|
||||||
|
with actor_id <- Utils.get_ap_id(conn.params["actor"]),
|
||||||
|
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
|
||||||
|
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||||
|
{:ok, public_key}
|
||||||
|
else
|
||||||
|
e ->
|
||||||
|
{:error, e}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def sign(%User{} = user, headers) do
|
||||||
|
with {:ok, %{info: %{keys: keys}}} <- WebFinger.ensure_keys_present(user),
|
||||||
|
{:ok, private_key, _} <- Salmon.keys_from_pem(keys) do
|
||||||
|
HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers)
|
||||||
|
end
|
||||||
|
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}
|
||||||
|
@ -1378,4 +1390,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)
|
||||||
|
|
||||||
|
|
|
@ -540,8 +540,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}")
|
||||||
|
@ -557,8 +555,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
|
||||||
|
|
||||||
|
@ -569,6 +565,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
|
||||||
|
@ -695,6 +703,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,
|
||||||
|
@ -750,8 +764,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:
|
||||||
|
@ -761,7 +778,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
|
||||||
|
|
||||||
|
@ -843,11 +861,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)
|
||||||
|
@ -966,11 +986,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)
|
||||||
|
|
|
@ -54,7 +54,7 @@ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = pa
|
||||||
|> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
|
|> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
|
||||||
|
|
||||||
signature =
|
signature =
|
||||||
Pleroma.Web.HTTPSignatures.sign(actor, %{
|
Pleroma.Signature.sign(actor, %{
|
||||||
host: host,
|
host: host,
|
||||||
"content-length": byte_size(json),
|
"content-length": byte_size(json),
|
||||||
digest: digest,
|
digest: digest,
|
||||||
|
|
|
@ -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,24 +40,37 @@ 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
|
end
|
||||||
|
|
||||||
def entire_thread_visible_for_user?(
|
def get_visibility(object) do
|
||||||
%Activity{} = tail,
|
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||||
# %Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,
|
to = object.data["to"] || []
|
||||||
user
|
cc = object.data["cc"] || []
|
||||||
) 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)
|
|
||||||
|
|
||||||
_ ->
|
cond do
|
||||||
visible_for_user?(tail, user)
|
public in to ->
|
||||||
|
"public"
|
||||||
|
|
||||||
|
public in cc ->
|
||||||
|
"unlisted"
|
||||||
|
|
||||||
|
# this should use the sql for the object's activity
|
||||||
|
Enum.any?(to, &String.contains?(&1, "/followers")) ->
|
||||||
|
"private"
|
||||||
|
|
||||||
|
length(cc) > 0 ->
|
||||||
|
"private"
|
||||||
|
|
||||||
|
true ->
|
||||||
|
"direct"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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)
|
||||||
|
|
41
lib/pleroma/web/admin_api/views/report_view.ex
Normal file
41
lib/pleroma/web/admin_api/views/report_view.ex
Normal file
|
@ -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
|
||||||
|
|
||||||
|
@ -116,32 +119,34 @@ def unfavorite(id_or_ap_id, user) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_visibility(%{"visibility" => visibility})
|
def get_visibility(%{"visibility" => visibility}, in_reply_to)
|
||||||
when visibility in ~w{public unlisted private direct},
|
when visibility in ~w{public unlisted private direct},
|
||||||
do: visibility
|
do: {visibility, get_replied_to_visibility(in_reply_to)}
|
||||||
|
|
||||||
def get_visibility(%{"in_reply_to_status_id" => status_id}) when not is_nil(status_id) do
|
def get_visibility(_, in_reply_to) when not is_nil(in_reply_to) do
|
||||||
case get_replied_to_activity(status_id) do
|
visibility = get_replied_to_visibility(in_reply_to)
|
||||||
nil ->
|
{visibility, visibility}
|
||||||
"public"
|
end
|
||||||
|
|
||||||
in_reply_to ->
|
def get_visibility(_, in_reply_to), do: {"public", get_replied_to_visibility(in_reply_to)}
|
||||||
# XXX: these heuristics should be moved out of MastodonAPI.
|
|
||||||
with %Object{} = object <- Object.normalize(in_reply_to) do
|
def get_replied_to_visibility(nil), do: nil
|
||||||
Pleroma.Web.MastodonAPI.StatusView.get_visibility(object)
|
|
||||||
end
|
def get_replied_to_visibility(activity) do
|
||||||
|
with %Object{} = object <- Object.normalize(activity) do
|
||||||
|
Pleroma.Web.ActivityPub.Visibility.get_visibility(object)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_visibility(_), do: "public"
|
|
||||||
|
|
||||||
def post(user, %{"status" => status} = data) do
|
def post(user, %{"status" => status} = data) do
|
||||||
visibility = get_visibility(data)
|
|
||||||
limit = Pleroma.Config.get([:instance, :limit])
|
limit = Pleroma.Config.get([:instance, :limit])
|
||||||
|
|
||||||
with status <- String.trim(status),
|
with status <- String.trim(status),
|
||||||
attachments <- attachments_from_ids(data),
|
attachments <- attachments_from_ids(data),
|
||||||
in_reply_to <- get_replied_to_activity(data["in_reply_to_status_id"]),
|
in_reply_to <- get_replied_to_activity(data["in_reply_to_status_id"]),
|
||||||
|
{visibility, in_reply_to_visibility} <- get_visibility(data, in_reply_to),
|
||||||
|
{_, false} <-
|
||||||
|
{:private_to_public, in_reply_to_visibility == "direct" && visibility != "direct"},
|
||||||
{content_html, mentions, tags} <-
|
{content_html, mentions, tags} <-
|
||||||
make_content_html(
|
make_content_html(
|
||||||
status,
|
status,
|
||||||
|
@ -185,6 +190,8 @@ def post(user, %{"status" => status} = data) do
|
||||||
)
|
)
|
||||||
|
|
||||||
res
|
res
|
||||||
|
else
|
||||||
|
e -> {:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -193,7 +200,7 @@ def update(user) do
|
||||||
user =
|
user =
|
||||||
with emoji <- emoji_from_profile(user),
|
with emoji <- emoji_from_profile(user),
|
||||||
source_data <- (user.info.source_data || %{}) |> Map.put("tag", emoji),
|
source_data <- (user.info.source_data || %{}) |> Map.put("tag", emoji),
|
||||||
info_cng <- Pleroma.User.Info.set_source_data(user.info, source_data),
|
info_cng <- User.Info.set_source_data(user.info, source_data),
|
||||||
change <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
|
change <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
|
||||||
{:ok, user} <- User.update_and_set_cache(change) do
|
{:ok, user} <- User.update_and_set_cache(change) do
|
||||||
user
|
user
|
||||||
|
@ -226,7 +233,7 @@ def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
|
||||||
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||||
true <- Enum.member?(object_to, "https://www.w3.org/ns/activitystreams#Public"),
|
true <- Enum.member?(object_to, "https://www.w3.org/ns/activitystreams#Public"),
|
||||||
%{valid?: true} = info_changeset <-
|
%{valid?: true} = info_changeset <-
|
||||||
Pleroma.User.Info.add_pinnned_activity(user.info, activity),
|
User.Info.add_pinnned_activity(user.info, activity),
|
||||||
changeset <-
|
changeset <-
|
||||||
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
|
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
|
||||||
{:ok, _user} <- User.update_and_set_cache(changeset) do
|
{:ok, _user} <- User.update_and_set_cache(changeset) do
|
||||||
|
@ -243,7 +250,7 @@ def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
|
||||||
def unpin(id_or_ap_id, user) do
|
def unpin(id_or_ap_id, user) do
|
||||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||||
%{valid?: true} = info_changeset <-
|
%{valid?: true} = info_changeset <-
|
||||||
Pleroma.User.Info.remove_pinnned_activity(user.info, activity),
|
User.Info.remove_pinnned_activity(user.info, activity),
|
||||||
changeset <-
|
changeset <-
|
||||||
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
|
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
|
||||||
{:ok, _user} <- User.update_and_set_cache(changeset) do
|
{:ok, _user} <- User.update_and_set_cache(changeset) do
|
||||||
|
@ -311,6 +318,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
|
||||||
|
|
||||||
|
|
|
@ -237,13 +237,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
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ defmodule Pleroma.Web.Federator.Publisher do
|
||||||
"""
|
"""
|
||||||
@spec enqueue_one(module(), Map.t()) :: :ok
|
@spec enqueue_one(module(), Map.t()) :: :ok
|
||||||
def enqueue_one(module, %{} = params),
|
def enqueue_one(module, %{} = params),
|
||||||
do: PleromaJobQueue.enqueue(:federation_outgoing, __MODULE__, [:publish_one, module, params])
|
do: PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_one, module, params])
|
||||||
|
|
||||||
@spec perform(atom(), module(), any()) :: {:ok, any()} | {:error, any()}
|
@spec perform(atom(), module(), any()) :: {:ok, any()} | {:error, any()}
|
||||||
def perform(:publish_one, module, params) do
|
def perform(:publish_one, module, params) do
|
||||||
|
@ -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 ->
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
# https://tools.ietf.org/html/draft-cavage-http-signatures-08
|
|
||||||
defmodule Pleroma.Web.HTTPSignatures do
|
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
|
||||||
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
def split_signature(sig) do
|
|
||||||
default = %{"headers" => "date"}
|
|
||||||
|
|
||||||
sig =
|
|
||||||
sig
|
|
||||||
|> String.trim()
|
|
||||||
|> String.split(",")
|
|
||||||
|> Enum.reduce(default, fn part, acc ->
|
|
||||||
[key | rest] = String.split(part, "=")
|
|
||||||
value = Enum.join(rest, "=")
|
|
||||||
Map.put(acc, key, String.trim(value, "\""))
|
|
||||||
end)
|
|
||||||
|
|
||||||
Map.put(sig, "headers", String.split(sig["headers"], ~r/\s/))
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate(headers, signature, public_key) do
|
|
||||||
sigstring = build_signing_string(headers, signature["headers"])
|
|
||||||
Logger.debug("Signature: #{signature["signature"]}")
|
|
||||||
Logger.debug("Sigstring: #{sigstring}")
|
|
||||||
{:ok, sig} = Base.decode64(signature["signature"])
|
|
||||||
:public_key.verify(sigstring, :sha256, sig, public_key)
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_conn(conn) do
|
|
||||||
# TODO: How to get the right key and see if it is actually valid for that request.
|
|
||||||
# For now, fetch the key for the actor.
|
|
||||||
with actor_id <- Utils.get_ap_id(conn.params["actor"]),
|
|
||||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
|
||||||
if validate_conn(conn, public_key) do
|
|
||||||
true
|
|
||||||
else
|
|
||||||
Logger.debug("Could not validate, re-fetching user and trying one more time")
|
|
||||||
# Fetch user anew and try one more time
|
|
||||||
with actor_id <- Utils.get_ap_id(conn.params["actor"]),
|
|
||||||
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
|
|
||||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
|
||||||
validate_conn(conn, public_key)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
_e ->
|
|
||||||
Logger.debug("Could not public key!")
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_conn(conn, public_key) do
|
|
||||||
headers = Enum.into(conn.req_headers, %{})
|
|
||||||
signature = split_signature(headers["signature"])
|
|
||||||
validate(headers, signature, public_key)
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_signing_string(headers, used_headers) do
|
|
||||||
used_headers
|
|
||||||
|> Enum.map(fn header -> "#{header}: #{headers[header]}" end)
|
|
||||||
|> Enum.join("\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
def sign(user, headers) do
|
|
||||||
with {:ok, %{info: %{keys: keys}}} <- Pleroma.Web.WebFinger.ensure_keys_present(user),
|
|
||||||
{:ok, private_key, _} = Pleroma.Web.Salmon.keys_from_pem(keys) do
|
|
||||||
sigstring = build_signing_string(headers, Map.keys(headers))
|
|
||||||
|
|
||||||
signature =
|
|
||||||
:public_key.sign(sigstring, :sha256, private_key)
|
|
||||||
|> Base.encode64()
|
|
||||||
|
|
||||||
[
|
|
||||||
keyId: user.ap_id <> "#main-key",
|
|
||||||
algorithm: "rsa-sha256",
|
|
||||||
headers: Map.keys(headers) |> Enum.join(" "),
|
|
||||||
signature: signature
|
|
||||||
]
|
|
||||||
|> Enum.map(fn {k, v} -> "#{k}=\"#{v}\"" end)
|
|
||||||
|> Enum.join(",")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -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)
|
||||||
|
|
|
@ -16,6 +16,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
|
|
||||||
|
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1]
|
||||||
|
|
||||||
# TODO: Add cached version.
|
# TODO: Add cached version.
|
||||||
defp get_replied_to_activities(activities) do
|
defp get_replied_to_activities(activities) do
|
||||||
activities
|
activities
|
||||||
|
@ -340,30 +342,6 @@ def get_reply_to(%{data: %{"object" => _object}} = activity, _) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_visibility(object) do
|
|
||||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
|
||||||
to = object.data["to"] || []
|
|
||||||
cc = object.data["cc"] || []
|
|
||||||
|
|
||||||
cond do
|
|
||||||
public in to ->
|
|
||||||
"public"
|
|
||||||
|
|
||||||
public in cc ->
|
|
||||||
"unlisted"
|
|
||||||
|
|
||||||
# this should use the sql for the object's activity
|
|
||||||
Enum.any?(to, &String.contains?(&1, "/followers")) ->
|
|
||||||
"private"
|
|
||||||
|
|
||||||
length(cc) > 0 ->
|
|
||||||
"private"
|
|
||||||
|
|
||||||
true ->
|
|
||||||
"direct"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_content(%{data: %{"type" => "Video"}} = object) do
|
def render_content(%{data: %{"type" => "Video"}} = object) do
|
||||||
with name when not is_nil(name) and name != "" <- object.data["name"] do
|
with name when not is_nil(name) and name != "" <- object.data["name"] do
|
||||||
"<p><a href=\"#{object.data["id"]}\">#{name}</a></p>#{object.data["content"]}"
|
"<p><a href=\"#{object.data["id"]}\">#{name}</a></p>#{object.data["content"]}"
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -84,11 +84,13 @@ defmodule Pleroma.Web.Router do
|
||||||
plug(Pleroma.Plugs.EnsureUserKeyPlug)
|
plug(Pleroma.Plugs.EnsureUserKeyPlug)
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :oauth_read_or_unauthenticated do
|
pipeline :oauth_read_or_public do
|
||||||
plug(Pleroma.Plugs.OAuthScopesPlug, %{
|
plug(Pleroma.Plugs.OAuthScopesPlug, %{
|
||||||
scopes: ["read"],
|
scopes: ["read"],
|
||||||
fallback: :proceed_unauthenticated
|
fallback: :proceed_unauthenticated
|
||||||
})
|
})
|
||||||
|
|
||||||
|
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :oauth_read do
|
pipeline :oauth_read do
|
||||||
|
@ -192,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
|
||||||
|
@ -404,7 +414,7 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/accounts/search", MastodonAPIController, :account_search)
|
get("/accounts/search", MastodonAPIController, :account_search)
|
||||||
|
|
||||||
scope [] do
|
scope [] do
|
||||||
pipe_through(:oauth_read_or_unauthenticated)
|
pipe_through(:oauth_read_or_public)
|
||||||
|
|
||||||
get("/timelines/public", MastodonAPIController, :public_timeline)
|
get("/timelines/public", MastodonAPIController, :public_timeline)
|
||||||
get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline)
|
get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline)
|
||||||
|
@ -425,7 +435,7 @@ defmodule Pleroma.Web.Router do
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/v2", Pleroma.Web.MastodonAPI do
|
scope "/api/v2", Pleroma.Web.MastodonAPI do
|
||||||
pipe_through([:api, :oauth_read_or_unauthenticated])
|
pipe_through([:api, :oauth_read_or_public])
|
||||||
get("/search", MastodonAPIController, :search2)
|
get("/search", MastodonAPIController, :search2)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -455,7 +465,7 @@ defmodule Pleroma.Web.Router do
|
||||||
)
|
)
|
||||||
|
|
||||||
scope [] do
|
scope [] do
|
||||||
pipe_through(:oauth_read_or_unauthenticated)
|
pipe_through(:oauth_read_or_public)
|
||||||
|
|
||||||
get("/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
|
get("/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
|
||||||
get("/qvitter/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
|
get("/qvitter/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
|
||||||
|
@ -473,7 +483,7 @@ defmodule Pleroma.Web.Router do
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api", Pleroma.Web do
|
scope "/api", Pleroma.Web do
|
||||||
pipe_through([:api, :oauth_read_or_unauthenticated])
|
pipe_through([:api, :oauth_read_or_public])
|
||||||
|
|
||||||
get("/statuses/public_timeline", TwitterAPI.Controller, :public_timeline)
|
get("/statuses/public_timeline", TwitterAPI.Controller, :public_timeline)
|
||||||
|
|
||||||
|
@ -487,7 +497,7 @@ defmodule Pleroma.Web.Router do
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api", Pleroma.Web, as: :twitter_api_search do
|
scope "/api", Pleroma.Web, as: :twitter_api_search do
|
||||||
pipe_through([:api, :oauth_read_or_unauthenticated])
|
pipe_through([:api, :oauth_read_or_public])
|
||||||
get("/pleroma/search_user", TwitterAPI.Controller, :search_user)
|
get("/pleroma/search_user", TwitterAPI.Controller, :search_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -671,7 +681,7 @@ defmodule Pleroma.Web.Router do
|
||||||
delete("/auth/sign_out", MastodonAPIController, :logout)
|
delete("/auth/sign_out", MastodonAPIController, :logout)
|
||||||
|
|
||||||
scope [] do
|
scope [] do
|
||||||
pipe_through(:oauth_read_or_unauthenticated)
|
pipe_through(:oauth_read_or_public)
|
||||||
get("/web/*path", MastodonAPIController, :index)
|
get("/web/*path", MastodonAPIController, :index)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -310,7 +310,7 @@ def render(
|
||||||
"tags" => tags,
|
"tags" => tags,
|
||||||
"activity_type" => "post",
|
"activity_type" => "post",
|
||||||
"possibly_sensitive" => possibly_sensitive,
|
"possibly_sensitive" => possibly_sensitive,
|
||||||
"visibility" => StatusView.get_visibility(object),
|
"visibility" => Pleroma.Web.ActivityPub.Visibility.get_visibility(object),
|
||||||
"summary" => summary,
|
"summary" => summary,
|
||||||
"summary_html" => summary |> Formatter.emojify(object.data["emoji"]),
|
"summary_html" => summary |> Formatter.emojify(object.data["emoji"]),
|
||||||
"card" => card,
|
"card" => card,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
7
mix.exs
7
mix.exs
|
@ -13,6 +13,7 @@ def project do
|
||||||
start_permanent: Mix.env() == :prod,
|
start_permanent: Mix.env() == :prod,
|
||||||
aliases: aliases(),
|
aliases: aliases(),
|
||||||
deps: deps(),
|
deps: deps(),
|
||||||
|
test_coverage: [tool: ExCoveralls],
|
||||||
|
|
||||||
# Docs
|
# Docs
|
||||||
name: "Pleroma",
|
name: "Pleroma",
|
||||||
|
@ -106,6 +107,9 @@ defp deps do
|
||||||
{:auto_linker,
|
{:auto_linker,
|
||||||
git: "https://git.pleroma.social/pleroma/auto_linker.git",
|
git: "https://git.pleroma.social/pleroma/auto_linker.git",
|
||||||
ref: "c00c4e75b35367fa42c95ffd9b8c455bf9995829"},
|
ref: "c00c4e75b35367fa42c95ffd9b8c455bf9995829"},
|
||||||
|
{:http_signatures,
|
||||||
|
git: "https://git.pleroma.social/pleroma/http_signatures.git",
|
||||||
|
ref: "9789401987096ead65646b52b5a2ca6bf52fc531"},
|
||||||
{:pleroma_job_queue, "~> 0.2.0"},
|
{:pleroma_job_queue, "~> 0.2.0"},
|
||||||
{:telemetry, "~> 0.3"},
|
{:telemetry, "~> 0.3"},
|
||||||
{:prometheus_ex, "~> 3.0"},
|
{:prometheus_ex, "~> 3.0"},
|
||||||
|
@ -118,7 +122,8 @@ defp deps do
|
||||||
{:benchee, "~> 1.0"},
|
{:benchee, "~> 1.0"},
|
||||||
{:esshd, "~> 0.1.0"},
|
{:esshd, "~> 0.1.0"},
|
||||||
{:ex_rated, "~> 1.2"},
|
{:ex_rated, "~> 1.2"},
|
||||||
{:plug_static_index_html, "~> 1.0.0"}
|
{:plug_static_index_html, "~> 1.0.0"},
|
||||||
|
{:excoveralls, "~> 0.11.1", only: :test}
|
||||||
] ++ oauth_deps
|
] ++ oauth_deps
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
2
mix.lock
2
mix.lock
|
@ -31,12 +31,14 @@
|
||||||
"ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"},
|
"ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"ex_rated": {:hex, :ex_rated, "1.3.2", "6aeb32abb46ea6076f417a9ce8cb1cf08abf35fb2d42375beaad4dd72b550bf1", [:mix], [{:ex2ms, "~> 1.5", [hex: :ex2ms, repo: "hexpm", optional: false]}], "hexpm"},
|
"ex_rated": {:hex, :ex_rated, "1.3.2", "6aeb32abb46ea6076f417a9ce8cb1cf08abf35fb2d42375beaad4dd72b550bf1", [:mix], [{:ex2ms, "~> 1.5", [hex: :ex2ms, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"ex_syslogger": {:git, "https://github.com/slashmili/ex_syslogger.git", "f3963399047af17e038897c69e20d552e6899e1d", [tag: "1.4.0"]},
|
"ex_syslogger": {:git, "https://github.com/slashmili/ex_syslogger.git", "f3963399047af17e038897c69e20d552e6899e1d", [tag: "1.4.0"]},
|
||||||
|
"excoveralls": {:hex, :excoveralls, "0.11.1", "dd677fbdd49114fdbdbf445540ec735808250d56b011077798316505064edb2c", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"floki": {:hex, :floki, "0.20.4", "be42ac911fece24b4c72f3b5846774b6e61b83fe685c2fc9d62093277fb3bc86", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}, {:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
|
"floki": {:hex, :floki, "0.20.4", "be42ac911fece24b4c72f3b5846774b6e61b83fe685c2fc9d62093277fb3bc86", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}, {:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"gen_smtp": {:hex, :gen_smtp, "0.13.0", "11f08504c4bdd831dc520b8f84a1dce5ce624474a797394e7aafd3c29f5dcd25", [:rebar3], [], "hexpm"},
|
"gen_smtp": {:hex, :gen_smtp, "0.13.0", "11f08504c4bdd831dc520b8f84a1dce5ce624474a797394e7aafd3c29f5dcd25", [:rebar3], [], "hexpm"},
|
||||||
"gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
|
"gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
|
||||||
"hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
|
"hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"},
|
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"},
|
||||||
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
|
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
"http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "9789401987096ead65646b52b5a2ca6bf52fc531", [ref: "9789401987096ead65646b52b5a2ca6bf52fc531"]},
|
||||||
"httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
"httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
|
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
|
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "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"})
|
||||||
|
|
55
test/plugs/ensure_public_or_authenticated_plug_test.exs
Normal file
55
test/plugs/ensure_public_or_authenticated_plug_test.exs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlugTest do
|
||||||
|
use Pleroma.Web.ConnCase, async: true
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
test "it halts if not public and no user is assigned", %{conn: conn} do
|
||||||
|
set_public_to(false)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> EnsurePublicOrAuthenticatedPlug.call(%{})
|
||||||
|
|
||||||
|
assert conn.status == 403
|
||||||
|
assert conn.halted == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it continues if public", %{conn: conn} do
|
||||||
|
set_public_to(true)
|
||||||
|
|
||||||
|
ret_conn =
|
||||||
|
conn
|
||||||
|
|> EnsurePublicOrAuthenticatedPlug.call(%{})
|
||||||
|
|
||||||
|
assert ret_conn == conn
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it continues if a user is assigned, even if not public", %{conn: conn} do
|
||||||
|
set_public_to(false)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, %User{})
|
||||||
|
|
||||||
|
ret_conn =
|
||||||
|
conn
|
||||||
|
|> EnsurePublicOrAuthenticatedPlug.call(%{})
|
||||||
|
|
||||||
|
assert ret_conn == conn
|
||||||
|
end
|
||||||
|
|
||||||
|
defp set_public_to(value) do
|
||||||
|
orig = Config.get!([:instance, :public])
|
||||||
|
Config.put([:instance, :public], value)
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
Config.put([:instance, :public], orig)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
|
@ -7,28 +7,89 @@ 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
|
||||||
Config.put([:http_security, :enabled], true)
|
setup do
|
||||||
|
enabled = Config.get([:http_securiy, :enabled])
|
||||||
|
|
||||||
conn =
|
Config.put([:http_security, :enabled], true)
|
||||||
conn
|
|
||||||
|> get("/api/v1/instance")
|
|
||||||
|
|
||||||
refute Conn.get_resp_header(conn, "x-xss-protection") == []
|
on_exit(fn ->
|
||||||
refute Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
|
Config.put([:http_security, :enabled], enabled)
|
||||||
refute Conn.get_resp_header(conn, "x-frame-options") == []
|
end)
|
||||||
refute Conn.get_resp_header(conn, "x-content-type-options") == []
|
|
||||||
refute Conn.get_resp_header(conn, "x-download-options") == []
|
:ok
|
||||||
refute Conn.get_resp_header(conn, "referrer-policy") == []
|
end
|
||||||
refute Conn.get_resp_header(conn, "content-security-policy") == []
|
|
||||||
|
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-permitted-cross-domain-policies") == []
|
||||||
|
refute Conn.get_resp_header(conn, "x-frame-options") == []
|
||||||
|
refute Conn.get_resp_header(conn, "x-content-type-options") == []
|
||||||
|
refute Conn.get_resp_header(conn, "x-download-options") == []
|
||||||
|
refute Conn.get_resp_header(conn, "referrer-policy") == []
|
||||||
|
refute Conn.get_resp_header(conn, "content-security-policy") == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it sends STS headers when enabled", %{conn: conn} do
|
||||||
|
Config.put([:http_security, :sts], true)
|
||||||
|
|
||||||
|
conn = get(conn, "/api/v1/instance")
|
||||||
|
|
||||||
|
refute Conn.get_resp_header(conn, "strict-transport-security") == []
|
||||||
|
refute Conn.get_resp_header(conn, "expect-ct") == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it does not send STS headers when disabled", %{conn: conn} do
|
||||||
|
Config.put([:http_security, :sts], false)
|
||||||
|
|
||||||
|
conn = get(conn, "/api/v1/instance")
|
||||||
|
|
||||||
|
assert Conn.get_resp_header(conn, "strict-transport-security") == []
|
||||||
|
assert Conn.get_resp_header(conn, "expect-ct") == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "referrer-policy header reflects configured value", %{conn: conn} do
|
||||||
|
conn = get(conn, "/api/v1/instance")
|
||||||
|
|
||||||
|
assert Conn.get_resp_header(conn, "referrer-policy") == ["same-origin"]
|
||||||
|
|
||||||
|
Config.put([:http_security, :referrer_policy], "no-referrer")
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> get("/api/v1/instance")
|
||||||
|
|
||||||
|
assert Conn.get_resp_header(conn, "referrer-policy") == ["no-referrer"]
|
||||||
|
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
|
end
|
||||||
|
|
||||||
test "it does not send CSP headers when disabled", %{conn: conn} do
|
test "it does not send CSP headers when disabled", %{conn: conn} do
|
||||||
|
enabled = Config.get([:http_securiy, :enabled])
|
||||||
|
|
||||||
Config.put([:http_security, :enabled], false)
|
Config.put([:http_security, :enabled], false)
|
||||||
|
|
||||||
conn =
|
on_exit(fn ->
|
||||||
conn
|
Config.put([:http_security, :enabled], enabled)
|
||||||
|> get("/api/v1/instance")
|
end)
|
||||||
|
|
||||||
|
conn = get(conn, "/api/v1/instance")
|
||||||
|
|
||||||
assert Conn.get_resp_header(conn, "x-xss-protection") == []
|
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-permitted-cross-domain-policies") == []
|
||||||
|
@ -38,46 +99,4 @@ test "it does not send CSP headers when disabled", %{conn: conn} do
|
||||||
assert Conn.get_resp_header(conn, "referrer-policy") == []
|
assert Conn.get_resp_header(conn, "referrer-policy") == []
|
||||||
assert Conn.get_resp_header(conn, "content-security-policy") == []
|
assert Conn.get_resp_header(conn, "content-security-policy") == []
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it sends STS headers when enabled", %{conn: conn} do
|
|
||||||
Config.put([:http_security, :enabled], true)
|
|
||||||
Config.put([:http_security, :sts], true)
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/instance")
|
|
||||||
|
|
||||||
refute Conn.get_resp_header(conn, "strict-transport-security") == []
|
|
||||||
refute Conn.get_resp_header(conn, "expect-ct") == []
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it does not send STS headers when disabled", %{conn: conn} do
|
|
||||||
Config.put([:http_security, :enabled], true)
|
|
||||||
Config.put([:http_security, :sts], false)
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/instance")
|
|
||||||
|
|
||||||
assert Conn.get_resp_header(conn, "strict-transport-security") == []
|
|
||||||
assert Conn.get_resp_header(conn, "expect-ct") == []
|
|
||||||
end
|
|
||||||
|
|
||||||
test "referrer-policy header reflects configured value", %{conn: conn} do
|
|
||||||
Config.put([:http_security, :enabled], true)
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/instance")
|
|
||||||
|
|
||||||
assert Conn.get_resp_header(conn, "referrer-policy") == ["same-origin"]
|
|
||||||
|
|
||||||
Config.put([:http_security, :referrer_policy], "no-referrer")
|
|
||||||
|
|
||||||
conn =
|
|
||||||
build_conn()
|
|
||||||
|> get("/api/v1/instance")
|
|
||||||
|
|
||||||
assert Conn.get_resp_header(conn, "referrer-policy") == ["no-referrer"]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
|
defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
|
||||||
use Pleroma.Web.ConnCase
|
use Pleroma.Web.ConnCase
|
||||||
alias Pleroma.Web.HTTPSignatures
|
|
||||||
alias Pleroma.Web.Plugs.HTTPSignaturePlug
|
alias Pleroma.Web.Plugs.HTTPSignaturePlug
|
||||||
|
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
49
test/tasks/database_test.exs
Normal file
49
test/tasks/database_test.exs
Normal file
|
@ -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
|
||||||
|
|
192
test/web/activity_pub/mrf/simple_policy_test.exs
Normal file
192
test/web/activity_pub/mrf/simple_policy_test.exs
Normal file
|
@ -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
|
|
@ -95,4 +95,26 @@ test "visible_for_user?", %{
|
||||||
refute Visibility.visible_for_user?(private, unrelated)
|
refute Visibility.visible_for_user?(private, unrelated)
|
||||||
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", %{
|
||||||
|
public: public,
|
||||||
|
private: private,
|
||||||
|
direct: direct,
|
||||||
|
unlisted: unlisted
|
||||||
|
} do
|
||||||
|
assert Visibility.get_visibility(public) == "public"
|
||||||
|
assert Visibility.get_visibility(private) == "private"
|
||||||
|
assert Visibility.get_visibility(direct) == "direct"
|
||||||
|
assert Visibility.get_visibility(unlisted) == "unlisted"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -87,6 +87,28 @@ test "it filters out obviously bad tags when accepting a post as Markdown" do
|
||||||
|
|
||||||
assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
|
assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it does not allow replies to direct messages that are not direct messages themselves" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
|
||||||
|
|
||||||
|
assert {:ok, _} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
"status" => "suya..",
|
||||||
|
"visibility" => "direct",
|
||||||
|
"in_reply_to_status_id" => activity.id
|
||||||
|
})
|
||||||
|
|
||||||
|
Enum.each(["public", "private", "unlisted"], fn visibility ->
|
||||||
|
assert {:error, {:private_to_public, _}} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
"status" => "suya..",
|
||||||
|
"visibility" => visibility,
|
||||||
|
"in_reply_to_status_id" => activity.id
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "reactions" do
|
describe "reactions" do
|
||||||
|
@ -239,10 +261,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
|
||||||
|
@ -257,14 +310,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
|
||||||
|
|
|
@ -1,194 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
# http signatures
|
|
||||||
# Test data from https://tools.ietf.org/html/draft-cavage-http-signatures-08#appendix-C
|
|
||||||
defmodule Pleroma.Web.HTTPSignaturesTest do
|
|
||||||
use Pleroma.DataCase
|
|
||||||
alias Pleroma.Web.HTTPSignatures
|
|
||||||
import Pleroma.Factory
|
|
||||||
import Tesla.Mock
|
|
||||||
|
|
||||||
setup do
|
|
||||||
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
@public_key hd(:public_key.pem_decode(File.read!("test/web/http_sigs/pub.key")))
|
|
||||||
|> :public_key.pem_entry_decode()
|
|
||||||
|
|
||||||
@headers %{
|
|
||||||
"(request-target)" => "post /foo?param=value&pet=dog",
|
|
||||||
"host" => "example.com",
|
|
||||||
"date" => "Thu, 05 Jan 2014 21:31:40 GMT",
|
|
||||||
"content-type" => "application/json",
|
|
||||||
"digest" => "SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=",
|
|
||||||
"content-length" => "18"
|
|
||||||
}
|
|
||||||
|
|
||||||
@default_signature """
|
|
||||||
keyId="Test",algorithm="rsa-sha256",signature="jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9HpFQlG7N4YcJPteKTu4MWCLyk+gIr0wDgqtLWf9NLpMAMimdfsH7FSWGfbMFSrsVTHNTk0rK3usrfFnti1dxsM4jl0kYJCKTGI/UWkqiaxwNiKqGcdlEDrTcUhhsFsOIo8VhddmZTZ8w="
|
|
||||||
"""
|
|
||||||
|
|
||||||
@basic_signature """
|
|
||||||
keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date",signature="HUxc9BS3P/kPhSmJo+0pQ4IsCo007vkv6bUm4Qehrx+B1Eo4Mq5/6KylET72ZpMUS80XvjlOPjKzxfeTQj4DiKbAzwJAb4HX3qX6obQTa00/qPDXlMepD2JtTw33yNnm/0xV7fQuvILN/ys+378Ysi082+4xBQFwvhNvSoVsGv4="
|
|
||||||
"""
|
|
||||||
|
|
||||||
@all_headers_signature """
|
|
||||||
keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date content-type digest content-length",signature="Ef7MlxLXoBovhil3AlyjtBwAL9g4TN3tibLj7uuNB3CROat/9KaeQ4hW2NiJ+pZ6HQEOx9vYZAyi+7cmIkmJszJCut5kQLAwuX+Ms/mUFvpKlSo9StS2bMXDBNjOh4Auj774GFj4gwjS+3NhFeoqyr/MuN6HsEnkvn6zdgfE2i0="
|
|
||||||
"""
|
|
||||||
|
|
||||||
test "split up a signature" do
|
|
||||||
expected = %{
|
|
||||||
"keyId" => "Test",
|
|
||||||
"algorithm" => "rsa-sha256",
|
|
||||||
"signature" =>
|
|
||||||
"jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9HpFQlG7N4YcJPteKTu4MWCLyk+gIr0wDgqtLWf9NLpMAMimdfsH7FSWGfbMFSrsVTHNTk0rK3usrfFnti1dxsM4jl0kYJCKTGI/UWkqiaxwNiKqGcdlEDrTcUhhsFsOIo8VhddmZTZ8w=",
|
|
||||||
"headers" => ["date"]
|
|
||||||
}
|
|
||||||
|
|
||||||
assert HTTPSignatures.split_signature(@default_signature) == expected
|
|
||||||
end
|
|
||||||
|
|
||||||
test "validates the default case" do
|
|
||||||
signature = HTTPSignatures.split_signature(@default_signature)
|
|
||||||
assert HTTPSignatures.validate(@headers, signature, @public_key)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "validates the basic case" do
|
|
||||||
signature = HTTPSignatures.split_signature(@basic_signature)
|
|
||||||
assert HTTPSignatures.validate(@headers, signature, @public_key)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "validates the all-headers case" do
|
|
||||||
signature = HTTPSignatures.split_signature(@all_headers_signature)
|
|
||||||
assert HTTPSignatures.validate(@headers, signature, @public_key)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it contructs a signing string" do
|
|
||||||
expected = "date: Thu, 05 Jan 2014 21:31:40 GMT\ncontent-length: 18"
|
|
||||||
assert expected == HTTPSignatures.build_signing_string(@headers, ["date", "content-length"])
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it validates a conn" do
|
|
||||||
public_key_pem =
|
|
||||||
"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnGb42rPZIapY4Hfhxrgn\nxKVJczBkfDviCrrYaYjfGxawSw93dWTUlenCVTymJo8meBlFgIQ70ar4rUbzl6GX\nMYvRdku072d1WpglNHXkjKPkXQgngFDrh2sGKtNB/cEtJcAPRO8OiCgPFqRtMiNM\nc8VdPfPdZuHEIZsJ/aUM38EnqHi9YnVDQik2xxDe3wPghOhqjxUM6eLC9jrjI+7i\naIaEygUdyst9qVg8e2FGQlwAeS2Eh8ygCxn+bBlT5OyV59jSzbYfbhtF2qnWHtZy\nkL7KOOwhIfGs7O9SoR2ZVpTEQ4HthNzainIe/6iCR5HGrao/T8dygweXFYRv+k5A\nPQIDAQAB\n-----END PUBLIC KEY-----\n"
|
|
||||||
|
|
||||||
[public_key] = :public_key.pem_decode(public_key_pem)
|
|
||||||
|
|
||||||
public_key =
|
|
||||||
public_key
|
|
||||||
|> :public_key.pem_entry_decode()
|
|
||||||
|
|
||||||
conn = %{
|
|
||||||
req_headers: [
|
|
||||||
{"host", "localtesting.pleroma.lol"},
|
|
||||||
{"connection", "close"},
|
|
||||||
{"content-length", "2316"},
|
|
||||||
{"user-agent", "http.rb/2.2.2 (Mastodon/2.1.0.rc3; +http://mastodon.example.org/)"},
|
|
||||||
{"date", "Sun, 10 Dec 2017 14:23:49 GMT"},
|
|
||||||
{"digest", "SHA-256=x/bHADMW8qRrq2NdPb5P9fl0lYpKXXpe5h5maCIL0nM="},
|
|
||||||
{"content-type", "application/activity+json"},
|
|
||||||
{"(request-target)", "post /users/demiurge/inbox"},
|
|
||||||
{"signature",
|
|
||||||
"keyId=\"http://mastodon.example.org/users/admin#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"i0FQvr51sj9BoWAKydySUAO1RDxZmNY6g7M62IA7VesbRSdFZZj9/fZapLp6YSuvxUF0h80ZcBEq9GzUDY3Chi9lx6yjpUAS2eKb+Am/hY3aswhnAfYd6FmIdEHzsMrpdKIRqO+rpQ2tR05LwiGEHJPGS0p528NvyVxrxMT5H5yZS5RnxY5X2HmTKEgKYYcvujdv7JWvsfH88xeRS7Jlq5aDZkmXvqoR4wFyfgnwJMPLel8P/BUbn8BcXglH/cunR0LUP7sflTxEz+Rv5qg+9yB8zgBsB4C0233WpcJxjeD6Dkq0EcoJObBR56F8dcb7NQtUDu7x6xxzcgSd7dHm5w==\""}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
assert HTTPSignatures.validate_conn(conn, public_key)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it validates a conn and fetches the key" do
|
|
||||||
conn = %{
|
|
||||||
params: %{"actor" => "http://mastodon.example.org/users/admin"},
|
|
||||||
req_headers: [
|
|
||||||
{"host", "localtesting.pleroma.lol"},
|
|
||||||
{"x-forwarded-for", "127.0.0.1"},
|
|
||||||
{"connection", "close"},
|
|
||||||
{"content-length", "2307"},
|
|
||||||
{"user-agent", "http.rb/2.2.2 (Mastodon/2.1.0.rc3; +http://mastodon.example.org/)"},
|
|
||||||
{"date", "Sun, 11 Feb 2018 17:12:01 GMT"},
|
|
||||||
{"digest", "SHA-256=UXsAnMtR9c7mi1FOf6HRMtPgGI1yi2e9nqB/j4rZ99I="},
|
|
||||||
{"content-type", "application/activity+json"},
|
|
||||||
{"signature",
|
|
||||||
"keyId=\"http://mastodon.example.org/users/admin#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"qXKqpQXUpC3d9bZi2ioEeAqP8nRMD021CzH1h6/w+LRk4Hj31ARJHDwQM+QwHltwaLDUepshMfz2WHSXAoLmzWtvv7xRwY+mRqe+NGk1GhxVZ/LSrO/Vp7rYfDpfdVtkn36LU7/Bzwxvvaa4ZWYltbFsRBL0oUrqsfmJFswNCQIG01BB52BAhGSCORHKtQyzo1IZHdxl8y80pzp/+FOK2SmHkqWkP9QbaU1qTZzckL01+7M5btMW48xs9zurEqC2sM5gdWMQSZyL6isTV5tmkTZrY8gUFPBJQZgihK44v3qgfWojYaOwM8ATpiv7NG8wKN/IX7clDLRMA8xqKRCOKw==\""},
|
|
||||||
{"(request-target)", "post /users/demiurge/inbox"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
assert HTTPSignatures.validate_conn(conn)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "validate this" do
|
|
||||||
conn = %{
|
|
||||||
params: %{"actor" => "https://niu.moe/users/rye"},
|
|
||||||
req_headers: [
|
|
||||||
{"x-forwarded-for", "149.202.73.191"},
|
|
||||||
{"host", "testing.pleroma.lol"},
|
|
||||||
{"x-cluster-client-ip", "149.202.73.191"},
|
|
||||||
{"connection", "upgrade"},
|
|
||||||
{"content-length", "2396"},
|
|
||||||
{"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://niu.moe/)"},
|
|
||||||
{"date", "Sun, 18 Feb 2018 20:31:51 GMT"},
|
|
||||||
{"digest", "SHA-256=dzH+vLyhxxALoe9RJdMl4hbEV9bGAZnSfddHQzeidTU="},
|
|
||||||
{"content-type", "application/activity+json"},
|
|
||||||
{"signature",
|
|
||||||
"keyId=\"https://niu.moe/users/rye#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"wtxDg4kIpW7nsnUcVJhBk6SgJeDZOocr8yjsnpDRqE52lR47SH6X7G16r7L1AUJdlnbfx7oqcvomoIJoHB3ghP6kRnZW6MyTMZ2jPoi3g0iC5RDqv6oAmDSO14iw6U+cqZbb3P/odS5LkbThF0UNXcfenVNfsKosIJycFjhNQc54IPCDXYq/7SArEKJp8XwEgzmiC2MdxlkVIUSTQYfjM4EG533cwlZocw1mw72e5mm/owTa80BUZAr0OOuhoWARJV9btMb02ZyAF6SCSoGPTA37wHyfM1Dk88NHf7Z0Aov/Fl65dpRM+XyoxdkpkrhDfH9qAx4iuV2VEWddQDiXHA==\""},
|
|
||||||
{"(request-target)", "post /inbox"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
assert HTTPSignatures.validate_conn(conn)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "validate this too" do
|
|
||||||
conn = %{
|
|
||||||
params: %{"actor" => "https://niu.moe/users/rye"},
|
|
||||||
req_headers: [
|
|
||||||
{"x-forwarded-for", "149.202.73.191"},
|
|
||||||
{"host", "testing.pleroma.lol"},
|
|
||||||
{"x-cluster-client-ip", "149.202.73.191"},
|
|
||||||
{"connection", "upgrade"},
|
|
||||||
{"content-length", "2342"},
|
|
||||||
{"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://niu.moe/)"},
|
|
||||||
{"date", "Sun, 18 Feb 2018 21:44:46 GMT"},
|
|
||||||
{"digest", "SHA-256=vS8uDOJlyAu78cF3k5EzrvaU9iilHCX3chP37gs5sS8="},
|
|
||||||
{"content-type", "application/activity+json"},
|
|
||||||
{"signature",
|
|
||||||
"keyId=\"https://niu.moe/users/rye#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"IN6fHD8pLiDEf35dOaRHzJKc1wBYh3/Yq0ItaNGxUSbJTd2xMjigZbcsVKzvgYYjglDDN+disGNeD+OBKwMqkXWaWe/lyMc9wHvCH5NMhpn/A7qGLY8yToSt4vh8ytSkZKO6B97yC+Nvy6Fz/yMbvKtFycIvSXCq417cMmY6f/aG+rtMUlTbKO5gXzC7SUgGJCtBPCh1xZzu5/w0pdqdjO46ePNeR6JyJSLLV4hfo3+p2n7SRraxM4ePVCUZqhwS9LPt3Zdhy3ut+IXCZgMVIZggQFM+zXLtcXY5HgFCsFQr5WQDu+YkhWciNWtKFnWfAsnsg5sC330lZ/0Z8Z91yA==\""},
|
|
||||||
{"(request-target)", "post /inbox"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
assert HTTPSignatures.validate_conn(conn)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it generates a signature" do
|
|
||||||
user = insert(:user)
|
|
||||||
assert HTTPSignatures.sign(user, %{host: "mastodon.example.org"}) =~ "keyId=\""
|
|
||||||
end
|
|
||||||
|
|
||||||
test "this too" do
|
|
||||||
conn = %{
|
|
||||||
params: %{"actor" => "https://mst3k.interlinked.me/users/luciferMysticus"},
|
|
||||||
req_headers: [
|
|
||||||
{"host", "soc.canned-death.us"},
|
|
||||||
{"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://mst3k.interlinked.me/)"},
|
|
||||||
{"date", "Sun, 11 Mar 2018 12:19:36 GMT"},
|
|
||||||
{"digest", "SHA-256=V7Hl6qDK2m8WzNsjzNYSBISi9VoIXLFlyjF/a5o1SOc="},
|
|
||||||
{"content-type", "application/activity+json"},
|
|
||||||
{"signature",
|
|
||||||
"keyId=\"https://mst3k.interlinked.me/users/luciferMysticus#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15pCpAyx8IZp53s0Fn/y8MjCTzp+absw8kxx0k2sQAXYs2iy6xhdDUe7iGzz+XLAEqLyZIZfecynaU2nb3Z2XnFDjhGjR1vj/JP7wiXpwp6o1dpDZj+KT2vxHtXuB9585V+sOHLwSB1cGDbAgTy0jx/2az2EGIKK2zkw1KJuAZm0DDMSZalp/30P8dl3qz7DV2EHdDNfaVtrs5BfbDOZ7t1hCcASllzAzgVGFl0BsrkzBfRMeUMRucr111ZG+c0BNOEtJYOHSyZsSSdNknElggCJekONYMYk5ZA==\""},
|
|
||||||
{"x-forwarded-for", "2607:5300:203:2899::31:1337"},
|
|
||||||
{"x-forwarded-host", "soc.canned-death.us"},
|
|
||||||
{"x-forwarded-server", "soc.canned-death.us"},
|
|
||||||
{"connection", "Keep-Alive"},
|
|
||||||
{"content-length", "2006"},
|
|
||||||
{"(request-target)", "post /inbox"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
assert HTTPSignatures.validate_conn(conn)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,15 +0,0 @@
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF
|
|
||||||
NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F
|
|
||||||
UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB
|
|
||||||
AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA
|
|
||||||
QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK
|
|
||||||
kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg
|
|
||||||
f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u
|
|
||||||
412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc
|
|
||||||
mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7
|
|
||||||
kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA
|
|
||||||
gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW
|
|
||||||
G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
|
|
||||||
7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA==
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
|
@ -1,6 +0,0 @@
|
||||||
-----BEGIN PUBLIC KEY-----
|
|
||||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3
|
|
||||||
6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6
|
|
||||||
Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw
|
|
||||||
oYi+1hqp1fIekaxsyQIDAQAB
|
|
||||||
-----END PUBLIC KEY-----
|
|
|
@ -81,6 +81,19 @@ test "the public timeline", %{conn: conn} do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "the public timeline when public is set to false", %{conn: conn} do
|
||||||
|
public = Pleroma.Config.get([:instance, :public])
|
||||||
|
Pleroma.Config.put([:instance, :public], false)
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
Pleroma.Config.put([:instance, :public], public)
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> get("/api/v1/timelines/public", %{"local" => "False"})
|
||||||
|
|> json_response(403) == %{"error" => "This resource requires authentication."}
|
||||||
|
end
|
||||||
|
|
||||||
test "posting a status", %{conn: conn} do
|
test "posting a status", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
@ -433,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
|
||||||
|
@ -1309,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)
|
||||||
|
@ -1354,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
|
||||||
|
@ -1366,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)
|
||||||
|
@ -2116,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", %{
|
||||||
|
@ -2140,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: %{},
|
||||||
|
|
Loading…
Reference in a new issue