forked from AkkomaGang/akkoma
Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop
This commit is contained in:
commit
c0b7bc2927
96 changed files with 2827 additions and 969 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
|
||||||
|
|
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -10,7 +10,9 @@ 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.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
|
||||||
|
@ -22,12 +24,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- 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/
|
||||||
- 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)
|
||||||
- Mastodon API: `/api/v1/pleroma/accounts/:id/favourites` (API extension)
|
- Mastodon API: `/api/v1/pleroma/accounts/:id/favourites` (API extension)
|
||||||
- Mastodon API: [Reports](https://docs.joinmastodon.org/api/rest/reports/)
|
- Mastodon API: [Reports](https://docs.joinmastodon.org/api/rest/reports/)
|
||||||
|
- Mastodon API: `POST /api/v1/accounts` (account creation API)
|
||||||
- ActivityPub C2S: OAuth endpoints
|
- ActivityPub C2S: OAuth endpoints
|
||||||
- Metadata RelMe provider
|
- Metadata: RelMe provider
|
||||||
- OAuth: added support for refresh tokens
|
- OAuth: added support for refresh tokens
|
||||||
- Emoji packs and emoji pack manager
|
- Emoji packs and emoji pack manager
|
||||||
|
|
||||||
|
@ -42,8 +48,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Federation: Removed `inReplyToStatusId` from objects
|
- Federation: Removed `inReplyToStatusId` from objects
|
||||||
- Configuration: Dedupe enabled by default
|
- Configuration: Dedupe enabled by default
|
||||||
- Configuration: Added `extra_cookie_attrs` for setting non-standard cookie attributes. Defaults to ["SameSite=Lax"] so that remote follows work.
|
- Configuration: Added `extra_cookie_attrs` for setting non-standard cookie attributes. Defaults to ["SameSite=Lax"] so that remote follows work.
|
||||||
- Pleroma API: Support for emoji tags in `/api/pleroma/emoji` resulting in a breaking API change
|
|
||||||
- Timelines: Messages involving people you have blocked will be excluded from the timeline in all cases instead of just repeats.
|
- Timelines: Messages involving people you have blocked will be excluded from the timeline in all cases instead of just repeats.
|
||||||
|
- Admin API: Move the user related API to `api/pleroma/admin/users`
|
||||||
|
- Pleroma API: Support for emoji tags in `/api/pleroma/emoji` resulting in a breaking API change
|
||||||
- Mastodon API: Support for `exclude_types`, `limit` and `min_id` in `/api/v1/notifications`
|
- Mastodon API: Support for `exclude_types`, `limit` and `min_id` in `/api/v1/notifications`
|
||||||
- Mastodon API: Add `languages` and `registrations` to `/api/v1/instance`
|
- Mastodon API: Add `languages` and `registrations` to `/api/v1/instance`
|
||||||
- Mastodon API: Provide plaintext versions of cw/content in the Status entity
|
- Mastodon API: Provide plaintext versions of cw/content in the Status entity
|
||||||
|
@ -57,18 +64,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Mastodon API: Add `with_muted` parameter to timeline endpoints
|
- Mastodon API: Add `with_muted` parameter to timeline endpoints
|
||||||
- Mastodon API: Actual reblog hiding instead of a dummy
|
- Mastodon API: Actual reblog hiding instead of a dummy
|
||||||
- Mastodon API: Remove attachment limit in the Status entity
|
- Mastodon API: Remove attachment limit in the Status entity
|
||||||
|
- Mastodon API: Added support max_id & since_id for bookmark timeline endpoints.
|
||||||
- Deps: Updated Cowboy to 2.6
|
- Deps: Updated Cowboy to 2.6
|
||||||
- Deps: Updated Ecto to 3.0.7
|
- Deps: Updated Ecto to 3.0.7
|
||||||
- Don't ship finmoji by default, they can be installed as an emoji pack
|
- Don't ship finmoji by default, they can be installed as an emoji pack
|
||||||
- Mastodon API: Added support max_id & since_id for bookmark timeline endpoints.
|
- Hide deactivated users and their statuses
|
||||||
- Admin API: Move the user related API to `api/pleroma/admin/users`
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Added an FTS index on objects. Running `vacuum analyze` and setting a larger `work_mem` is recommended.
|
- Added an FTS index on objects. Running `vacuum analyze` and setting a larger `work_mem` is recommended.
|
||||||
- Followers counter not being updated when a follower is blocked
|
- Followers counter not being updated when a follower is blocked
|
||||||
- Deactivated users being able to request an access token
|
- Deactivated users being able to request an access token
|
||||||
- Limit on request body in rich media/relme parsers being ignored resulting in a possible memory leak
|
- Limit on request body in rich media/relme parsers being ignored resulting in a possible memory leak
|
||||||
- proper Twitter Card generation instead of a dummy
|
- Proper Twitter Card generation instead of a dummy
|
||||||
- Deletions failing for users with a large number of posts
|
- Deletions failing for users with a large number of posts
|
||||||
- NodeInfo: Include admins in `staffAccounts`
|
- NodeInfo: Include admins in `staffAccounts`
|
||||||
- ActivityPub: Crashing when requesting empty local user's outbox
|
- ActivityPub: Crashing when requesting empty local user's outbox
|
||||||
|
@ -92,6 +99,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Mastodon API: Handling of `reblogs` in `/api/v1/accounts/:id/follow`
|
- Mastodon API: Handling of `reblogs` in `/api/v1/accounts/:id/follow`
|
||||||
- Mastodon API: Correct `reblogged`, `favourited`, and `bookmarked` values in the reblog status JSON
|
- Mastodon API: Correct `reblogged`, `favourited`, and `bookmarked` values in the reblog status JSON
|
||||||
- Mastodon API: Exposing default scope of the user to anyone
|
- Mastodon API: Exposing default scope of the user to anyone
|
||||||
|
- Mastodon API: Make `irreversible` field default to `false` [`POST /api/v1/filters`]
|
||||||
|
|
||||||
|
## Removed
|
||||||
|
- Configuration: `config :pleroma, :fe` in favor of the more flexible `config :pleroma, :frontend_configurations`
|
||||||
|
|
||||||
## [0.9.9999] - 2019-04-05
|
## [0.9.9999] - 2019-04-05
|
||||||
### Security
|
### Security
|
||||||
|
|
8
COPYING
8
COPYING
|
@ -15,6 +15,14 @@ priv/static/images/pleroma-tan.png
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
The following files are copyright © 2019 shitposter.club, and are distributed
|
||||||
|
under the Creative Commons Attribution 4.0 International license, you should
|
||||||
|
have received a copy of the license file as CC-BY-4.0.
|
||||||
|
|
||||||
|
priv/static/images/pleroma-fox-tan-shy.png
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
The following files are copyright © 2017-2019 Pleroma Authors
|
The following files are copyright © 2017-2019 Pleroma Authors
|
||||||
<https://pleroma.social/>, and are distributed under the Creative Commons
|
<https://pleroma.social/>, and are distributed under the Creative Commons
|
||||||
Attribution-ShareAlike 4.0 International license, you should have received
|
Attribution-ShareAlike 4.0 International license, you should have received
|
||||||
|
|
|
@ -212,6 +212,11 @@
|
||||||
registrations_open: true,
|
registrations_open: true,
|
||||||
federating: true,
|
federating: true,
|
||||||
federation_reachability_timeout_days: 7,
|
federation_reachability_timeout_days: 7,
|
||||||
|
federation_publisher_modules: [
|
||||||
|
Pleroma.Web.ActivityPub.Publisher,
|
||||||
|
Pleroma.Web.Websub,
|
||||||
|
Pleroma.Web.Salmon
|
||||||
|
],
|
||||||
allow_relay: true,
|
allow_relay: true,
|
||||||
rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
|
rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
|
||||||
public: true,
|
public: true,
|
||||||
|
@ -234,6 +239,8 @@
|
||||||
safe_dm_mentions: false,
|
safe_dm_mentions: false,
|
||||||
healthcheck: false
|
healthcheck: false
|
||||||
|
|
||||||
|
config :pleroma, :app_account_creation, enabled: true, max_requests: 25, interval: 1800
|
||||||
|
|
||||||
config :pleroma, :markup,
|
config :pleroma, :markup,
|
||||||
# XXX - unfortunately, inline images must be enabled by default right now, because
|
# XXX - unfortunately, inline images must be enabled by default right now, because
|
||||||
# of custom emoji. Issue #275 discusses defanging that somehow.
|
# of custom emoji. Issue #275 discusses defanging that somehow.
|
||||||
|
@ -246,25 +253,6 @@
|
||||||
Pleroma.HTML.Scrubber.Default
|
Pleroma.HTML.Scrubber.Default
|
||||||
]
|
]
|
||||||
|
|
||||||
# Deprecated, will be gone in 1.0
|
|
||||||
config :pleroma, :fe,
|
|
||||||
theme: "pleroma-dark",
|
|
||||||
logo: "/static/logo.png",
|
|
||||||
logo_mask: true,
|
|
||||||
logo_margin: "0.1em",
|
|
||||||
background: "/static/aurora_borealis.jpg",
|
|
||||||
redirect_root_no_login: "/main/all",
|
|
||||||
redirect_root_login: "/main/friends",
|
|
||||||
show_instance_panel: true,
|
|
||||||
scope_options_enabled: false,
|
|
||||||
formatting_options_enabled: false,
|
|
||||||
collapse_message_with_subject: false,
|
|
||||||
hide_post_stats: false,
|
|
||||||
hide_user_stats: false,
|
|
||||||
scope_copy: true,
|
|
||||||
subject_line_behavior: "email",
|
|
||||||
always_show_subject_input: true
|
|
||||||
|
|
||||||
config :pleroma, :frontend_configurations,
|
config :pleroma, :frontend_configurations,
|
||||||
pleroma_fe: %{
|
pleroma_fe: %{
|
||||||
theme: "pleroma-dark",
|
theme: "pleroma-dark",
|
||||||
|
@ -476,6 +464,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"
|
||||||
|
|
|
@ -59,6 +59,8 @@
|
||||||
total_user_limit: 3,
|
total_user_limit: 3,
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
|
config :pleroma, :app_account_creation, max_requests: 5
|
||||||
|
|
||||||
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 `{}`
|
||||||
|
|
|
@ -87,3 +87,13 @@ Additional parameters can be added to the JSON body/Form data:
|
||||||
|
|
||||||
`POST /oauth/token`
|
`POST /oauth/token`
|
||||||
Post here request with grant_type=refresh_token to obtain new access token. Returns an access token.
|
Post here request with grant_type=refresh_token to obtain new access token. Returns an access token.
|
||||||
|
|
||||||
|
## Account Registration
|
||||||
|
`POST /api/v1/accounts`
|
||||||
|
|
||||||
|
Has theses additionnal parameters (which are the same as in Pleroma-API):
|
||||||
|
* `fullname`: optional
|
||||||
|
* `bio`: optional
|
||||||
|
* `captcha_solution`: optional, contains provider-specific captcha solution,
|
||||||
|
* `captcha_token`: optional, contains provider-specific captcha token
|
||||||
|
* `token`: invite token required when the registerations aren't public.
|
||||||
|
|
|
@ -61,6 +61,15 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
|
||||||
* Response: JSON. Returns `{"status": "success"}` if the deletion was successful, `{"error": "[error message]"}` otherwise
|
* Response: JSON. Returns `{"status": "success"}` if the deletion was successful, `{"error": "[error message]"}` otherwise
|
||||||
* Example response: `{"error": "Invalid password."}`
|
* Example response: `{"error": "Invalid password."}`
|
||||||
|
|
||||||
|
## `/api/pleroma/disable_account`
|
||||||
|
### Disable an account
|
||||||
|
* Method `POST`
|
||||||
|
* Authentication: required
|
||||||
|
* Params:
|
||||||
|
* `password`: user's password
|
||||||
|
* Response: JSON. Returns `{"status": "success"}` if the account was successfully disabled, `{"error": "[error message]"}` otherwise
|
||||||
|
* Example response: `{"error": "Invalid password."}`
|
||||||
|
|
||||||
## `/api/account/register`
|
## `/api/account/register`
|
||||||
### Register a new user
|
### Register a new user
|
||||||
* Method `POST`
|
* Method `POST`
|
||||||
|
|
|
@ -105,6 +105,12 @@ config :pleroma, Pleroma.Emails.Mailer,
|
||||||
* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). (Default: `false`)
|
* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). (Default: `false`)
|
||||||
* `healthcheck`: if set to true, system data will be shown on ``/api/pleroma/healthcheck``.
|
* `healthcheck`: if set to true, system data will be shown on ``/api/pleroma/healthcheck``.
|
||||||
|
|
||||||
|
## :app_account_creation
|
||||||
|
REST API for creating an account settings
|
||||||
|
* `enabled`: Enable/disable registration
|
||||||
|
* `max_requests`: Number of requests allowed for creating accounts
|
||||||
|
* `interval`: Interval for restricting requests for one ip (seconds)
|
||||||
|
|
||||||
## :logger
|
## :logger
|
||||||
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog, and `Quack.Logger` to log to Slack
|
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog, and `Quack.Logger` to log to Slack
|
||||||
|
|
||||||
|
|
|
@ -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 (バックエンド) をインストールします
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
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
|
||||||
require Logger
|
require Logger
|
||||||
use Mix.Task
|
use Mix.Task
|
||||||
|
|
||||||
|
@ -19,6 +20,11 @@ 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
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def run(["remove_embedded_objects" | args]) do
|
def run(["remove_embedded_objects" | args]) do
|
||||||
{options, [], []} =
|
{options, [], []} =
|
||||||
|
@ -48,4 +54,9 @@ def run(["remove_embedded_objects" | args]) do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def run(["bump_all_conversations"]) do
|
||||||
|
Common.start_pleroma()
|
||||||
|
Conversation.bump_for_all_activities()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -137,7 +137,7 @@ def run(["get-packs" | args]) do
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
|
||||||
files = Tesla.get!(client(), files_url).body |> Poison.decode!()
|
files = Tesla.get!(client(), files_url).body |> Jason.decode!()
|
||||||
|
|
||||||
IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name]))
|
IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name]))
|
||||||
|
|
||||||
|
@ -239,7 +239,7 @@ def run(["gen-pack", src]) do
|
||||||
|
|
||||||
emoji_map = Pleroma.Emoji.make_shortcode_to_file_map(tmp_pack_dir, exts)
|
emoji_map = Pleroma.Emoji.make_shortcode_to_file_map(tmp_pack_dir, exts)
|
||||||
|
|
||||||
File.write!(files_name, Poison.encode!(emoji_map, pretty: true))
|
File.write!(files_name, Jason.encode!(emoji_map, pretty: true))
|
||||||
|
|
||||||
IO.puts("""
|
IO.puts("""
|
||||||
|
|
||||||
|
@ -248,11 +248,11 @@ def run(["gen-pack", src]) do
|
||||||
""")
|
""")
|
||||||
|
|
||||||
if File.exists?("index.json") do
|
if File.exists?("index.json") do
|
||||||
existing_data = File.read!("index.json") |> Poison.decode!()
|
existing_data = File.read!("index.json") |> Jason.decode!()
|
||||||
|
|
||||||
File.write!(
|
File.write!(
|
||||||
"index.json",
|
"index.json",
|
||||||
Poison.encode!(
|
Jason.encode!(
|
||||||
Map.merge(
|
Map.merge(
|
||||||
existing_data,
|
existing_data,
|
||||||
pack_json
|
pack_json
|
||||||
|
@ -263,14 +263,14 @@ def run(["gen-pack", src]) do
|
||||||
|
|
||||||
IO.puts("index.json file has been update with the #{name} pack")
|
IO.puts("index.json file has been update with the #{name} pack")
|
||||||
else
|
else
|
||||||
File.write!("index.json", Poison.encode!(pack_json, pretty: true))
|
File.write!("index.json", Jason.encode!(pack_json, pretty: true))
|
||||||
|
|
||||||
IO.puts("index.json has been created with the #{name} pack")
|
IO.puts("index.json has been created with the #{name} pack")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp fetch_manifest(from) do
|
defp fetch_manifest(from) do
|
||||||
Poison.decode!(
|
Jason.decode!(
|
||||||
if String.starts_with?(from, "http") do
|
if String.starts_with?(from, "http") do
|
||||||
Tesla.get!(client(), from).body
|
Tesla.get!(client(), from).body
|
||||||
else
|
else
|
||||||
|
|
|
@ -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, [], []} =
|
||||||
|
@ -138,7 +142,7 @@ def run(["new", nickname, email | rest]) do
|
||||||
bio: bio
|
bio: bio
|
||||||
}
|
}
|
||||||
|
|
||||||
changeset = User.register_changeset(%User{}, params, confirmed: true)
|
changeset = User.register_changeset(%User{}, params, need_confirmation: false)
|
||||||
{:ok, _user} = User.register(changeset)
|
{:ok, _user} = User.register(changeset)
|
||||||
|
|
||||||
Mix.shell().info("User #{nickname} created")
|
Mix.shell().info("User #{nickname} created")
|
||||||
|
@ -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
|
||||||
|
@ -132,7 +135,10 @@ def get_by_ap_id_with_object(ap_id) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_by_id(id) do
|
def get_by_id(id) do
|
||||||
Repo.get(Activity, id)
|
Activity
|
||||||
|
|> where([a], a.id == ^id)
|
||||||
|
|> restrict_deactivated_users()
|
||||||
|
|> Repo.one()
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_by_id_with_object(id) do
|
def get_by_id_with_object(id) do
|
||||||
|
@ -200,6 +206,7 @@ def get_all_create_by_object_ap_id(ap_id) do
|
||||||
|
|
||||||
def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do
|
def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do
|
||||||
create_by_object_ap_id(ap_id)
|
create_by_object_ap_id(ap_id)
|
||||||
|
|> restrict_deactivated_users()
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -314,4 +321,14 @@ def follow_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do
|
||||||
def query_by_actor(actor) do
|
def query_by_actor(actor) do
|
||||||
from(a in Activity, where: a.actor == ^actor)
|
from(a in Activity, where: a.actor == ^actor)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def restrict_deactivated_users(query) do
|
||||||
|
from(activity in query,
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
|
||||||
|
activity.actor
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
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)
|
||||||
|
|
|
@ -15,7 +15,7 @@ def new do
|
||||||
%{error: "Kocaptcha service unavailable"}
|
%{error: "Kocaptcha service unavailable"}
|
||||||
|
|
||||||
{:ok, res} ->
|
{:ok, res} ->
|
||||||
json_resp = Poison.decode!(res.body)
|
json_resp = Jason.decode!(res.body)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
type: :kocaptcha,
|
type: :kocaptcha,
|
||||||
|
|
|
@ -12,8 +12,12 @@ def get(key), do: get(key, nil)
|
||||||
def get([key], default), do: get(key, default)
|
def get([key], default), do: get(key, default)
|
||||||
|
|
||||||
def get([parent_key | keys], default) do
|
def get([parent_key | keys], default) do
|
||||||
Application.get_env(:pleroma, parent_key)
|
case :pleroma
|
||||||
|> get_in(keys) || default
|
|> Application.get_env(parent_key)
|
||||||
|
|> get_in(keys) do
|
||||||
|
nil -> default
|
||||||
|
any -> any
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get(key, default) do
|
def get(key, default) do
|
||||||
|
|
|
@ -5,15 +5,6 @@
|
||||||
defmodule Pleroma.Config.DeprecationWarnings do
|
defmodule Pleroma.Config.DeprecationWarnings do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
def check_frontend_config_mechanism do
|
|
||||||
if Pleroma.Config.get(:fe) do
|
|
||||||
Logger.warn("""
|
|
||||||
!!!DEPRECATION WARNING!!!
|
|
||||||
You are using the old configuration mechanism for the frontend. Please check config.md.
|
|
||||||
""")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_hellthread_threshold do
|
def check_hellthread_threshold do
|
||||||
if Pleroma.Config.get([:mrf_hellthread, :threshold]) do
|
if Pleroma.Config.get([:mrf_hellthread, :threshold]) do
|
||||||
Logger.warn("""
|
Logger.warn("""
|
||||||
|
@ -24,7 +15,6 @@ def check_hellthread_threshold do
|
||||||
end
|
end
|
||||||
|
|
||||||
def warn do
|
def warn do
|
||||||
check_frontend_config_mechanism()
|
|
||||||
check_hellthread_threshold()
|
check_hellthread_threshold()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -77,13 +77,13 @@ def render_activities(activities) do
|
||||||
user = User.get_cached_by_ap_id(activity.data["actor"])
|
user = User.get_cached_by_ap_id(activity.data["actor"])
|
||||||
|
|
||||||
object = Object.normalize(activity)
|
object = Object.normalize(activity)
|
||||||
like_count = object["like_count"] || 0
|
like_count = object.data["like_count"] || 0
|
||||||
announcement_count = object["announcement_count"] || 0
|
announcement_count = object.data["announcement_count"] || 0
|
||||||
|
|
||||||
link("Post ##{activity.id} by #{user.nickname}", "/notices/#{activity.id}") <>
|
link("Post ##{activity.id} by #{user.nickname}", "/notices/#{activity.id}") <>
|
||||||
info("#{like_count} likes, #{announcement_count} repeats") <>
|
info("#{like_count} likes, #{announcement_count} repeats") <>
|
||||||
"i\tfake\t(NULL)\t0\r\n" <>
|
"i\tfake\t(NULL)\t0\r\n" <>
|
||||||
info(HTML.strip_tags(String.replace(object["content"], "<br>", "\r")))
|
info(HTML.strip_tags(String.replace(object.data["content"], "<br>", "\r")))
|
||||||
end)
|
end)
|
||||||
|> Enum.join("i\tfake\t(NULL)\t0\r\n")
|
|> Enum.join("i\tfake\t(NULL)\t0\r\n")
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,6 +33,13 @@ def changeset(%Notification{} = notification, attrs) do
|
||||||
def for_user_query(user) do
|
def for_user_query(user) do
|
||||||
Notification
|
Notification
|
||||||
|> where(user_id: ^user.id)
|
|> where(user_id: ^user.id)
|
||||||
|
|> where(
|
||||||
|
[n, a],
|
||||||
|
fragment(
|
||||||
|
"? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
|
||||||
|
a.actor
|
||||||
|
)
|
||||||
|
)
|
||||||
|> join(:inner, [n], activity in assoc(n, :activity))
|
|> join(:inner, [n], activity in assoc(n, :activity))
|
||||||
|> join(:left, [n, a], object in Object,
|
|> join(:left, [n, a], object in Object,
|
||||||
on:
|
on:
|
||||||
|
|
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
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Plugs.OAuthPlug do
|
||||||
|
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.OAuth.App
|
||||||
alias Pleroma.Web.OAuth.Token
|
alias Pleroma.Web.OAuth.Token
|
||||||
|
|
||||||
@realm_reg Regex.compile!("Bearer\:?\s+(.*)$", "i")
|
@realm_reg Regex.compile!("Bearer\:?\s+(.*)$", "i")
|
||||||
|
@ -22,18 +23,39 @@ def call(%{params: %{"access_token" => access_token}} = conn, _) do
|
||||||
|> assign(:token, token_record)
|
|> assign(:token, token_record)
|
||||||
|> assign(:user, user)
|
|> assign(:user, user)
|
||||||
else
|
else
|
||||||
_ -> conn
|
_ ->
|
||||||
|
# token found, but maybe only with app
|
||||||
|
with {:ok, app, token_record} <- fetch_app_and_token(access_token) do
|
||||||
|
conn
|
||||||
|
|> assign(:token, token_record)
|
||||||
|
|> assign(:app, app)
|
||||||
|
else
|
||||||
|
_ -> conn
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(conn, _) do
|
def call(conn, _) do
|
||||||
with {:ok, token_str} <- fetch_token_str(conn),
|
case fetch_token_str(conn) do
|
||||||
{:ok, user, token_record} <- fetch_user_and_token(token_str) do
|
{:ok, token} ->
|
||||||
conn
|
with {:ok, user, token_record} <- fetch_user_and_token(token) do
|
||||||
|> assign(:token, token_record)
|
conn
|
||||||
|> assign(:user, user)
|
|> assign(:token, token_record)
|
||||||
else
|
|> assign(:user, user)
|
||||||
_ -> conn
|
else
|
||||||
|
_ ->
|
||||||
|
# token found, but maybe only with app
|
||||||
|
with {:ok, app, token_record} <- fetch_app_and_token(token) do
|
||||||
|
conn
|
||||||
|
|> assign(:token, token_record)
|
||||||
|
|> assign(:app, app)
|
||||||
|
else
|
||||||
|
_ -> conn
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
conn
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -54,6 +76,16 @@ defp fetch_user_and_token(token) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec fetch_app_and_token(String.t()) :: {:ok, App.t(), Token.t()} | nil
|
||||||
|
defp fetch_app_and_token(token) do
|
||||||
|
query =
|
||||||
|
from(t in Token, where: t.token == ^token, join: app in assoc(t, :app), preload: [app: app])
|
||||||
|
|
||||||
|
with %Token{app: app} = token_record <- Repo.one(query) do
|
||||||
|
{:ok, app, token_record}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Gets token from session by :oauth_token key
|
# Gets token from session by :oauth_token key
|
||||||
#
|
#
|
||||||
@spec fetch_token_from_session(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()}
|
@spec fetch_token_from_session(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()}
|
||||||
|
|
36
lib/pleroma/plugs/rate_limit_plug.ex
Normal file
36
lib/pleroma/plugs/rate_limit_plug.ex
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Plugs.RateLimitPlug do
|
||||||
|
import Phoenix.Controller, only: [json: 2]
|
||||||
|
import Plug.Conn
|
||||||
|
|
||||||
|
def init(opts), do: opts
|
||||||
|
|
||||||
|
def call(conn, opts) do
|
||||||
|
enabled? = Pleroma.Config.get([:app_account_creation, :enabled])
|
||||||
|
|
||||||
|
case check_rate(conn, Map.put(opts, :enabled, enabled?)) do
|
||||||
|
{:ok, _count} -> conn
|
||||||
|
{:error, _count} -> render_error(conn)
|
||||||
|
%Plug.Conn{} = conn -> conn
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_rate(conn, %{enabled: true} = opts) do
|
||||||
|
max_requests = opts[:max_requests]
|
||||||
|
bucket_name = conn.remote_ip |> Tuple.to_list() |> Enum.join(".")
|
||||||
|
|
||||||
|
ExRated.check_rate(bucket_name, opts[:interval] * 1000, max_requests)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_rate(conn, _), do: conn
|
||||||
|
|
||||||
|
defp render_error(conn) do
|
||||||
|
conn
|
||||||
|
|> put_status(:forbidden)
|
||||||
|
|> json(%{error: "Rate limit exceeded."})
|
||||||
|
|> halt()
|
||||||
|
end
|
||||||
|
end
|
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
|
|
@ -14,7 +14,7 @@ def process_url(url) do
|
||||||
|
|
||||||
def process_response_body(body) do
|
def process_response_body(body) do
|
||||||
body
|
body
|
||||||
|> Poison.decode!()
|
|> Jason.decode!()
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_token do
|
def get_token do
|
||||||
|
@ -38,7 +38,7 @@ def get_token do
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_auth_body(username, password, tenant) do
|
def make_auth_body(username, password, tenant) do
|
||||||
Poison.encode!(%{
|
Jason.encode!(%{
|
||||||
:auth => %{
|
:auth => %{
|
||||||
:passwordCredentials => %{
|
:passwordCredentials => %{
|
||||||
:username => username,
|
:username => username,
|
||||||
|
|
|
@ -105,10 +105,8 @@ def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
|
||||||
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
|
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
|
||||||
|
|
||||||
def user_info(%User{} = user) do
|
def user_info(%User{} = user) do
|
||||||
oneself = if user.local, do: 1, else: 0
|
|
||||||
|
|
||||||
%{
|
%{
|
||||||
following_count: length(user.following) - oneself,
|
following_count: following_count(user),
|
||||||
note_count: user.info.note_count,
|
note_count: user.info.note_count,
|
||||||
follower_count: user.info.follower_count,
|
follower_count: user.info.follower_count,
|
||||||
locked: user.info.locked,
|
locked: user.info.locked,
|
||||||
|
@ -117,6 +115,20 @@ def user_info(%User{} = user) do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def restrict_deactivated(query) do
|
||||||
|
from(u in query,
|
||||||
|
where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def following_count(%User{following: []}), do: 0
|
||||||
|
|
||||||
|
def following_count(%User{} = user) do
|
||||||
|
user
|
||||||
|
|> get_friends_query()
|
||||||
|
|> Repo.aggregate(:count, :id)
|
||||||
|
end
|
||||||
|
|
||||||
def remote_user_creation(params) do
|
def remote_user_creation(params) do
|
||||||
params =
|
params =
|
||||||
params
|
params
|
||||||
|
@ -204,14 +216,15 @@ def reset_password(user, data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_changeset(struct, params \\ %{}, opts \\ []) do
|
def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||||
confirmation_status =
|
need_confirmation? =
|
||||||
if opts[:confirmed] || !Pleroma.Config.get([:instance, :account_activation_required]) do
|
if is_nil(opts[:need_confirmation]) do
|
||||||
:confirmed
|
Pleroma.Config.get([:instance, :account_activation_required])
|
||||||
else
|
else
|
||||||
:unconfirmed
|
opts[:need_confirmation]
|
||||||
end
|
end
|
||||||
|
|
||||||
info_change = User.Info.confirmation_changeset(%User.Info{}, confirmation_status)
|
info_change =
|
||||||
|
User.Info.confirmation_changeset(%User.Info{}, need_confirmation: need_confirmation?)
|
||||||
|
|
||||||
changeset =
|
changeset =
|
||||||
struct
|
struct
|
||||||
|
@ -254,7 +267,7 @@ defp autofollow_users(user) do
|
||||||
candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
|
candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
|
||||||
|
|
||||||
autofollowed_users =
|
autofollowed_users =
|
||||||
User.Query.build(%{nickname: candidates, local: true})
|
User.Query.build(%{nickname: candidates, local: true, deactivated: false})
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|
|
||||||
follow_all(user, autofollowed_users)
|
follow_all(user, autofollowed_users)
|
||||||
|
@ -412,24 +425,6 @@ def following?(%User{} = follower, %User{} = followed) do
|
||||||
Enum.member?(follower.following, followed.follower_address)
|
Enum.member?(follower.following, followed.follower_address)
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow_import(%User{} = follower, followed_identifiers)
|
|
||||||
when is_list(followed_identifiers) do
|
|
||||||
Enum.map(
|
|
||||||
followed_identifiers,
|
|
||||||
fn followed_identifier ->
|
|
||||||
with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
|
|
||||||
{:ok, follower} <- maybe_direct_follow(follower, followed),
|
|
||||||
{:ok, _} <- ActivityPub.follow(follower, followed) do
|
|
||||||
followed
|
|
||||||
else
|
|
||||||
err ->
|
|
||||||
Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
|
|
||||||
err
|
|
||||||
end
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def locked?(%User{} = user) do
|
def locked?(%User{} = user) do
|
||||||
user.info.locked || false
|
user.info.locked || false
|
||||||
end
|
end
|
||||||
|
@ -551,8 +546,7 @@ def get_or_fetch_by_nickname(nickname) do
|
||||||
with [_nick, _domain] <- String.split(nickname, "@"),
|
with [_nick, _domain] <- String.split(nickname, "@"),
|
||||||
{:ok, user} <- fetch_by_nickname(nickname) do
|
{:ok, user} <- fetch_by_nickname(nickname) do
|
||||||
if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
|
if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
|
||||||
# TODO turn into job
|
fetch_initial_posts(user)
|
||||||
{:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
|
@ -563,19 +557,12 @@ def get_or_fetch_by_nickname(nickname) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Fetch some posts when the user has just been federated with"
|
@doc "Fetch some posts when the user has just been federated with"
|
||||||
def fetch_initial_posts(user) do
|
def fetch_initial_posts(user),
|
||||||
pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
|
do: PleromaJobQueue.enqueue(:background, __MODULE__, [:fetch_initial_posts, user])
|
||||||
|
|
||||||
Enum.each(
|
|
||||||
# Insert all the posts in reverse order, so they're in the right order on the timeline
|
|
||||||
Enum.reverse(Utils.fetch_ordered_collection(user.info.source_data["outbox"], pages)),
|
|
||||||
&Pleroma.Web.Federator.incoming_ap_doc/1
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
|
@spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
|
||||||
def get_followers_query(%User{} = user, nil) do
|
def get_followers_query(%User{} = user, nil) do
|
||||||
User.Query.build(%{followers: user})
|
User.Query.build(%{followers: user, deactivated: false})
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_followers_query(user, page) do
|
def get_followers_query(user, page) do
|
||||||
|
@ -600,7 +587,7 @@ def get_followers_ids(user, page \\ nil) do
|
||||||
|
|
||||||
@spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
|
@spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
|
||||||
def get_friends_query(%User{} = user, nil) do
|
def get_friends_query(%User{} = user, nil) do
|
||||||
User.Query.build(%{friends: user})
|
User.Query.build(%{friends: user, deactivated: false})
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_friends_query(user, page) do
|
def get_friends_query(user, page) do
|
||||||
|
@ -690,16 +677,16 @@ def update_note_count(%User{} = user) do
|
||||||
|
|
||||||
info_cng = User.Info.set_note_count(user.info, note_count)
|
info_cng = User.Info.set_note_count(user.info, note_count)
|
||||||
|
|
||||||
cng =
|
user
|
||||||
change(user)
|
|> change()
|
||||||
|> put_embed(:info, info_cng)
|
|> put_embed(:info, info_cng)
|
||||||
|
|> update_and_set_cache()
|
||||||
update_and_set_cache(cng)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_follower_count(%User{} = user) do
|
def update_follower_count(%User{} = user) do
|
||||||
follower_count_query =
|
follower_count_query =
|
||||||
User.Query.build(%{followers: user}) |> select([u], %{count: count(u.id)})
|
User.Query.build(%{followers: user, deactivated: false})
|
||||||
|
|> select([u], %{count: count(u.id)})
|
||||||
|
|
||||||
User
|
User
|
||||||
|> where(id: ^user.id)
|
|> where(id: ^user.id)
|
||||||
|
@ -724,7 +711,7 @@ def update_follower_count(%User{} = user) do
|
||||||
|
|
||||||
@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}
|
criteria = %{ap_id: ap_ids, deactivated: false}
|
||||||
criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
|
criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
|
||||||
|
|
||||||
User.Query.build(criteria)
|
User.Query.build(criteria)
|
||||||
|
@ -733,7 +720,7 @@ def get_users_from_set(ap_ids, local_only \\ true) do
|
||||||
|
|
||||||
@spec get_recipients_from_activity(Activity.t()) :: [User.t()]
|
@spec get_recipients_from_activity(Activity.t()) :: [User.t()]
|
||||||
def get_recipients_from_activity(%Activity{recipients: to}) do
|
def get_recipients_from_activity(%Activity{recipients: to}) do
|
||||||
User.Query.build(%{recipients_from_activity: to, local: true})
|
User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -831,6 +818,7 @@ defp fts_search_subquery(term, query \\ User) do
|
||||||
^processed_query
|
^processed_query
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|> restrict_deactivated()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp trigram_search_subquery(term) do
|
defp trigram_search_subquery(term) do
|
||||||
|
@ -849,23 +837,7 @@ defp trigram_search_subquery(term) do
|
||||||
},
|
},
|
||||||
where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term)
|
where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term)
|
||||||
)
|
)
|
||||||
end
|
|> restrict_deactivated()
|
||||||
|
|
||||||
def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
|
|
||||||
Enum.map(
|
|
||||||
blocked_identifiers,
|
|
||||||
fn blocked_identifier ->
|
|
||||||
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
|
|
||||||
{:ok, blocker} <- block(blocker, blocked),
|
|
||||||
{:ok, _} <- ActivityPub.block(blocker, blocked) do
|
|
||||||
blocked
|
|
||||||
else
|
|
||||||
err ->
|
|
||||||
Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
|
|
||||||
err
|
|
||||||
end
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def mute(muter, %User{ap_id: ap_id}) do
|
def mute(muter, %User{ap_id: ap_id}) do
|
||||||
|
@ -998,19 +970,19 @@ def subscribed_to?(user, %{ap_id: ap_id}) do
|
||||||
|
|
||||||
@spec muted_users(User.t()) :: [User.t()]
|
@spec muted_users(User.t()) :: [User.t()]
|
||||||
def muted_users(user) do
|
def muted_users(user) do
|
||||||
User.Query.build(%{ap_id: user.info.mutes})
|
User.Query.build(%{ap_id: user.info.mutes, deactivated: false})
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec blocked_users(User.t()) :: [User.t()]
|
@spec blocked_users(User.t()) :: [User.t()]
|
||||||
def blocked_users(user) do
|
def blocked_users(user) do
|
||||||
User.Query.build(%{ap_id: user.info.blocks})
|
User.Query.build(%{ap_id: user.info.blocks, deactivated: false})
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec subscribers(User.t()) :: [User.t()]
|
@spec subscribers(User.t()) :: [User.t()]
|
||||||
def subscribers(user) do
|
def subscribers(user) do
|
||||||
User.Query.build(%{ap_id: user.info.subscribers})
|
User.Query.build(%{ap_id: user.info.subscribers, deactivated: false})
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1038,14 +1010,25 @@ def unblock_domain(user, domain) do
|
||||||
update_and_set_cache(cng)
|
update_and_set_cache(cng)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def deactivate_async(user, status \\ true) do
|
||||||
|
PleromaJobQueue.enqueue(:background, __MODULE__, [:deactivate_async, user, status])
|
||||||
|
end
|
||||||
|
|
||||||
def deactivate(%User{} = user, status \\ true) do
|
def deactivate(%User{} = user, status \\ true) do
|
||||||
info_cng = User.Info.set_activation_status(user.info, status)
|
info_cng = User.Info.set_activation_status(user.info, status)
|
||||||
|
|
||||||
cng =
|
with {:ok, friends} <- User.get_friends(user),
|
||||||
change(user)
|
{:ok, followers} <- User.get_followers(user),
|
||||||
|> put_embed(:info, info_cng)
|
{:ok, user} <-
|
||||||
|
user
|
||||||
|
|> change()
|
||||||
|
|> put_embed(:info, info_cng)
|
||||||
|
|> update_and_set_cache() do
|
||||||
|
Enum.each(followers, &invalidate_cache(&1))
|
||||||
|
Enum.each(friends, &update_follower_count(&1))
|
||||||
|
|
||||||
update_and_set_cache(cng)
|
{:ok, user}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_notification_settings(%User{} = user, settings \\ %{}) do
|
def update_notification_settings(%User{} = user, settings \\ %{}) do
|
||||||
|
@ -1076,6 +1059,75 @@ def perform(:delete, %User{} = user) do
|
||||||
delete_user_activities(user)
|
delete_user_activities(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec perform(atom(), User.t()) :: {:ok, User.t()}
|
||||||
|
def perform(:fetch_initial_posts, %User{} = user) do
|
||||||
|
pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
|
||||||
|
|
||||||
|
Enum.each(
|
||||||
|
# Insert all the posts in reverse order, so they're in the right order on the timeline
|
||||||
|
Enum.reverse(Utils.fetch_ordered_collection(user.info.source_data["outbox"], pages)),
|
||||||
|
&Pleroma.Web.Federator.incoming_ap_doc/1
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, user}
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform(:deactivate_async, user, status), do: deactivate(user, status)
|
||||||
|
|
||||||
|
@spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
|
||||||
|
def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
|
||||||
|
when is_list(blocked_identifiers) do
|
||||||
|
Enum.map(
|
||||||
|
blocked_identifiers,
|
||||||
|
fn blocked_identifier ->
|
||||||
|
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
|
||||||
|
{:ok, blocker} <- block(blocker, blocked),
|
||||||
|
{:ok, _} <- ActivityPub.block(blocker, blocked) do
|
||||||
|
blocked
|
||||||
|
else
|
||||||
|
err ->
|
||||||
|
Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
|
||||||
|
err
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
|
||||||
|
def perform(:follow_import, %User{} = follower, followed_identifiers)
|
||||||
|
when is_list(followed_identifiers) do
|
||||||
|
Enum.map(
|
||||||
|
followed_identifiers,
|
||||||
|
fn followed_identifier ->
|
||||||
|
with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
|
||||||
|
{:ok, follower} <- maybe_direct_follow(follower, followed),
|
||||||
|
{:ok, _} <- ActivityPub.follow(follower, followed) do
|
||||||
|
followed
|
||||||
|
else
|
||||||
|
err ->
|
||||||
|
Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
|
||||||
|
err
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers),
|
||||||
|
do:
|
||||||
|
PleromaJobQueue.enqueue(:background, __MODULE__, [
|
||||||
|
:blocks_import,
|
||||||
|
blocker,
|
||||||
|
blocked_identifiers
|
||||||
|
])
|
||||||
|
|
||||||
|
def follow_import(%User{} = follower, followed_identifiers) when is_list(followed_identifiers),
|
||||||
|
do:
|
||||||
|
PleromaJobQueue.enqueue(:background, __MODULE__, [
|
||||||
|
:follow_import,
|
||||||
|
follower,
|
||||||
|
followed_identifiers
|
||||||
|
])
|
||||||
|
|
||||||
def delete_user_activities(%User{ap_id: ap_id} = user) do
|
def delete_user_activities(%User{ap_id: ap_id} = user) do
|
||||||
stream =
|
stream =
|
||||||
ap_id
|
ap_id
|
||||||
|
@ -1129,8 +1181,8 @@ def get_or_fetch_by_ap_id(ap_id) do
|
||||||
resp = fetch_by_ap_id(ap_id)
|
resp = fetch_by_ap_id(ap_id)
|
||||||
|
|
||||||
if should_fetch_initial do
|
if should_fetch_initial do
|
||||||
with {:ok, %User{} = user} = resp do
|
with {:ok, %User{} = user} <- resp do
|
||||||
{:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
|
fetch_initial_posts(user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1319,11 +1371,24 @@ def error_user(ap_id) do
|
||||||
|
|
||||||
@spec all_superusers() :: [User.t()]
|
@spec all_superusers() :: [User.t()]
|
||||||
def all_superusers do
|
def all_superusers do
|
||||||
User.Query.build(%{super_users: true, local: true})
|
User.Query.build(%{super_users: true, local: true, deactivated: false})
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -8,6 +8,8 @@ defmodule Pleroma.User.Info do
|
||||||
|
|
||||||
alias Pleroma.User.Info
|
alias Pleroma.User.Info
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:banner, :map, default: %{})
|
field(:banner, :map, default: %{})
|
||||||
field(:background, :map, default: %{})
|
field(:background, :map, default: %{})
|
||||||
|
@ -210,21 +212,23 @@ def profile_update(info, params) do
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
def confirmation_changeset(info, :confirmed) do
|
@spec confirmation_changeset(Info.t(), keyword()) :: Changeset.t()
|
||||||
confirmation_changeset(info, %{
|
def confirmation_changeset(info, opts) do
|
||||||
confirmation_pending: false,
|
need_confirmation? = Keyword.get(opts, :need_confirmation)
|
||||||
confirmation_token: nil
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
def confirmation_changeset(info, :unconfirmed) do
|
params =
|
||||||
confirmation_changeset(info, %{
|
if need_confirmation? do
|
||||||
confirmation_pending: true,
|
%{
|
||||||
confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
|
confirmation_pending: true,
|
||||||
})
|
confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
|
||||||
end
|
}
|
||||||
|
else
|
||||||
|
%{
|
||||||
|
confirmation_pending: false,
|
||||||
|
confirmation_token: nil
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def confirmation_changeset(info, params) do
|
|
||||||
cast(info, params, [:confirmation_pending, :confirmation_token])
|
cast(info, params, [:confirmation_pending, :confirmation_token])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,11 @@ defp compose_query({:active, _}, query) do
|
||||||
|> where([u], not is_nil(u.nickname))
|
|> where([u], not is_nil(u.nickname))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp compose_query({:deactivated, _}, query) do
|
defp compose_query({:deactivated, false}, query) do
|
||||||
|
User.restrict_deactivated(query)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp compose_query({:deactivated, true}, query) do
|
||||||
where(query, [u], fragment("?->'deactivated' @> 'true'", u.info))
|
where(query, [u], fragment("?->'deactivated' @> 'true'", u.info))
|
||||||
|> where([u], not is_nil(u.nickname))
|
|> where([u], not is_nil(u.nickname))
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Conversation
|
alias Pleroma.Conversation
|
||||||
alias Pleroma.Instances
|
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Object.Fetcher
|
alias Pleroma.Object.Fetcher
|
||||||
|
@ -15,7 +14,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.MRF
|
alias Pleroma.Web.ActivityPub.MRF
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
alias Pleroma.Web.Federator
|
|
||||||
alias Pleroma.Web.WebFinger
|
alias Pleroma.Web.WebFinger
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
@ -24,8 +22,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
|
||||||
|
|
||||||
# For Announce activities, we filter the recipients based on following status for any actors
|
# For Announce activities, we filter the recipients based on following status for any actors
|
||||||
# that match actual users. See issue #164 for more information about why this is necessary.
|
# that match actual users. See issue #164 for more information about why this is necessary.
|
||||||
defp get_recipients(%{"type" => "Announce"} = data) do
|
defp get_recipients(%{"type" => "Announce"} = data) do
|
||||||
|
@ -137,9 +133,7 @@ def insert(map, local \\ true, fake \\ false) when is_map(map) do
|
||||||
activity
|
activity
|
||||||
end
|
end
|
||||||
|
|
||||||
Task.start(fn ->
|
PleromaJobQueue.enqueue(:background, Pleroma.Web.RichMedia.Helpers, [:fetch, activity])
|
||||||
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
|
|
||||||
end)
|
|
||||||
|
|
||||||
Notification.create_notifications(activity)
|
Notification.create_notifications(activity)
|
||||||
|
|
||||||
|
@ -546,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}")
|
||||||
|
@ -563,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
|
||||||
|
|
||||||
|
@ -575,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
|
||||||
|
@ -701,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,
|
||||||
|
@ -756,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:
|
||||||
|
@ -767,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
|
||||||
|
|
||||||
|
@ -849,15 +861,18 @@ 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)
|
||||||
|> restrict_muted_reblogs(opts)
|
|> restrict_muted_reblogs(opts)
|
||||||
|
|> Activity.restrict_deactivated_users()
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_activities(recipients, opts \\ %{}) do
|
def fetch_activities(recipients, opts \\ %{}) do
|
||||||
|
@ -961,89 +976,6 @@ def make_user_from_nickname(nickname) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def should_federate?(inbox, public) do
|
|
||||||
if public do
|
|
||||||
true
|
|
||||||
else
|
|
||||||
inbox_info = URI.parse(inbox)
|
|
||||||
!Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def publish(actor, activity) do
|
|
||||||
remote_followers =
|
|
||||||
if actor.follower_address in activity.recipients do
|
|
||||||
{:ok, followers} = User.get_followers(actor)
|
|
||||||
followers |> Enum.filter(&(!&1.local))
|
|
||||||
else
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
|
|
||||||
public = is_public?(activity)
|
|
||||||
|
|
||||||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
|
||||||
json = Jason.encode!(data)
|
|
||||||
|
|
||||||
(Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers)
|
|
||||||
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
|
|
||||||
|> Enum.map(fn %{info: %{source_data: data}} ->
|
|
||||||
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
|
|
||||||
end)
|
|
||||||
|> Enum.uniq()
|
|
||||||
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|
|
||||||
|> Instances.filter_reachable()
|
|
||||||
|> Enum.each(fn {inbox, unreachable_since} ->
|
|
||||||
Federator.publish_single_ap(%{
|
|
||||||
inbox: inbox,
|
|
||||||
json: json,
|
|
||||||
actor: actor,
|
|
||||||
id: activity.data["id"],
|
|
||||||
unreachable_since: unreachable_since
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
def publish_one(%{inbox: inbox, json: json, actor: actor, id: id} = params) do
|
|
||||||
Logger.info("Federating #{id} to #{inbox}")
|
|
||||||
host = URI.parse(inbox).host
|
|
||||||
|
|
||||||
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
|
|
||||||
|
|
||||||
date =
|
|
||||||
NaiveDateTime.utc_now()
|
|
||||||
|> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
|
|
||||||
|
|
||||||
signature =
|
|
||||||
Pleroma.Web.HTTPSignatures.sign(actor, %{
|
|
||||||
host: host,
|
|
||||||
"content-length": byte_size(json),
|
|
||||||
digest: digest,
|
|
||||||
date: date
|
|
||||||
})
|
|
||||||
|
|
||||||
with {:ok, %{status: code}} when code in 200..299 <-
|
|
||||||
result =
|
|
||||||
@httpoison.post(
|
|
||||||
inbox,
|
|
||||||
json,
|
|
||||||
[
|
|
||||||
{"Content-Type", "application/activity+json"},
|
|
||||||
{"Date", date},
|
|
||||||
{"signature", signature},
|
|
||||||
{"digest", digest}
|
|
||||||
]
|
|
||||||
) do
|
|
||||||
if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
|
|
||||||
do: Instances.set_reachable(inbox)
|
|
||||||
|
|
||||||
result
|
|
||||||
else
|
|
||||||
{_post_result, response} ->
|
|
||||||
unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
|
|
||||||
{:error, response}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# filter out broken threads
|
# filter out broken threads
|
||||||
def contain_broken_threads(%Activity{} = activity, %User{} = user) do
|
def contain_broken_threads(%Activity{} = activity, %User{} = user) do
|
||||||
entire_thread_visible_for_user?(activity, user)
|
entire_thread_visible_for_user?(activity, user)
|
||||||
|
@ -1054,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
|
||||||
|
|
152
lib/pleroma/web/activity_pub/publisher.ex
Normal file
152
lib/pleroma/web/activity_pub/publisher.ex
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.Publisher do
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Instances
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.Relay
|
||||||
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
|
||||||
|
import Pleroma.Web.ActivityPub.Visibility
|
||||||
|
|
||||||
|
@behaviour Pleroma.Web.Federator.Publisher
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||||
|
|
||||||
|
@moduledoc """
|
||||||
|
ActivityPub outgoing federation module.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Determine if an activity can be represented by running it through Transmogrifier.
|
||||||
|
"""
|
||||||
|
def is_representable?(%Activity{} = activity) do
|
||||||
|
with {:ok, _data} <- Transmogrifier.prepare_outgoing(activity.data) do
|
||||||
|
true
|
||||||
|
else
|
||||||
|
_e ->
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Publish a single message to a peer. Takes a struct with the following
|
||||||
|
parameters set:
|
||||||
|
|
||||||
|
* `inbox`: the inbox to publish to
|
||||||
|
* `json`: the JSON message body representing the ActivityPub message
|
||||||
|
* `actor`: the actor which is signing the message
|
||||||
|
* `id`: the ActivityStreams URI of the message
|
||||||
|
"""
|
||||||
|
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
|
||||||
|
Logger.info("Federating #{id} to #{inbox}")
|
||||||
|
host = URI.parse(inbox).host
|
||||||
|
|
||||||
|
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
|
||||||
|
|
||||||
|
date =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
|
||||||
|
|
||||||
|
signature =
|
||||||
|
Pleroma.Signature.sign(actor, %{
|
||||||
|
host: host,
|
||||||
|
"content-length": byte_size(json),
|
||||||
|
digest: digest,
|
||||||
|
date: date
|
||||||
|
})
|
||||||
|
|
||||||
|
with {:ok, %{status: code}} when code in 200..299 <-
|
||||||
|
result =
|
||||||
|
@httpoison.post(
|
||||||
|
inbox,
|
||||||
|
json,
|
||||||
|
[
|
||||||
|
{"Content-Type", "application/activity+json"},
|
||||||
|
{"Date", date},
|
||||||
|
{"signature", signature},
|
||||||
|
{"digest", digest}
|
||||||
|
]
|
||||||
|
) do
|
||||||
|
if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
|
||||||
|
do: Instances.set_reachable(inbox)
|
||||||
|
|
||||||
|
result
|
||||||
|
else
|
||||||
|
{_post_result, response} ->
|
||||||
|
unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
|
||||||
|
{:error, response}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp should_federate?(inbox, public) do
|
||||||
|
if public do
|
||||||
|
true
|
||||||
|
else
|
||||||
|
inbox_info = URI.parse(inbox)
|
||||||
|
!Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Publishes an activity to all relevant peers.
|
||||||
|
"""
|
||||||
|
def publish(%User{} = actor, %Activity{} = activity) do
|
||||||
|
remote_followers =
|
||||||
|
if actor.follower_address in activity.recipients do
|
||||||
|
{:ok, followers} = User.get_followers(actor)
|
||||||
|
followers |> Enum.filter(&(!&1.local))
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
public = is_public?(activity)
|
||||||
|
|
||||||
|
if public && Config.get([:instance, :allow_relay]) do
|
||||||
|
Logger.info(fn -> "Relaying #{activity.data["id"]} out" end)
|
||||||
|
Relay.publish(activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
|
json = Jason.encode!(data)
|
||||||
|
|
||||||
|
(Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers)
|
||||||
|
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
|
||||||
|
|> Enum.map(fn %{info: %{source_data: data}} ->
|
||||||
|
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
|
||||||
|
end)
|
||||||
|
|> Enum.uniq()
|
||||||
|
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|
||||||
|
|> Instances.filter_reachable()
|
||||||
|
|> Enum.each(fn {inbox, unreachable_since} ->
|
||||||
|
Pleroma.Web.Federator.Publisher.enqueue_one(
|
||||||
|
__MODULE__,
|
||||||
|
%{
|
||||||
|
inbox: inbox,
|
||||||
|
json: json,
|
||||||
|
actor: actor,
|
||||||
|
id: activity.data["id"],
|
||||||
|
unreachable_since: unreachable_since
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def gather_webfinger_links(%User{} = user) do
|
||||||
|
[
|
||||||
|
%{"rel" => "self", "type" => "application/activity+json", "href" => user.ap_id},
|
||||||
|
%{
|
||||||
|
"rel" => "self",
|
||||||
|
"type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
|
||||||
|
"href" => user.ap_id
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def gather_nodeinfo_protocol_names, do: ["activitypub"]
|
||||||
|
end
|
|
@ -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
|
||||||
|
@ -682,7 +685,7 @@ def make_flag_data(params, additional) do
|
||||||
"""
|
"""
|
||||||
def fetch_ordered_collection(from, pages_left, acc \\ []) do
|
def fetch_ordered_collection(from, pages_left, acc \\ []) do
|
||||||
with {:ok, response} <- Tesla.get(from),
|
with {:ok, response} <- Tesla.get(from),
|
||||||
{:ok, collection} <- Poison.decode(response.body) do
|
{:ok, collection} <- Jason.decode(response.body) do
|
||||||
case collection["type"] do
|
case collection["type"] do
|
||||||
"OrderedCollection" ->
|
"OrderedCollection" ->
|
||||||
# If we've encountered the OrderedCollection and not the page,
|
# If we've encountered the OrderedCollection and not the page,
|
||||||
|
@ -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]
|
||||||
|
|
||||||
|
@ -59,7 +64,7 @@ def user_create(
|
||||||
bio: "."
|
bio: "."
|
||||||
}
|
}
|
||||||
|
|
||||||
changeset = User.register_changeset(%User{}, user_data, confirmed: true)
|
changeset = User.register_changeset(%User{}, user_data, need_confirmation: false)
|
||||||
{:ok, user} = User.register(changeset)
|
{:ok, user} = User.register(changeset)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
@ -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
|
|
@ -74,7 +74,7 @@ def create_from_registration(
|
||||||
password_confirmation: random_password
|
password_confirmation: random_password
|
||||||
},
|
},
|
||||||
external: true,
|
external: true,
|
||||||
confirmed: true
|
need_confirmation: false
|
||||||
)
|
)
|
||||||
|> Repo.insert(),
|
|> Repo.insert(),
|
||||||
{:ok, _} <-
|
{:ok, _} <-
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,10 @@ defmodule Pleroma.Web.Federator do
|
||||||
alias Pleroma.Object.Containment
|
alias Pleroma.Object.Containment
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Relay
|
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.Federator.Publisher
|
||||||
alias Pleroma.Web.Federator.RetryQueue
|
alias Pleroma.Web.Federator.RetryQueue
|
||||||
alias Pleroma.Web.OStatus
|
|
||||||
alias Pleroma.Web.Salmon
|
|
||||||
alias Pleroma.Web.WebFinger
|
alias Pleroma.Web.WebFinger
|
||||||
alias Pleroma.Web.Websub
|
alias Pleroma.Web.Websub
|
||||||
|
|
||||||
|
@ -42,14 +39,6 @@ def publish(activity, priority \\ 1) do
|
||||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish, activity], priority)
|
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish, activity], priority)
|
||||||
end
|
end
|
||||||
|
|
||||||
def publish_single_ap(params) do
|
|
||||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_ap, params])
|
|
||||||
end
|
|
||||||
|
|
||||||
def publish_single_websub(websub) do
|
|
||||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_websub, websub])
|
|
||||||
end
|
|
||||||
|
|
||||||
def verify_websub(websub) do
|
def verify_websub(websub) do
|
||||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:verify_websub, websub])
|
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:verify_websub, websub])
|
||||||
end
|
end
|
||||||
|
@ -62,10 +51,6 @@ def refresh_subscriptions do
|
||||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:refresh_subscriptions])
|
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:refresh_subscriptions])
|
||||||
end
|
end
|
||||||
|
|
||||||
def publish_single_salmon(params) do
|
|
||||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_salmon, params])
|
|
||||||
end
|
|
||||||
|
|
||||||
# Job Worker Callbacks
|
# Job Worker Callbacks
|
||||||
|
|
||||||
def perform(:refresh_subscriptions) do
|
def perform(:refresh_subscriptions) do
|
||||||
|
@ -95,23 +80,7 @@ def perform(:publish, activity) do
|
||||||
with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||||
{:ok, actor} = WebFinger.ensure_keys_present(actor)
|
{:ok, actor} = WebFinger.ensure_keys_present(actor)
|
||||||
|
|
||||||
if Visibility.is_public?(activity) do
|
Publisher.publish(actor, activity)
|
||||||
if OStatus.is_representable?(activity) do
|
|
||||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via WebSub" end)
|
|
||||||
Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
|
|
||||||
|
|
||||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via Salmon" end)
|
|
||||||
Pleroma.Web.Salmon.publish(actor, activity)
|
|
||||||
end
|
|
||||||
|
|
||||||
if Keyword.get(Application.get_env(:pleroma, :instance), :allow_relay) do
|
|
||||||
Logger.info(fn -> "Relaying #{activity.data["id"]} out" end)
|
|
||||||
Relay.publish(activity)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via AP" end)
|
|
||||||
Pleroma.Web.ActivityPub.ActivityPub.publish(actor, activity)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -148,25 +117,11 @@ def perform(:incoming_ap_doc, params) do
|
||||||
_e ->
|
_e ->
|
||||||
# Just drop those for now
|
# Just drop those for now
|
||||||
Logger.info("Unhandled activity")
|
Logger.info("Unhandled activity")
|
||||||
Logger.info(Poison.encode!(params, pretty: 2))
|
Logger.info(Jason.encode!(params, pretty: true))
|
||||||
:error
|
:error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform(:publish_single_salmon, params) do
|
|
||||||
Salmon.send_to_user(params)
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform(:publish_single_ap, params) do
|
|
||||||
case ActivityPub.publish_one(params) do
|
|
||||||
{:ok, _} ->
|
|
||||||
:ok
|
|
||||||
|
|
||||||
{:error, _} ->
|
|
||||||
RetryQueue.enqueue(params, ActivityPub)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform(
|
def perform(
|
||||||
:publish_single_websub,
|
:publish_single_websub,
|
||||||
%{xml: _xml, topic: _topic, callback: _callback, secret: _secret} = params
|
%{xml: _xml, topic: _topic, callback: _callback, secret: _secret} = params
|
||||||
|
|
95
lib/pleroma/web/federator/publisher.ex
Normal file
95
lib/pleroma/web/federator/publisher.ex
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.Federator.Publisher do
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.Federator.RetryQueue
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@moduledoc """
|
||||||
|
Defines the contract used by federation implementations to publish messages to
|
||||||
|
their peers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Determine whether an activity can be relayed using the federation module.
|
||||||
|
"""
|
||||||
|
@callback is_representable?(Pleroma.Activity.t()) :: boolean()
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Relays an activity to a specified peer, determined by the parameters. The
|
||||||
|
parameters used are controlled by the federation module.
|
||||||
|
"""
|
||||||
|
@callback publish_one(Map.t()) :: {:ok, Map.t()} | {:error, any()}
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Enqueue publishing a single activity.
|
||||||
|
"""
|
||||||
|
@spec enqueue_one(module(), Map.t()) :: :ok
|
||||||
|
def enqueue_one(module, %{} = params),
|
||||||
|
do: PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_one, module, params])
|
||||||
|
|
||||||
|
@spec perform(atom(), module(), any()) :: {:ok, any()} | {:error, any()}
|
||||||
|
def perform(:publish_one, module, params) do
|
||||||
|
case apply(module, :publish_one, [params]) do
|
||||||
|
{:ok, _} ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
{:error, _e} ->
|
||||||
|
RetryQueue.enqueue(params, module)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform(type, _, _) do
|
||||||
|
Logger.debug("Unknown task: #{type}")
|
||||||
|
{:error, "Don't know what to do with this"}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Relays an activity to all specified peers.
|
||||||
|
"""
|
||||||
|
@callback publish(Pleroma.User.t(), Pleroma.Activity.t()) :: :ok | {:error, any()}
|
||||||
|
|
||||||
|
@spec publish(Pleroma.User.t(), Pleroma.Activity.t()) :: :ok
|
||||||
|
def publish(%User{} = user, %Activity{} = activity) do
|
||||||
|
Config.get([:instance, :federation_publisher_modules])
|
||||||
|
|> Enum.each(fn module ->
|
||||||
|
if module.is_representable?(activity) do
|
||||||
|
Logger.info("Publishing #{activity.data["id"]} using #{inspect(module)}")
|
||||||
|
module.publish(user, activity)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gathers links used by an outgoing federation module for WebFinger output.
|
||||||
|
"""
|
||||||
|
@callback gather_webfinger_links(Pleroma.User.t()) :: list()
|
||||||
|
|
||||||
|
@spec gather_webfinger_links(Pleroma.User.t()) :: list()
|
||||||
|
def gather_webfinger_links(%User{} = user) do
|
||||||
|
Config.get([:instance, :federation_publisher_modules])
|
||||||
|
|> Enum.reduce([], fn module, links ->
|
||||||
|
links ++ module.gather_webfinger_links(user)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gathers nodeinfo protocol names supported by the federation module.
|
||||||
|
"""
|
||||||
|
@callback gather_nodeinfo_protocol_names() :: list()
|
||||||
|
|
||||||
|
@spec gather_nodeinfo_protocol_names() :: list()
|
||||||
|
def gather_nodeinfo_protocol_names do
|
||||||
|
Config.get([:instance, :federation_publisher_modules])
|
||||||
|
|> Enum.reduce([], fn module, links ->
|
||||||
|
links ++ module.gather_nodeinfo_protocol_names()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
|
@ -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
|
|
|
@ -39,12 +39,22 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
alias Pleroma.Web.OAuth.Authorization
|
alias Pleroma.Web.OAuth.Authorization
|
||||||
alias Pleroma.Web.OAuth.Scopes
|
alias Pleroma.Web.OAuth.Scopes
|
||||||
alias Pleroma.Web.OAuth.Token
|
alias Pleroma.Web.OAuth.Token
|
||||||
|
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
||||||
|
|
||||||
alias Pleroma.Web.ControllerHelper
|
alias Pleroma.Web.ControllerHelper
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
plug(
|
||||||
|
Pleroma.Plugs.RateLimitPlug,
|
||||||
|
%{
|
||||||
|
max_requests: Config.get([:app_account_creation, :max_requests]),
|
||||||
|
interval: Config.get([:app_account_creation, :interval])
|
||||||
|
}
|
||||||
|
when action in [:account_register]
|
||||||
|
)
|
||||||
|
|
||||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||||
@local_mastodon_name "Mastodon-Local"
|
@local_mastodon_name "Mastodon-Local"
|
||||||
|
|
||||||
|
@ -168,7 +178,7 @@ def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@mastodon_api_level "2.6.5"
|
@mastodon_api_level "2.7.2"
|
||||||
|
|
||||||
def masto_instance(conn, _params) do
|
def masto_instance(conn, _params) do
|
||||||
instance = Config.get(:instance)
|
instance = Config.get(:instance)
|
||||||
|
@ -293,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
|
||||||
|
@ -1536,7 +1545,7 @@ def create_filter(
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
phrase: phrase,
|
phrase: phrase,
|
||||||
context: context,
|
context: context,
|
||||||
hide: Map.get(params, "irreversible", nil),
|
hide: Map.get(params, "irreversible", false),
|
||||||
whole_word: Map.get(params, "boolean", true)
|
whole_word: Map.get(params, "boolean", true)
|
||||||
# expires_at
|
# expires_at
|
||||||
}
|
}
|
||||||
|
@ -1693,6 +1702,53 @@ def reports(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def account_register(
|
||||||
|
%{assigns: %{app: app}} = conn,
|
||||||
|
%{"username" => nickname, "email" => _, "password" => _, "agreement" => true} = params
|
||||||
|
) do
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.take([
|
||||||
|
"email",
|
||||||
|
"captcha_solution",
|
||||||
|
"captcha_token",
|
||||||
|
"captcha_answer_data",
|
||||||
|
"token",
|
||||||
|
"password"
|
||||||
|
])
|
||||||
|
|> Map.put("nickname", nickname)
|
||||||
|
|> Map.put("fullname", params["fullname"] || nickname)
|
||||||
|
|> Map.put("bio", params["bio"] || "")
|
||||||
|
|> Map.put("confirm", params["password"])
|
||||||
|
|
||||||
|
with {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
|
||||||
|
{:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do
|
||||||
|
json(conn, %{
|
||||||
|
token_type: "Bearer",
|
||||||
|
access_token: token.token,
|
||||||
|
scope: app.scopes,
|
||||||
|
created_at: Token.Utils.format_created_at(token)
|
||||||
|
})
|
||||||
|
else
|
||||||
|
{:error, errors} ->
|
||||||
|
conn
|
||||||
|
|> put_status(400)
|
||||||
|
|> json(Jason.encode!(errors))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_register(%{assigns: %{app: _app}} = conn, _params) do
|
||||||
|
conn
|
||||||
|
|> put_status(400)
|
||||||
|
|> json(%{error: "Missing parameters"})
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_register(conn, _) do
|
||||||
|
conn
|
||||||
|
|> put_status(403)
|
||||||
|
|> json(%{error: "Invalid credentials"})
|
||||||
|
end
|
||||||
|
|
||||||
def conversations(%{assigns: %{user: user}} = conn, params) do
|
def conversations(%{assigns: %{user: user}} = conn, params) do
|
||||||
participations = Participation.for_user_with_last_activity_id(user, params)
|
participations = Participation.for_user_with_last_activity_id(user, params)
|
||||||
|
|
||||||
|
|
|
@ -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"]}"
|
||||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
alias Pleroma.Web.ActivityPub.MRF
|
alias Pleroma.Web.ActivityPub.MRF
|
||||||
|
alias Pleroma.Web.Federator.Publisher
|
||||||
|
|
||||||
plug(Pleroma.Web.FederatingPlug)
|
plug(Pleroma.Web.FederatingPlug)
|
||||||
|
|
||||||
|
@ -137,7 +138,7 @@ def raw_nodeinfo do
|
||||||
name: Pleroma.Application.name() |> String.downcase(),
|
name: Pleroma.Application.name() |> String.downcase(),
|
||||||
version: Pleroma.Application.version()
|
version: Pleroma.Application.version()
|
||||||
},
|
},
|
||||||
protocols: ["ostatus", "activitypub"],
|
protocols: Publisher.gather_nodeinfo_protocol_names(),
|
||||||
services: %{
|
services: %{
|
||||||
inbound: [],
|
inbound: [],
|
||||||
outbound: []
|
outbound: []
|
||||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.OAuth.App do
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
@type t :: %__MODULE__{}
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
schema "apps" do
|
schema "apps" do
|
||||||
field(:client_name, :string)
|
field(:client_name, :string)
|
||||||
field(:redirect_uris, :string)
|
field(:redirect_uris, :string)
|
||||||
|
|
|
@ -14,6 +14,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
@type t :: %__MODULE__{}
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
schema "oauth_authorizations" do
|
schema "oauth_authorizations" do
|
||||||
field(:token, :string)
|
field(:token, :string)
|
||||||
field(:scopes, {:array, :string}, default: [])
|
field(:scopes, {:array, :string}, default: [])
|
||||||
|
@ -25,28 +26,45 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec create_authorization(App.t(), User.t() | %{}, [String.t()] | nil) ::
|
||||||
|
{:ok, Authorization.t()} | {:error, Changeset.t()}
|
||||||
def create_authorization(%App{} = app, %User{} = user, scopes \\ nil) do
|
def create_authorization(%App{} = app, %User{} = user, scopes \\ nil) do
|
||||||
scopes = scopes || app.scopes
|
%{
|
||||||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
|
scopes: scopes || app.scopes,
|
||||||
|
|
||||||
authorization = %Authorization{
|
|
||||||
token: token,
|
|
||||||
used: false,
|
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
app_id: app.id,
|
app_id: app.id
|
||||||
scopes: scopes,
|
|
||||||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10)
|
|
||||||
}
|
}
|
||||||
|
|> create_changeset()
|
||||||
Repo.insert(authorization)
|
|> Repo.insert()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec create_changeset(map()) :: Changeset.t()
|
||||||
|
def create_changeset(attrs \\ %{}) do
|
||||||
|
%Authorization{}
|
||||||
|
|> cast(attrs, [:user_id, :app_id, :scopes, :valid_until])
|
||||||
|
|> validate_required([:app_id, :scopes])
|
||||||
|
|> add_token()
|
||||||
|
|> add_lifetime()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp add_token(changeset) do
|
||||||
|
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
|
||||||
|
put_change(changeset, :token, token)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp add_lifetime(changeset) do
|
||||||
|
put_change(changeset, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10))
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec use_changeset(Authtorizatiton.t(), map()) :: Changeset.t()
|
||||||
def use_changeset(%Authorization{} = auth, params) do
|
def use_changeset(%Authorization{} = auth, params) do
|
||||||
auth
|
auth
|
||||||
|> cast(params, [:used])
|
|> cast(params, [:used])
|
||||||
|> validate_required([:used])
|
|> validate_required([:used])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec use_token(Authorization.t()) ::
|
||||||
|
{:ok, Authorization.t()} | {:error, Changeset.t()} | {:error, String.t()}
|
||||||
def use_token(%Authorization{used: false, valid_until: valid_until} = auth) do
|
def use_token(%Authorization{used: false, valid_until: valid_until} = auth) do
|
||||||
if NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) < 0 do
|
if NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) < 0 do
|
||||||
Repo.update(use_changeset(auth, %{used: true}))
|
Repo.update(use_changeset(auth, %{used: true}))
|
||||||
|
@ -57,6 +75,7 @@ def use_token(%Authorization{used: false, valid_until: valid_until} = auth) do
|
||||||
|
|
||||||
def use_token(%Authorization{used: true}), do: {:error, "already used"}
|
def use_token(%Authorization{used: true}), do: {:error, "already used"}
|
||||||
|
|
||||||
|
@spec delete_user_authorizations(User.t()) :: {integer(), any()}
|
||||||
def delete_user_authorizations(%User{id: user_id}) do
|
def delete_user_authorizations(%User{id: user_id}) do
|
||||||
from(
|
from(
|
||||||
a in Pleroma.Web.OAuth.Authorization,
|
a in Pleroma.Web.OAuth.Authorization,
|
||||||
|
|
|
@ -19,8 +19,6 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||||
|
|
||||||
if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
|
if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
|
||||||
|
|
||||||
@expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
|
|
||||||
|
|
||||||
plug(:fetch_session)
|
plug(:fetch_session)
|
||||||
plug(:fetch_flash)
|
plug(:fetch_flash)
|
||||||
|
|
||||||
|
@ -144,14 +142,14 @@ defp handle_create_authorization_error(conn, error, %{"authorization" => _}) do
|
||||||
@doc "Renew access_token with refresh_token"
|
@doc "Renew access_token with refresh_token"
|
||||||
def token_exchange(
|
def token_exchange(
|
||||||
conn,
|
conn,
|
||||||
%{"grant_type" => "refresh_token", "refresh_token" => token} = params
|
%{"grant_type" => "refresh_token", "refresh_token" => token} = _params
|
||||||
) do
|
) do
|
||||||
with %App{} = app <- get_app_from_request(conn, params),
|
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||||
{:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
|
{:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
|
||||||
{:ok, token} <- RefreshToken.grant(token) do
|
{:ok, token} <- RefreshToken.grant(token) do
|
||||||
response_attrs = %{created_at: Token.Utils.format_created_at(token)}
|
response_attrs = %{created_at: Token.Utils.format_created_at(token)}
|
||||||
|
|
||||||
json(conn, response_token(user, token, response_attrs))
|
json(conn, Token.Response.build(user, token, response_attrs))
|
||||||
else
|
else
|
||||||
_error ->
|
_error ->
|
||||||
put_status(conn, 400)
|
put_status(conn, 400)
|
||||||
|
@ -160,14 +158,14 @@ def token_exchange(
|
||||||
end
|
end
|
||||||
|
|
||||||
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
|
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
|
||||||
with %App{} = app <- get_app_from_request(conn, params),
|
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||||
fixed_token = Token.Utils.fix_padding(params["code"]),
|
fixed_token = Token.Utils.fix_padding(params["code"]),
|
||||||
{:ok, auth} <- Authorization.get_by_token(app, fixed_token),
|
{:ok, auth} <- Authorization.get_by_token(app, fixed_token),
|
||||||
%User{} = user <- User.get_cached_by_id(auth.user_id),
|
%User{} = user <- User.get_cached_by_id(auth.user_id),
|
||||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||||
response_attrs = %{created_at: Token.Utils.format_created_at(token)}
|
response_attrs = %{created_at: Token.Utils.format_created_at(token)}
|
||||||
|
|
||||||
json(conn, response_token(user, token, response_attrs))
|
json(conn, Token.Response.build(user, token, response_attrs))
|
||||||
else
|
else
|
||||||
_error ->
|
_error ->
|
||||||
put_status(conn, 400)
|
put_status(conn, 400)
|
||||||
|
@ -179,14 +177,14 @@ def token_exchange(
|
||||||
conn,
|
conn,
|
||||||
%{"grant_type" => "password"} = params
|
%{"grant_type" => "password"} = params
|
||||||
) do
|
) do
|
||||||
with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},
|
with {:ok, %User{} = user} <- Authenticator.get_user(conn),
|
||||||
%App{} = app <- get_app_from_request(conn, params),
|
{:ok, app} <- Token.Utils.fetch_app(conn),
|
||||||
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
|
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
|
||||||
{:user_active, true} <- {:user_active, !user.info.deactivated},
|
{:user_active, true} <- {:user_active, !user.info.deactivated},
|
||||||
{:ok, scopes} <- validate_scopes(app, params),
|
{:ok, scopes} <- validate_scopes(app, params),
|
||||||
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
|
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
|
||||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||||
json(conn, response_token(user, token))
|
json(conn, Token.Response.build(user, token))
|
||||||
else
|
else
|
||||||
{:auth_active, false} ->
|
{:auth_active, false} ->
|
||||||
# Per https://github.com/tootsuite/mastodon/blob/
|
# Per https://github.com/tootsuite/mastodon/blob/
|
||||||
|
@ -218,11 +216,23 @@ def token_exchange(
|
||||||
token_exchange(conn, params)
|
token_exchange(conn, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def token_exchange(conn, %{"grant_type" => "client_credentials"} = _params) do
|
||||||
|
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||||
|
{:ok, auth} <- Authorization.create_authorization(app, %User{}),
|
||||||
|
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||||
|
json(conn, Token.Response.build_for_client_credentials(token))
|
||||||
|
else
|
||||||
|
_error ->
|
||||||
|
put_status(conn, 400)
|
||||||
|
|> json(%{error: "Invalid credentials"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Bad request
|
# Bad request
|
||||||
def token_exchange(conn, params), do: bad_request(conn, params)
|
def token_exchange(conn, params), do: bad_request(conn, params)
|
||||||
|
|
||||||
def token_revoke(conn, %{"token" => _token} = params) do
|
def token_revoke(conn, %{"token" => _token} = params) do
|
||||||
with %App{} = app <- get_app_from_request(conn, params),
|
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||||
{:ok, _token} <- RevokeToken.revoke(app, params) do
|
{:ok, _token} <- RevokeToken.revoke(app, params) do
|
||||||
json(conn, %{})
|
json(conn, %{})
|
||||||
else
|
else
|
||||||
|
@ -252,7 +262,7 @@ def prepare_request(conn, %{"provider" => provider, "authorization" => auth_attr
|
||||||
auth_attrs
|
auth_attrs
|
||||||
|> Map.delete("scopes")
|
|> Map.delete("scopes")
|
||||||
|> Map.put("scope", scope)
|
|> Map.put("scope", scope)
|
||||||
|> Poison.encode!()
|
|> Jason.encode!()
|
||||||
|
|
||||||
params =
|
params =
|
||||||
auth_attrs
|
auth_attrs
|
||||||
|
@ -316,7 +326,7 @@ def callback(conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp callback_params(%{"state" => state} = params) do
|
defp callback_params(%{"state" => state} = params) do
|
||||||
Map.merge(params, Poison.decode!(state))
|
Map.merge(params, Jason.decode!(state))
|
||||||
end
|
end
|
||||||
|
|
||||||
def registration_details(conn, %{"authorization" => auth_attrs}) do
|
def registration_details(conn, %{"authorization" => auth_attrs}) do
|
||||||
|
@ -405,33 +415,6 @@ defp do_create_authorization(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_app_from_request(conn, params) do
|
|
||||||
conn
|
|
||||||
|> fetch_client_credentials(params)
|
|
||||||
|> fetch_client
|
|
||||||
end
|
|
||||||
|
|
||||||
defp fetch_client({id, secret}) when is_binary(id) and is_binary(secret) do
|
|
||||||
Repo.get_by(App, client_id: id, client_secret: secret)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp fetch_client({_id, _secret}), do: nil
|
|
||||||
|
|
||||||
defp fetch_client_credentials(conn, params) do
|
|
||||||
# Per RFC 6749, HTTP Basic is preferred to body params
|
|
||||||
with ["Basic " <> encoded] <- get_req_header(conn, "authorization"),
|
|
||||||
{:ok, decoded} <- Base.decode64(encoded),
|
|
||||||
[id, secret] <-
|
|
||||||
Enum.map(
|
|
||||||
String.split(decoded, ":"),
|
|
||||||
fn s -> URI.decode_www_form(s) end
|
|
||||||
) do
|
|
||||||
{id, secret}
|
|
||||||
else
|
|
||||||
_ -> {params["client_id"], params["client_secret"]}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Special case: Local MastodonFE
|
# Special case: Local MastodonFE
|
||||||
defp redirect_uri(conn, "."), do: mastodon_api_url(conn, :login)
|
defp redirect_uri(conn, "."), do: mastodon_api_url(conn, :login)
|
||||||
|
|
||||||
|
@ -442,18 +425,6 @@ defp get_session_registration_id(conn), do: get_session(conn, :registration_id)
|
||||||
defp put_session_registration_id(conn, registration_id),
|
defp put_session_registration_id(conn, registration_id),
|
||||||
do: put_session(conn, :registration_id, registration_id)
|
do: put_session(conn, :registration_id, registration_id)
|
||||||
|
|
||||||
defp response_token(%User{} = user, token, opts \\ %{}) do
|
|
||||||
%{
|
|
||||||
token_type: "Bearer",
|
|
||||||
access_token: token.token,
|
|
||||||
refresh_token: token.refresh_token,
|
|
||||||
expires_in: @expires_in,
|
|
||||||
scope: Enum.join(token.scopes, " "),
|
|
||||||
me: user.ap_id
|
|
||||||
}
|
|
||||||
|> Map.merge(opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec validate_scopes(App.t(), map()) ::
|
@spec validate_scopes(App.t(), map()) ::
|
||||||
{:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
|
{:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
|
||||||
defp validate_scopes(app, params) do
|
defp validate_scopes(app, params) do
|
||||||
|
|
|
@ -45,12 +45,16 @@ def get_by_refresh_token(%App{id: app_id} = _app, token) do
|
||||||
|> Repo.find_resource()
|
|> Repo.find_resource()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec exchange_token(App.t(), Authorization.t()) ::
|
||||||
|
{:ok, Token.t()} | {:error, Changeset.t()}
|
||||||
def exchange_token(app, auth) do
|
def exchange_token(app, auth) do
|
||||||
with {:ok, auth} <- Authorization.use_token(auth),
|
with {:ok, auth} <- Authorization.use_token(auth),
|
||||||
true <- auth.app_id == app.id do
|
true <- auth.app_id == app.id do
|
||||||
|
user = if auth.user_id, do: User.get_cached_by_id(auth.user_id), else: %User{}
|
||||||
|
|
||||||
create_token(
|
create_token(
|
||||||
app,
|
app,
|
||||||
User.get_cached_by_id(auth.user_id),
|
user,
|
||||||
%{scopes: auth.scopes}
|
%{scopes: auth.scopes}
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -81,12 +85,13 @@ defp put_valid_until(changeset, attrs) do
|
||||||
|> validate_required([:valid_until])
|
|> validate_required([:valid_until])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec create_token(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Changeset.t()}
|
||||||
def create_token(%App{} = app, %User{} = user, attrs \\ %{}) do
|
def create_token(%App{} = app, %User{} = user, attrs \\ %{}) do
|
||||||
%__MODULE__{user_id: user.id, app_id: app.id}
|
%__MODULE__{user_id: user.id, app_id: app.id}
|
||||||
|> cast(%{scopes: attrs[:scopes] || app.scopes}, [:scopes])
|
|> cast(%{scopes: attrs[:scopes] || app.scopes}, [:scopes])
|
||||||
|> validate_required([:scopes, :user_id, :app_id])
|
|> validate_required([:scopes, :app_id])
|
||||||
|> put_valid_until(attrs)
|
|> put_valid_until(attrs)
|
||||||
|> put_token
|
|> put_token()
|
||||||
|> put_refresh_token(attrs)
|
|> put_refresh_token(attrs)
|
||||||
|> Repo.insert()
|
|> Repo.insert()
|
||||||
end
|
end
|
||||||
|
|
32
lib/pleroma/web/oauth/token/response.ex
Normal file
32
lib/pleroma/web/oauth/token/response.ex
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
defmodule Pleroma.Web.OAuth.Token.Response do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.OAuth.Token.Utils
|
||||||
|
|
||||||
|
@expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def build(%User{} = user, token, opts \\ %{}) do
|
||||||
|
%{
|
||||||
|
token_type: "Bearer",
|
||||||
|
access_token: token.token,
|
||||||
|
refresh_token: token.refresh_token,
|
||||||
|
expires_in: @expires_in,
|
||||||
|
scope: Enum.join(token.scopes, " "),
|
||||||
|
me: user.ap_id
|
||||||
|
}
|
||||||
|
|> Map.merge(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_for_client_credentials(token) do
|
||||||
|
%{
|
||||||
|
token_type: "Bearer",
|
||||||
|
access_token: token.token,
|
||||||
|
refresh_token: token.refresh_token,
|
||||||
|
created_at: Utils.format_created_at(token),
|
||||||
|
expires_in: @expires_in,
|
||||||
|
scope: Enum.join(token.scopes, " ")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,6 +3,44 @@ defmodule Pleroma.Web.OAuth.Token.Utils do
|
||||||
Auxiliary functions for dealing with tokens.
|
Auxiliary functions for dealing with tokens.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.Web.OAuth.App
|
||||||
|
|
||||||
|
@doc "Fetch app by client credentials from request"
|
||||||
|
@spec fetch_app(Plug.Conn.t()) :: {:ok, App.t()} | {:error, :not_found}
|
||||||
|
def fetch_app(conn) do
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> fetch_client_credentials()
|
||||||
|
|> fetch_client
|
||||||
|
|
||||||
|
case res do
|
||||||
|
%App{} = app -> {:ok, app}
|
||||||
|
_ -> {:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fetch_client({id, secret}) when is_binary(id) and is_binary(secret) do
|
||||||
|
Repo.get_by(App, client_id: id, client_secret: secret)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fetch_client({_id, _secret}), do: nil
|
||||||
|
|
||||||
|
defp fetch_client_credentials(conn) do
|
||||||
|
# Per RFC 6749, HTTP Basic is preferred to body params
|
||||||
|
with ["Basic " <> encoded] <- Plug.Conn.get_req_header(conn, "authorization"),
|
||||||
|
{:ok, decoded} <- Base.decode64(encoded),
|
||||||
|
[id, secret] <-
|
||||||
|
Enum.map(
|
||||||
|
String.split(decoded, ":"),
|
||||||
|
fn s -> URI.decode_www_form(s) end
|
||||||
|
) do
|
||||||
|
{id, secret}
|
||||||
|
else
|
||||||
|
_ -> {conn.params["client_id"], conn.params["client_secret"]}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc "convert token inserted_at to unix timestamp"
|
@doc "convert token inserted_at to unix timestamp"
|
||||||
def format_created_at(%{inserted_at: inserted_at} = _token) do
|
def format_created_at(%{inserted_at: inserted_at} = _token) do
|
||||||
inserted_at
|
inserted_at
|
||||||
|
|
|
@ -16,6 +16,7 @@ defmodule Pleroma.Web.OStatus do
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Web.OStatus.DeleteHandler
|
alias Pleroma.Web.OStatus.DeleteHandler
|
||||||
alias Pleroma.Web.OStatus.FollowHandler
|
alias Pleroma.Web.OStatus.FollowHandler
|
||||||
alias Pleroma.Web.OStatus.NoteHandler
|
alias Pleroma.Web.OStatus.NoteHandler
|
||||||
|
@ -30,7 +31,7 @@ def is_representable?(%Activity{} = activity) do
|
||||||
is_nil(object) ->
|
is_nil(object) ->
|
||||||
false
|
false
|
||||||
|
|
||||||
object.data["type"] == "Note" ->
|
Visibility.is_public?(activity) && object.data["type"] == "Note" ->
|
||||||
true
|
true
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
|
|
|
@ -34,4 +34,6 @@ def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) d
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_data_for_activity(_), do: %{}
|
def fetch_data_for_activity(_), do: %{}
|
||||||
|
|
||||||
|
def perform(:fetch, %Activity{} = activity), do: fetch_data_for_activity(activity)
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
||||||
|
@ -215,6 +225,7 @@ defmodule Pleroma.Web.Router do
|
||||||
post("/change_password", UtilController, :change_password)
|
post("/change_password", UtilController, :change_password)
|
||||||
post("/delete_account", UtilController, :delete_account)
|
post("/delete_account", UtilController, :delete_account)
|
||||||
put("/notification_settings", UtilController, :update_notificaton_settings)
|
put("/notification_settings", UtilController, :update_notificaton_settings)
|
||||||
|
post("/disable_account", UtilController, :disable_account)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope [] do
|
scope [] do
|
||||||
|
@ -385,6 +396,8 @@ defmodule Pleroma.Web.Router do
|
||||||
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
||||||
pipe_through(:api)
|
pipe_through(:api)
|
||||||
|
|
||||||
|
post("/accounts", MastodonAPIController, :account_register)
|
||||||
|
|
||||||
get("/instance", MastodonAPIController, :masto_instance)
|
get("/instance", MastodonAPIController, :masto_instance)
|
||||||
get("/instance/peers", MastodonAPIController, :peers)
|
get("/instance/peers", MastodonAPIController, :peers)
|
||||||
post("/apps", MastodonAPIController, :create_app)
|
post("/apps", MastodonAPIController, :create_app)
|
||||||
|
@ -401,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)
|
||||||
|
@ -422,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
|
||||||
|
|
||||||
|
@ -452,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)
|
||||||
|
@ -470,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)
|
||||||
|
|
||||||
|
@ -484,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
|
||||||
|
|
||||||
|
@ -668,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
|
||||||
|
|
|
@ -3,12 +3,18 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.Salmon do
|
defmodule Pleroma.Web.Salmon do
|
||||||
|
@behaviour Pleroma.Web.Federator.Publisher
|
||||||
|
|
||||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||||
|
|
||||||
use Bitwise
|
use Bitwise
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Instances
|
alias Pleroma.Instances
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
alias Pleroma.Web.Federator.Publisher
|
||||||
|
alias Pleroma.Web.OStatus
|
||||||
alias Pleroma.Web.OStatus.ActivityRepresenter
|
alias Pleroma.Web.OStatus.ActivityRepresenter
|
||||||
alias Pleroma.Web.XML
|
alias Pleroma.Web.XML
|
||||||
|
|
||||||
|
@ -165,12 +171,12 @@ def remote_users(%{data: %{"to" => to} = data}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Pushes an activity to remote account."
|
@doc "Pushes an activity to remote account."
|
||||||
def send_to_user(%{recipient: %{info: %{salmon: salmon}}} = params),
|
def publish_one(%{recipient: %{info: %{salmon: salmon}}} = params),
|
||||||
do: send_to_user(Map.put(params, :recipient, salmon))
|
do: publish_one(Map.put(params, :recipient, salmon))
|
||||||
|
|
||||||
def send_to_user(%{recipient: url, feed: feed, poster: poster} = params) when is_binary(url) do
|
def publish_one(%{recipient: url, feed: feed} = params) when is_binary(url) do
|
||||||
with {:ok, %{status: code}} when code in 200..299 <-
|
with {:ok, %{status: code}} when code in 200..299 <-
|
||||||
poster.(
|
@httpoison.post(
|
||||||
url,
|
url,
|
||||||
feed,
|
feed,
|
||||||
[{"Content-Type", "application/magic-envelope+xml"}]
|
[{"Content-Type", "application/magic-envelope+xml"}]
|
||||||
|
@ -184,11 +190,11 @@ def send_to_user(%{recipient: url, feed: feed, poster: poster} = params) when is
|
||||||
e ->
|
e ->
|
||||||
unless params[:unreachable_since], do: Instances.set_reachable(url)
|
unless params[:unreachable_since], do: Instances.set_reachable(url)
|
||||||
Logger.debug(fn -> "Pushing Salmon to #{url} failed, #{inspect(e)}" end)
|
Logger.debug(fn -> "Pushing Salmon to #{url} failed, #{inspect(e)}" end)
|
||||||
:error
|
{:error, "Unreachable instance"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_to_user(_), do: :noop
|
def publish_one(_), do: :noop
|
||||||
|
|
||||||
@supported_activities [
|
@supported_activities [
|
||||||
"Create",
|
"Create",
|
||||||
|
@ -199,13 +205,19 @@ def send_to_user(_), do: :noop
|
||||||
"Delete"
|
"Delete"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def is_representable?(%Activity{data: %{"type" => type}} = activity)
|
||||||
|
when type in @supported_activities,
|
||||||
|
do: Visibility.is_public?(activity)
|
||||||
|
|
||||||
|
def is_representable?(_), do: false
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Publishes an activity to remote accounts
|
Publishes an activity to remote accounts
|
||||||
"""
|
"""
|
||||||
@spec publish(User.t(), Pleroma.Activity.t(), Pleroma.HTTP.t()) :: none
|
@spec publish(User.t(), Pleroma.Activity.t()) :: none
|
||||||
def publish(user, activity, poster \\ &@httpoison.post/3)
|
def publish(user, activity)
|
||||||
|
|
||||||
def publish(%{info: %{keys: keys}} = user, %{data: %{"type" => type}} = activity, poster)
|
def publish(%{info: %{keys: keys}} = user, %{data: %{"type" => type}} = activity)
|
||||||
when type in @supported_activities do
|
when type in @supported_activities do
|
||||||
feed = ActivityRepresenter.to_simple_form(activity, user, true)
|
feed = ActivityRepresenter.to_simple_form(activity, user, true)
|
||||||
|
|
||||||
|
@ -229,15 +241,29 @@ def publish(%{info: %{keys: keys}} = user, %{data: %{"type" => type}} = activity
|
||||||
|> Enum.each(fn remote_user ->
|
|> Enum.each(fn remote_user ->
|
||||||
Logger.debug(fn -> "Sending Salmon to #{remote_user.ap_id}" end)
|
Logger.debug(fn -> "Sending Salmon to #{remote_user.ap_id}" end)
|
||||||
|
|
||||||
Pleroma.Web.Federator.publish_single_salmon(%{
|
Publisher.enqueue_one(__MODULE__, %{
|
||||||
recipient: remote_user,
|
recipient: remote_user,
|
||||||
feed: feed,
|
feed: feed,
|
||||||
poster: poster,
|
|
||||||
unreachable_since: reachable_urls_metadata[remote_user.info.salmon]
|
unreachable_since: reachable_urls_metadata[remote_user.info.salmon]
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def publish(%{id: id}, _, _), do: Logger.debug(fn -> "Keys missing for user #{id}" end)
|
def publish(%{id: id}, _), do: Logger.debug(fn -> "Keys missing for user #{id}" end)
|
||||||
|
|
||||||
|
def gather_webfinger_links(%User{} = user) do
|
||||||
|
{:ok, _private, public} = keys_from_pem(user.info.keys)
|
||||||
|
magic_key = encode_key(public)
|
||||||
|
|
||||||
|
[
|
||||||
|
%{"rel" => "salmon", "href" => OStatus.salmon_path(user)},
|
||||||
|
%{
|
||||||
|
"rel" => "magic-public-key",
|
||||||
|
"href" => "data:application/magic-public-key,#{magic_key}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def gather_nodeinfo_protocol_names, do: []
|
||||||
end
|
end
|
||||||
|
|
|
@ -173,8 +173,6 @@ def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_
|
||||||
|
|
||||||
def config(conn, _params) do
|
def config(conn, _params) do
|
||||||
instance = Pleroma.Config.get(:instance)
|
instance = Pleroma.Config.get(:instance)
|
||||||
instance_fe = Pleroma.Config.get(:fe)
|
|
||||||
instance_chat = Pleroma.Config.get(:chat)
|
|
||||||
|
|
||||||
case get_format(conn) do
|
case get_format(conn) do
|
||||||
"xml" ->
|
"xml" ->
|
||||||
|
@ -219,31 +217,7 @@ def config(conn, _params) do
|
||||||
if(Pleroma.Config.get([:instance, :safe_dm_mentions]), do: "1", else: "0")
|
if(Pleroma.Config.get([:instance, :safe_dm_mentions]), do: "1", else: "0")
|
||||||
}
|
}
|
||||||
|
|
||||||
pleroma_fe =
|
pleroma_fe = Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
|
||||||
if instance_fe do
|
|
||||||
%{
|
|
||||||
theme: Keyword.get(instance_fe, :theme),
|
|
||||||
background: Keyword.get(instance_fe, :background),
|
|
||||||
logo: Keyword.get(instance_fe, :logo),
|
|
||||||
logoMask: Keyword.get(instance_fe, :logo_mask),
|
|
||||||
logoMargin: Keyword.get(instance_fe, :logo_margin),
|
|
||||||
redirectRootNoLogin: Keyword.get(instance_fe, :redirect_root_no_login),
|
|
||||||
redirectRootLogin: Keyword.get(instance_fe, :redirect_root_login),
|
|
||||||
chatDisabled: !Keyword.get(instance_chat, :enabled),
|
|
||||||
showInstanceSpecificPanel: Keyword.get(instance_fe, :show_instance_panel),
|
|
||||||
scopeOptionsEnabled: Keyword.get(instance_fe, :scope_options_enabled),
|
|
||||||
formattingOptionsEnabled: Keyword.get(instance_fe, :formatting_options_enabled),
|
|
||||||
collapseMessageWithSubject:
|
|
||||||
Keyword.get(instance_fe, :collapse_message_with_subject),
|
|
||||||
hidePostStats: Keyword.get(instance_fe, :hide_post_stats),
|
|
||||||
hideUserStats: Keyword.get(instance_fe, :hide_user_stats),
|
|
||||||
scopeCopy: Keyword.get(instance_fe, :scope_copy),
|
|
||||||
subjectLineBehavior: Keyword.get(instance_fe, :subject_line_behavior),
|
|
||||||
alwaysShowSubjectInput: Keyword.get(instance_fe, :always_show_subject_input)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
|
|
||||||
end
|
|
||||||
|
|
||||||
managed_config = Keyword.get(instance, :managed_config)
|
managed_config = Keyword.get(instance, :managed_config)
|
||||||
|
|
||||||
|
@ -309,8 +283,13 @@ def follow_import(%{assigns: %{user: follower}} = conn, %{"list" => list}) do
|
||||||
Enum.map(lines, fn line ->
|
Enum.map(lines, fn line ->
|
||||||
String.split(line, ",") |> List.first()
|
String.split(line, ",") |> List.first()
|
||||||
end)
|
end)
|
||||||
|> List.delete("Account address"),
|
|> List.delete("Account address") do
|
||||||
{:ok, _} = Task.start(fn -> User.follow_import(follower, followed_identifiers) end) do
|
PleromaJobQueue.enqueue(:background, User, [
|
||||||
|
:follow_import,
|
||||||
|
follower,
|
||||||
|
followed_identifiers
|
||||||
|
])
|
||||||
|
|
||||||
json(conn, "job started")
|
json(conn, "job started")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -320,8 +299,13 @@ def blocks_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do
|
def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do
|
||||||
with blocked_identifiers <- String.split(list),
|
with blocked_identifiers <- String.split(list) do
|
||||||
{:ok, _} = Task.start(fn -> User.blocks_import(blocker, blocked_identifiers) end) do
|
PleromaJobQueue.enqueue(:background, User, [
|
||||||
|
:blocks_import,
|
||||||
|
blocker,
|
||||||
|
blocked_identifiers
|
||||||
|
])
|
||||||
|
|
||||||
json(conn, "job started")
|
json(conn, "job started")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -360,6 +344,17 @@ def delete_account(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def disable_account(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
|
||||||
|
{:ok, user} ->
|
||||||
|
User.deactivate_async(user)
|
||||||
|
json(conn, %{status: "success"})
|
||||||
|
|
||||||
|
{:error, msg} ->
|
||||||
|
json(conn, %{error: msg})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def captcha(conn, _params) do
|
def captcha(conn, _params) do
|
||||||
json(conn, Pleroma.Captcha.new())
|
json(conn, Pleroma.Captcha.new())
|
||||||
end
|
end
|
||||||
|
|
|
@ -128,7 +128,7 @@ def upload(%Plug.Upload{} = file, %User{} = user, format \\ "xml") do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_user(params) do
|
def register_user(params, opts \\ []) do
|
||||||
token = params["token"]
|
token = params["token"]
|
||||||
|
|
||||||
params = %{
|
params = %{
|
||||||
|
@ -162,13 +162,22 @@ def register_user(params) do
|
||||||
# I have no idea how this error handling works
|
# I have no idea how this error handling works
|
||||||
{:error, %{error: Jason.encode!(%{captcha: [error]})}}
|
{:error, %{error: Jason.encode!(%{captcha: [error]})}}
|
||||||
else
|
else
|
||||||
registrations_open = Pleroma.Config.get([:instance, :registrations_open])
|
registration_process(
|
||||||
registration_process(registrations_open, params, token)
|
params,
|
||||||
|
%{
|
||||||
|
registrations_open: Pleroma.Config.get([:instance, :registrations_open]),
|
||||||
|
token: token
|
||||||
|
},
|
||||||
|
opts
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp registration_process(registration_open, params, token)
|
defp registration_process(params, %{registrations_open: true}, opts) do
|
||||||
when registration_open == false or is_nil(registration_open) do
|
create_user(params, opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp registration_process(params, %{token: token}, opts) do
|
||||||
invite =
|
invite =
|
||||||
unless is_nil(token) do
|
unless is_nil(token) do
|
||||||
Repo.get_by(UserInviteToken, %{token: token})
|
Repo.get_by(UserInviteToken, %{token: token})
|
||||||
|
@ -182,19 +191,15 @@ defp registration_process(registration_open, params, token)
|
||||||
|
|
||||||
invite when valid_invite? ->
|
invite when valid_invite? ->
|
||||||
UserInviteToken.update_usage!(invite)
|
UserInviteToken.update_usage!(invite)
|
||||||
create_user(params)
|
create_user(params, opts)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
{:error, "Expired token"}
|
{:error, "Expired token"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp registration_process(true, params, _token) do
|
defp create_user(params, opts) do
|
||||||
create_user(params)
|
changeset = User.register_changeset(%User{}, params, opts)
|
||||||
end
|
|
||||||
|
|
||||||
defp create_user(params) do
|
|
||||||
changeset = User.register_changeset(%User{}, params)
|
|
||||||
|
|
||||||
case User.register(changeset) do
|
case User.register(changeset) do
|
||||||
{:ok, user} ->
|
{:ok, user} ->
|
||||||
|
@ -231,12 +236,15 @@ def password_reset(nickname_or_email) do
|
||||||
def get_user(user \\ nil, params) do
|
def get_user(user \\ nil, params) do
|
||||||
case params do
|
case params do
|
||||||
%{"user_id" => user_id} ->
|
%{"user_id" => user_id} ->
|
||||||
case target = User.get_cached_by_nickname_or_id(user_id) do
|
case User.get_cached_by_nickname_or_id(user_id) do
|
||||||
nil ->
|
nil ->
|
||||||
{:error, "No user with such user_id"}
|
{:error, "No user with such user_id"}
|
||||||
|
|
||||||
_ ->
|
%User{info: %{deactivated: true}} ->
|
||||||
{:ok, target}
|
{:error, "User has been disabled"}
|
||||||
|
|
||||||
|
user ->
|
||||||
|
{:ok, user}
|
||||||
end
|
end
|
||||||
|
|
||||||
%{"screen_name" => nickname} ->
|
%{"screen_name" => nickname} ->
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -440,7 +438,7 @@ def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
|
||||||
true <- user.local,
|
true <- user.local,
|
||||||
true <- user.info.confirmation_pending,
|
true <- user.info.confirmation_pending,
|
||||||
true <- user.info.confirmation_token == token,
|
true <- user.info.confirmation_token == token,
|
||||||
info_change <- User.Info.confirmation_changeset(user.info, :confirmed),
|
info_change <- User.Info.confirmation_changeset(user.info, need_confirmation: false),
|
||||||
changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_change),
|
changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_change),
|
||||||
{:ok, _} <- User.update_and_set_cache(changeset) do
|
{:ok, _} <- User.update_and_set_cache(changeset) do
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Web.WebFinger do
|
||||||
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
alias Pleroma.Web.OStatus
|
alias Pleroma.Web.Federator.Publisher
|
||||||
alias Pleroma.Web.Salmon
|
alias Pleroma.Web.Salmon
|
||||||
alias Pleroma.Web.XML
|
alias Pleroma.Web.XML
|
||||||
alias Pleroma.XmlBuilder
|
alias Pleroma.XmlBuilder
|
||||||
|
@ -50,70 +50,40 @@ def webfinger(resource, fmt) when fmt in ["XML", "JSON"] do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp gather_links(%User{} = user) do
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
"rel" => "http://webfinger.net/rel/profile-page",
|
||||||
|
"type" => "text/html",
|
||||||
|
"href" => user.ap_id
|
||||||
|
}
|
||||||
|
] ++ Publisher.gather_webfinger_links(user)
|
||||||
|
end
|
||||||
|
|
||||||
def represent_user(user, "JSON") do
|
def represent_user(user, "JSON") do
|
||||||
{:ok, user} = ensure_keys_present(user)
|
{:ok, user} = ensure_keys_present(user)
|
||||||
{:ok, _private, public} = Salmon.keys_from_pem(user.info.keys)
|
|
||||||
magic_key = Salmon.encode_key(public)
|
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"subject" => "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}",
|
"subject" => "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}",
|
||||||
"aliases" => [user.ap_id],
|
"aliases" => [user.ap_id],
|
||||||
"links" => [
|
"links" => gather_links(user)
|
||||||
%{
|
|
||||||
"rel" => "http://schemas.google.com/g/2010#updates-from",
|
|
||||||
"type" => "application/atom+xml",
|
|
||||||
"href" => OStatus.feed_path(user)
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
"rel" => "http://webfinger.net/rel/profile-page",
|
|
||||||
"type" => "text/html",
|
|
||||||
"href" => user.ap_id
|
|
||||||
},
|
|
||||||
%{"rel" => "salmon", "href" => OStatus.salmon_path(user)},
|
|
||||||
%{
|
|
||||||
"rel" => "magic-public-key",
|
|
||||||
"href" => "data:application/magic-public-key,#{magic_key}"
|
|
||||||
},
|
|
||||||
%{"rel" => "self", "type" => "application/activity+json", "href" => user.ap_id},
|
|
||||||
%{
|
|
||||||
"rel" => "self",
|
|
||||||
"type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
|
|
||||||
"href" => user.ap_id
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
"rel" => "http://ostatus.org/schema/1.0/subscribe",
|
|
||||||
"template" => OStatus.remote_follow_path()
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def represent_user(user, "XML") do
|
def represent_user(user, "XML") do
|
||||||
{:ok, user} = ensure_keys_present(user)
|
{:ok, user} = ensure_keys_present(user)
|
||||||
{:ok, _private, public} = Salmon.keys_from_pem(user.info.keys)
|
|
||||||
magic_key = Salmon.encode_key(public)
|
links =
|
||||||
|
gather_links(user)
|
||||||
|
|> Enum.map(fn link -> {:Link, link} end)
|
||||||
|
|
||||||
{
|
{
|
||||||
:XRD,
|
:XRD,
|
||||||
%{xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0"},
|
%{xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0"},
|
||||||
[
|
[
|
||||||
{:Subject, "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}"},
|
{:Subject, "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}"},
|
||||||
{:Alias, user.ap_id},
|
{:Alias, user.ap_id}
|
||||||
{:Link,
|
] ++ links
|
||||||
%{
|
|
||||||
rel: "http://schemas.google.com/g/2010#updates-from",
|
|
||||||
type: "application/atom+xml",
|
|
||||||
href: OStatus.feed_path(user)
|
|
||||||
}},
|
|
||||||
{:Link,
|
|
||||||
%{rel: "http://webfinger.net/rel/profile-page", type: "text/html", href: user.ap_id}},
|
|
||||||
{:Link, %{rel: "salmon", href: OStatus.salmon_path(user)}},
|
|
||||||
{:Link,
|
|
||||||
%{rel: "magic-public-key", href: "data:application/magic-public-key,#{magic_key}"}},
|
|
||||||
{:Link, %{rel: "self", type: "application/activity+json", href: user.ap_id}},
|
|
||||||
{:Link,
|
|
||||||
%{rel: "http://ostatus.org/schema/1.0/subscribe", template: OStatus.remote_follow_path()}}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|> XmlBuilder.to_doc()
|
|> XmlBuilder.to_doc()
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,10 +4,14 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.Websub do
|
defmodule Pleroma.Web.Websub do
|
||||||
alias Ecto.Changeset
|
alias Ecto.Changeset
|
||||||
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Instances
|
alias Pleroma.Instances
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Web.Endpoint
|
alias Pleroma.Web.Endpoint
|
||||||
alias Pleroma.Web.Federator
|
alias Pleroma.Web.Federator
|
||||||
|
alias Pleroma.Web.Federator.Publisher
|
||||||
alias Pleroma.Web.OStatus
|
alias Pleroma.Web.OStatus
|
||||||
alias Pleroma.Web.OStatus.FeedRepresenter
|
alias Pleroma.Web.OStatus.FeedRepresenter
|
||||||
alias Pleroma.Web.Router.Helpers
|
alias Pleroma.Web.Router.Helpers
|
||||||
|
@ -18,6 +22,8 @@ defmodule Pleroma.Web.Websub do
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
|
@behaviour Pleroma.Web.Federator.Publisher
|
||||||
|
|
||||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||||
|
|
||||||
def verify(subscription, getter \\ &@httpoison.get/3) do
|
def verify(subscription, getter \\ &@httpoison.get/3) do
|
||||||
|
@ -56,6 +62,13 @@ def verify(subscription, getter \\ &@httpoison.get/3) do
|
||||||
"Undo",
|
"Undo",
|
||||||
"Delete"
|
"Delete"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def is_representable?(%Activity{data: %{"type" => type}} = activity)
|
||||||
|
when type in @supported_activities,
|
||||||
|
do: Visibility.is_public?(activity)
|
||||||
|
|
||||||
|
def is_representable?(_), do: false
|
||||||
|
|
||||||
def publish(topic, user, %{data: %{"type" => type}} = activity)
|
def publish(topic, user, %{data: %{"type" => type}} = activity)
|
||||||
when type in @supported_activities do
|
when type in @supported_activities do
|
||||||
response =
|
response =
|
||||||
|
@ -88,12 +101,14 @@ def publish(topic, user, %{data: %{"type" => type}} = activity)
|
||||||
unreachable_since: reachable_callbacks_metadata[sub.callback]
|
unreachable_since: reachable_callbacks_metadata[sub.callback]
|
||||||
}
|
}
|
||||||
|
|
||||||
Federator.publish_single_websub(data)
|
Publisher.enqueue_one(__MODULE__, data)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def publish(_, _, _), do: ""
|
def publish(_, _, _), do: ""
|
||||||
|
|
||||||
|
def publish(actor, activity), do: publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
|
||||||
|
|
||||||
def sign(secret, doc) do
|
def sign(secret, doc) do
|
||||||
:crypto.hmac(:sha, secret, to_string(doc)) |> Base.encode16() |> String.downcase()
|
:crypto.hmac(:sha, secret, to_string(doc)) |> Base.encode16() |> String.downcase()
|
||||||
end
|
end
|
||||||
|
@ -299,4 +314,20 @@ def publish_one(%{xml: xml, topic: topic, callback: callback, secret: secret} =
|
||||||
{:error, response}
|
{:error, response}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def gather_webfinger_links(%User{} = user) do
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
"rel" => "http://schemas.google.com/g/2010#updates-from",
|
||||||
|
"type" => "application/atom+xml",
|
||||||
|
"href" => OStatus.feed_path(user)
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"rel" => "http://ostatus.org/schema/1.0/subscribe",
|
||||||
|
"template" => OStatus.remote_follow_path()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def gather_nodeinfo_protocol_names, do: ["ostatus"]
|
||||||
end
|
end
|
||||||
|
|
|
@ -35,6 +35,7 @@ def to_doc(content), do: ~s(<?xml version="1.0" encoding="UTF-8"?>) <> to_xml(co
|
||||||
defp make_open_tag(tag, attributes) do
|
defp make_open_tag(tag, attributes) do
|
||||||
attributes_string =
|
attributes_string =
|
||||||
for {attribute, value} <- attributes do
|
for {attribute, value} <- attributes do
|
||||||
|
value = String.replace(value, "\"", """)
|
||||||
"#{attribute}=\"#{value}\""
|
"#{attribute}=\"#{value}\""
|
||||||
end
|
end
|
||||||
|> Enum.join(" ")
|
|> Enum.join(" ")
|
||||||
|
|
8
mix.exs
8
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",
|
||||||
|
@ -103,6 +104,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"},
|
||||||
|
@ -114,7 +118,9 @@ defp deps do
|
||||||
{:quack, "~> 0.1.1"},
|
{:quack, "~> 0.1.1"},
|
||||||
{:benchee, "~> 1.0"},
|
{:benchee, "~> 1.0"},
|
||||||
{:esshd, "~> 0.1.0"},
|
{:esshd, "~> 0.1.0"},
|
||||||
{:plug_static_index_html, "~> 1.0.0"}
|
{:ex_rated, "~> 1.2"},
|
||||||
|
{:plug_static_index_html, "~> 1.0.0"},
|
||||||
|
{:excoveralls, "~> 0.11.1", only: :test}
|
||||||
] ++ oauth_deps
|
] ++ oauth_deps
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
4
mix.lock
4
mix.lock
|
@ -24,17 +24,21 @@
|
||||||
"ecto_sql": {:hex, :ecto_sql, "3.0.5", "7e44172b4f7aca4469f38d7f6a3da394dbf43a1bcf0ca975e958cb957becd74e", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0.6", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
|
"ecto_sql": {:hex, :ecto_sql, "3.0.5", "7e44172b4f7aca4469f38d7f6a3da394dbf43a1bcf0ca975e958cb957becd74e", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0.6", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"},
|
"esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"},
|
||||||
"eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"},
|
"eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"},
|
||||||
|
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
|
||||||
"ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"},
|
"ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"ex_aws_s3": {:hex, :ex_aws_s3, "2.0.1", "9e09366e77f25d3d88c5393824e613344631be8db0d1839faca49686e99b6704", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"},
|
"ex_aws_s3": {:hex, :ex_aws_s3, "2.0.1", "9e09366e77f25d3d88c5393824e613344631be8db0d1839faca49686e99b6704", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
|
"ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "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_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_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,7 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.AddIndexOnUserInfoDeactivated do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create(index(:users, ["(info->'deactivated')"], name: :users_deactivated_index, using: :gin))
|
||||||
|
end
|
||||||
|
end
|
|
@ -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,9 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.ChangeHideColumnInFilterTable do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:filters) do
|
||||||
|
modify :hide, :boolean, default: false
|
||||||
|
end
|
||||||
|
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
|
BIN
priv/static/images/pleroma-fox-tan-shy.png
Normal file
BIN
priv/static/images/pleroma-fox-tan-shy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1,014 KiB |
|
@ -28,6 +28,15 @@ test "get/1 with a list of keys" do
|
||||||
assert Pleroma.Config.get([:azerty, :uiop], true) == true
|
assert Pleroma.Config.get([:azerty, :uiop], true) == true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "get/1 when value is false" do
|
||||||
|
Pleroma.Config.put([:instance, :false_test], false)
|
||||||
|
Pleroma.Config.put([:instance, :nested], [])
|
||||||
|
Pleroma.Config.put([:instance, :nested, :false_test], false)
|
||||||
|
|
||||||
|
assert Pleroma.Config.get([:instance, :false_test]) == false
|
||||||
|
assert Pleroma.Config.get([:instance, :nested, :false_test]) == false
|
||||||
|
end
|
||||||
|
|
||||||
test "get!/1" do
|
test "get!/1" do
|
||||||
assert Pleroma.Config.get!(:instance) == Application.get_env(:pleroma, :instance)
|
assert Pleroma.Config.get!(:instance) == Application.get_env(:pleroma, :instance)
|
||||||
|
|
||||||
|
@ -43,6 +52,15 @@ test "get!/1" do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "get!/1 when value is false" do
|
||||||
|
Pleroma.Config.put([:instance, :false_test], false)
|
||||||
|
Pleroma.Config.put([:instance, :nested], [])
|
||||||
|
Pleroma.Config.put([:instance, :nested, :false_test], false)
|
||||||
|
|
||||||
|
assert Pleroma.Config.get!([:instance, :false_test]) == false
|
||||||
|
assert Pleroma.Config.get!([:instance, :nested, :false_test]) == false
|
||||||
|
end
|
||||||
|
|
||||||
test "put/2 with a key" do
|
test "put/2 with a key" do
|
||||||
Pleroma.Config.put(:config_test, true)
|
Pleroma.Config.put(:config_test, true)
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
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
|
|
@ -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
|
||||||
|
|
50
test/plugs/rate_limit_plug_test.exs
Normal file
50
test/plugs/rate_limit_plug_test.exs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
defmodule Pleroma.Plugs.RateLimitPlugTest do
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
use Plug.Test
|
||||||
|
|
||||||
|
alias Pleroma.Plugs.RateLimitPlug
|
||||||
|
|
||||||
|
@opts RateLimitPlug.init(%{max_requests: 5, interval: 1})
|
||||||
|
|
||||||
|
setup do
|
||||||
|
enabled = Pleroma.Config.get([:app_account_creation, :enabled])
|
||||||
|
|
||||||
|
Pleroma.Config.put([:app_account_creation, :enabled], true)
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
Pleroma.Config.put([:app_account_creation, :enabled], enabled)
|
||||||
|
end)
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it restricts by opts" do
|
||||||
|
conn = conn(:get, "/")
|
||||||
|
bucket_name = conn.remote_ip |> Tuple.to_list() |> Enum.join(".")
|
||||||
|
ms = 1000
|
||||||
|
|
||||||
|
conn = RateLimitPlug.call(conn, @opts)
|
||||||
|
{1, 4, _, _, _} = ExRated.inspect_bucket(bucket_name, ms, 5)
|
||||||
|
conn = RateLimitPlug.call(conn, @opts)
|
||||||
|
{2, 3, _, _, _} = ExRated.inspect_bucket(bucket_name, ms, 5)
|
||||||
|
conn = RateLimitPlug.call(conn, @opts)
|
||||||
|
{3, 2, _, _, _} = ExRated.inspect_bucket(bucket_name, ms, 5)
|
||||||
|
conn = RateLimitPlug.call(conn, @opts)
|
||||||
|
{4, 1, _, _, _} = ExRated.inspect_bucket(bucket_name, ms, 5)
|
||||||
|
conn = RateLimitPlug.call(conn, @opts)
|
||||||
|
{5, 0, to_reset, _, _} = ExRated.inspect_bucket(bucket_name, ms, 5)
|
||||||
|
conn = RateLimitPlug.call(conn, @opts)
|
||||||
|
assert conn.status == 403
|
||||||
|
assert conn.halted
|
||||||
|
assert conn.resp_body == "{\"error\":\"Rate limit exceeded.\"}"
|
||||||
|
|
||||||
|
Process.sleep(to_reset)
|
||||||
|
|
||||||
|
conn = conn(:get, "/")
|
||||||
|
conn = RateLimitPlug.call(conn, @opts)
|
||||||
|
{1, 4, _, _, _} = ExRated.inspect_bucket(bucket_name, ms, 5)
|
||||||
|
refute conn.status == 403
|
||||||
|
refute conn.halted
|
||||||
|
refute conn.resp_body
|
||||||
|
end
|
||||||
|
end
|
|
@ -43,7 +43,7 @@ def user_factory do
|
||||||
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 +113,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(),
|
||||||
|
|
|
@ -338,4 +338,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
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.UserTest do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
@ -213,8 +214,8 @@ test "test if a user is following another user" do
|
||||||
test "fetches correct profile for nickname beginning with number" do
|
test "fetches correct profile for nickname beginning with number" do
|
||||||
# Use old-style integer ID to try to reproduce the problem
|
# Use old-style integer ID to try to reproduce the problem
|
||||||
user = insert(:user, %{id: 1080})
|
user = insert(:user, %{id: 1080})
|
||||||
userwithnumbers = insert(:user, %{nickname: "#{user.id}garbage"})
|
user_with_numbers = insert(:user, %{nickname: "#{user.id}garbage"})
|
||||||
assert userwithnumbers == User.get_cached_by_nickname_or_id(userwithnumbers.nickname)
|
assert user_with_numbers == User.get_cached_by_nickname_or_id(user_with_numbers.nickname)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "user registration" do
|
describe "user registration" do
|
||||||
|
@ -349,7 +350,7 @@ test "it creates unconfirmed user" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it creates confirmed user if :confirmed option is given" do
|
test "it creates confirmed user if :confirmed option is given" do
|
||||||
changeset = User.register_changeset(%User{}, @full_user_data, confirmed: true)
|
changeset = User.register_changeset(%User{}, @full_user_data, need_confirmation: false)
|
||||||
assert changeset.valid?
|
assert changeset.valid?
|
||||||
|
|
||||||
{:ok, user} = Repo.insert(changeset)
|
{:ok, user} = Repo.insert(changeset)
|
||||||
|
@ -816,13 +817,71 @@ test "get recipients from activity" do
|
||||||
assert addressed in recipients
|
assert addressed in recipients
|
||||||
end
|
end
|
||||||
|
|
||||||
test ".deactivate can de-activate then re-activate a user" do
|
describe ".deactivate" do
|
||||||
user = insert(:user)
|
test "can de-activate then re-activate a user" do
|
||||||
assert false == user.info.deactivated
|
user = insert(:user)
|
||||||
{:ok, user} = User.deactivate(user)
|
assert false == user.info.deactivated
|
||||||
assert true == user.info.deactivated
|
{:ok, user} = User.deactivate(user)
|
||||||
{:ok, user} = User.deactivate(user, false)
|
assert true == user.info.deactivated
|
||||||
assert false == user.info.deactivated
|
{:ok, user} = User.deactivate(user, false)
|
||||||
|
assert false == user.info.deactivated
|
||||||
|
end
|
||||||
|
|
||||||
|
test "hide a user from followers " do
|
||||||
|
user = insert(:user)
|
||||||
|
user2 = insert(:user)
|
||||||
|
|
||||||
|
{:ok, user} = User.follow(user, user2)
|
||||||
|
{:ok, _user} = User.deactivate(user)
|
||||||
|
|
||||||
|
info = User.get_cached_user_info(user2)
|
||||||
|
|
||||||
|
assert info.follower_count == 0
|
||||||
|
assert {:ok, []} = User.get_followers(user2)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "hide a user from friends" do
|
||||||
|
user = insert(:user)
|
||||||
|
user2 = insert(:user)
|
||||||
|
|
||||||
|
{:ok, user2} = User.follow(user2, user)
|
||||||
|
assert User.following_count(user2) == 1
|
||||||
|
|
||||||
|
{:ok, _user} = User.deactivate(user)
|
||||||
|
|
||||||
|
info = User.get_cached_user_info(user2)
|
||||||
|
|
||||||
|
assert info.following_count == 0
|
||||||
|
assert User.following_count(user2) == 0
|
||||||
|
assert {:ok, []} = User.get_friends(user2)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "hide a user's statuses from timelines and notifications" do
|
||||||
|
user = insert(:user)
|
||||||
|
user2 = insert(:user)
|
||||||
|
|
||||||
|
{:ok, user2} = User.follow(user2, user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{user2.nickname}"})
|
||||||
|
|
||||||
|
activity = Repo.preload(activity, :bookmark)
|
||||||
|
|
||||||
|
[notification] = Pleroma.Notification.for_user(user2)
|
||||||
|
assert notification.activity.id == activity.id
|
||||||
|
|
||||||
|
assert [activity] == ActivityPub.fetch_public_activities(%{}) |> Repo.preload(:bookmark)
|
||||||
|
|
||||||
|
assert [activity] ==
|
||||||
|
ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
|
||||||
|
|
||||||
|
{:ok, _user} = User.deactivate(user)
|
||||||
|
|
||||||
|
assert [] == ActivityPub.fetch_public_activities(%{})
|
||||||
|
assert [] == Pleroma.Notification.for_user(user2)
|
||||||
|
|
||||||
|
assert [] ==
|
||||||
|
ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test ".delete_user_activities deletes all create activities" do
|
test ".delete_user_activities deletes all create activities" do
|
||||||
|
@ -1143,4 +1202,22 @@ test "follower count is updated when a follower is blocked" do
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.ActivityPub.Publisher
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
@ -461,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)
|
||||||
|
@ -959,18 +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] ==
|
assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
|
||||||
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
|
||||||
|
@ -1057,7 +1084,7 @@ test "it can create a Flag activity" do
|
||||||
actor = insert(:user)
|
actor = insert(:user)
|
||||||
inbox = "http://200.site/users/nick1/inbox"
|
inbox = "http://200.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert {:ok, _} = ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
||||||
|
|
||||||
assert called(Instances.set_reachable(inbox))
|
assert called(Instances.set_reachable(inbox))
|
||||||
end
|
end
|
||||||
|
@ -1070,7 +1097,7 @@ test "it can create a Flag activity" do
|
||||||
inbox = "http://200.site/users/nick1/inbox"
|
inbox = "http://200.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert {:ok, _} =
|
assert {:ok, _} =
|
||||||
ActivityPub.publish_one(%{
|
Publisher.publish_one(%{
|
||||||
inbox: inbox,
|
inbox: inbox,
|
||||||
json: "{}",
|
json: "{}",
|
||||||
actor: actor,
|
actor: actor,
|
||||||
|
@ -1089,7 +1116,7 @@ test "it can create a Flag activity" do
|
||||||
inbox = "http://200.site/users/nick1/inbox"
|
inbox = "http://200.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert {:ok, _} =
|
assert {:ok, _} =
|
||||||
ActivityPub.publish_one(%{
|
Publisher.publish_one(%{
|
||||||
inbox: inbox,
|
inbox: inbox,
|
||||||
json: "{}",
|
json: "{}",
|
||||||
actor: actor,
|
actor: actor,
|
||||||
|
@ -1107,8 +1134,7 @@ test "it can create a Flag activity" do
|
||||||
actor = insert(:user)
|
actor = insert(:user)
|
||||||
inbox = "http://404.site/users/nick1/inbox"
|
inbox = "http://404.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert {:error, _} =
|
assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
||||||
ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
|
||||||
|
|
||||||
assert called(Instances.set_unreachable(inbox))
|
assert called(Instances.set_unreachable(inbox))
|
||||||
end
|
end
|
||||||
|
@ -1120,8 +1146,7 @@ test "it can create a Flag activity" do
|
||||||
actor = insert(:user)
|
actor = insert(:user)
|
||||||
inbox = "http://connrefused.site/users/nick1/inbox"
|
inbox = "http://connrefused.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert {:error, _} =
|
assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
||||||
ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
|
||||||
|
|
||||||
assert called(Instances.set_unreachable(inbox))
|
assert called(Instances.set_unreachable(inbox))
|
||||||
end
|
end
|
||||||
|
@ -1133,7 +1158,7 @@ test "it can create a Flag activity" do
|
||||||
actor = insert(:user)
|
actor = insert(:user)
|
||||||
inbox = "http://200.site/users/nick1/inbox"
|
inbox = "http://200.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert {:ok, _} = ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
||||||
|
|
||||||
refute called(Instances.set_unreachable(inbox))
|
refute called(Instances.set_unreachable(inbox))
|
||||||
end
|
end
|
||||||
|
@ -1146,7 +1171,7 @@ test "it can create a Flag activity" do
|
||||||
inbox = "http://connrefused.site/users/nick1/inbox"
|
inbox = "http://connrefused.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert {:error, _} =
|
assert {:error, _} =
|
||||||
ActivityPub.publish_one(%{
|
Publisher.publish_one(%{
|
||||||
inbox: inbox,
|
inbox: inbox,
|
||||||
json: "{}",
|
json: "{}",
|
||||||
actor: actor,
|
actor: actor,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -58,7 +58,7 @@ test "with relays deactivated, it does not publish to the relay", %{
|
||||||
|
|
||||||
describe "Targets reachability filtering in `publish`" do
|
describe "Targets reachability filtering in `publish`" do
|
||||||
test_with_mock "it federates only to reachable instances via AP",
|
test_with_mock "it federates only to reachable instances via AP",
|
||||||
Federator,
|
Pleroma.Web.ActivityPub.Publisher,
|
||||||
[:passthrough],
|
[:passthrough],
|
||||||
[] do
|
[] do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
@ -88,13 +88,18 @@ test "with relays deactivated, it does not publish to the relay", %{
|
||||||
{:ok, _activity} =
|
{:ok, _activity} =
|
||||||
CommonAPI.post(user, %{"status" => "HI @nick1@domain.com, @nick2@domain2.com!"})
|
CommonAPI.post(user, %{"status" => "HI @nick1@domain.com, @nick2@domain2.com!"})
|
||||||
|
|
||||||
assert called(Federator.publish_single_ap(%{inbox: inbox1, unreachable_since: dt}))
|
assert called(
|
||||||
|
Pleroma.Web.ActivityPub.Publisher.publish_one(%{
|
||||||
|
inbox: inbox1,
|
||||||
|
unreachable_since: dt
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
refute called(Federator.publish_single_ap(%{inbox: inbox2}))
|
refute called(Pleroma.Web.ActivityPub.Publisher.publish_one(%{inbox: inbox2}))
|
||||||
end
|
end
|
||||||
|
|
||||||
test_with_mock "it federates only to reachable instances via Websub",
|
test_with_mock "it federates only to reachable instances via Websub",
|
||||||
Federator,
|
Pleroma.Web.Websub,
|
||||||
[:passthrough],
|
[:passthrough],
|
||||||
[] do
|
[] do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
@ -122,17 +127,17 @@ test "with relays deactivated, it does not publish to the relay", %{
|
||||||
{:ok, _activity} = CommonAPI.post(user, %{"status" => "HI"})
|
{:ok, _activity} = CommonAPI.post(user, %{"status" => "HI"})
|
||||||
|
|
||||||
assert called(
|
assert called(
|
||||||
Federator.publish_single_websub(%{
|
Pleroma.Web.Websub.publish_one(%{
|
||||||
callback: sub2.callback,
|
callback: sub2.callback,
|
||||||
unreachable_since: dt
|
unreachable_since: dt
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
refute called(Federator.publish_single_websub(%{callback: sub1.callback}))
|
refute called(Pleroma.Web.Websub.publish_one(%{callback: sub1.callback}))
|
||||||
end
|
end
|
||||||
|
|
||||||
test_with_mock "it federates only to reachable instances via Salmon",
|
test_with_mock "it federates only to reachable instances via Salmon",
|
||||||
Federator,
|
Pleroma.Web.Salmon,
|
||||||
[:passthrough],
|
[:passthrough],
|
||||||
[] do
|
[] do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
@ -162,13 +167,13 @@ test "with relays deactivated, it does not publish to the relay", %{
|
||||||
CommonAPI.post(user, %{"status" => "HI @nick1@domain.com, @nick2@domain2.com!"})
|
CommonAPI.post(user, %{"status" => "HI @nick1@domain.com, @nick2@domain2.com!"})
|
||||||
|
|
||||||
assert called(
|
assert called(
|
||||||
Federator.publish_single_salmon(%{
|
Pleroma.Web.Salmon.publish_one(%{
|
||||||
recipient: remote_user2,
|
recipient: remote_user2,
|
||||||
unreachable_since: dt
|
unreachable_since: dt
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
refute called(Federator.publish_single_websub(%{recipient: remote_user1}))
|
refute called(Pleroma.Web.Salmon.publish_one(%{recipient: remote_user1}))
|
||||||
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-----
|
|
|
@ -16,6 +16,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.MastodonAPI.FilterView
|
alias Pleroma.Web.MastodonAPI.FilterView
|
||||||
alias Pleroma.Web.OAuth.App
|
alias Pleroma.Web.OAuth.App
|
||||||
|
alias Pleroma.Web.OAuth.Token
|
||||||
alias Pleroma.Web.OStatus
|
alias Pleroma.Web.OStatus
|
||||||
alias Pleroma.Web.Push
|
alias Pleroma.Web.Push
|
||||||
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
||||||
|
@ -80,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)
|
||||||
|
|
||||||
|
@ -572,6 +586,7 @@ test "creating a filter", %{conn: conn} do
|
||||||
assert response = json_response(conn, 200)
|
assert response = json_response(conn, 200)
|
||||||
assert response["phrase"] == filter.phrase
|
assert response["phrase"] == filter.phrase
|
||||||
assert response["context"] == filter.context
|
assert response["context"] == filter.context
|
||||||
|
assert response["irreversible"] == false
|
||||||
assert response["id"] != nil
|
assert response["id"] != nil
|
||||||
assert response["id"] != ""
|
assert response["id"] != ""
|
||||||
end
|
end
|
||||||
|
@ -2114,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", %{
|
||||||
|
@ -2138,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", %{
|
||||||
|
@ -3215,4 +3230,129 @@ test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{c
|
||||||
replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
|
replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
|
||||||
assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
|
assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "create account by app" do
|
||||||
|
setup do
|
||||||
|
enabled = Pleroma.Config.get([:app_account_creation, :enabled])
|
||||||
|
max_requests = Pleroma.Config.get([:app_account_creation, :max_requests])
|
||||||
|
interval = Pleroma.Config.get([:app_account_creation, :interval])
|
||||||
|
|
||||||
|
Pleroma.Config.put([:app_account_creation, :enabled], true)
|
||||||
|
Pleroma.Config.put([:app_account_creation, :max_requests], 5)
|
||||||
|
Pleroma.Config.put([:app_account_creation, :interval], 1)
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
Pleroma.Config.put([:app_account_creation, :enabled], enabled)
|
||||||
|
Pleroma.Config.put([:app_account_creation, :max_requests], max_requests)
|
||||||
|
Pleroma.Config.put([:app_account_creation, :interval], interval)
|
||||||
|
end)
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Account registration via Application", %{conn: conn} do
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> post("/api/v1/apps", %{
|
||||||
|
client_name: "client_name",
|
||||||
|
redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
|
||||||
|
scopes: "read, write, follow"
|
||||||
|
})
|
||||||
|
|
||||||
|
%{
|
||||||
|
"client_id" => client_id,
|
||||||
|
"client_secret" => client_secret,
|
||||||
|
"id" => _,
|
||||||
|
"name" => "client_name",
|
||||||
|
"redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
|
||||||
|
"vapid_key" => _,
|
||||||
|
"website" => nil
|
||||||
|
} = json_response(conn, 200)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> post("/oauth/token", %{
|
||||||
|
grant_type: "client_credentials",
|
||||||
|
client_id: client_id,
|
||||||
|
client_secret: client_secret
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
|
||||||
|
json_response(conn, 200)
|
||||||
|
|
||||||
|
assert token
|
||||||
|
token_from_db = Repo.get_by(Token, token: token)
|
||||||
|
assert token_from_db
|
||||||
|
assert refresh
|
||||||
|
assert scope == "read write follow"
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> put_req_header("authorization", "Bearer " <> token)
|
||||||
|
|> post("/api/v1/accounts", %{
|
||||||
|
username: "lain",
|
||||||
|
email: "lain@example.org",
|
||||||
|
password: "PlzDontHackLain",
|
||||||
|
agreement: true
|
||||||
|
})
|
||||||
|
|
||||||
|
%{
|
||||||
|
"access_token" => token,
|
||||||
|
"created_at" => _created_at,
|
||||||
|
"scope" => _scope,
|
||||||
|
"token_type" => "Bearer"
|
||||||
|
} = json_response(conn, 200)
|
||||||
|
|
||||||
|
token_from_db = Repo.get_by(Token, token: token)
|
||||||
|
assert token_from_db
|
||||||
|
token_from_db = Repo.preload(token_from_db, :user)
|
||||||
|
assert token_from_db.user
|
||||||
|
|
||||||
|
assert token_from_db.user.info.confirmation_pending
|
||||||
|
end
|
||||||
|
|
||||||
|
test "rate limit", %{conn: conn} do
|
||||||
|
app_token = insert(:oauth_token, user: nil)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
put_req_header(conn, "authorization", "Bearer " <> app_token.token)
|
||||||
|
|> Map.put(:remote_ip, {15, 15, 15, 15})
|
||||||
|
|
||||||
|
for i <- 1..5 do
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> post("/api/v1/accounts", %{
|
||||||
|
username: "#{i}lain",
|
||||||
|
email: "#{i}lain@example.org",
|
||||||
|
password: "PlzDontHackLain",
|
||||||
|
agreement: true
|
||||||
|
})
|
||||||
|
|
||||||
|
%{
|
||||||
|
"access_token" => token,
|
||||||
|
"created_at" => _created_at,
|
||||||
|
"scope" => _scope,
|
||||||
|
"token_type" => "Bearer"
|
||||||
|
} = json_response(conn, 200)
|
||||||
|
|
||||||
|
token_from_db = Repo.get_by(Token, token: token)
|
||||||
|
assert token_from_db
|
||||||
|
token_from_db = Repo.preload(token_from_db, :user)
|
||||||
|
assert token_from_db.user
|
||||||
|
|
||||||
|
assert token_from_db.user.info.confirmation_pending
|
||||||
|
end
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> post("/api/v1/accounts", %{
|
||||||
|
username: "6lain",
|
||||||
|
email: "6lain@example.org",
|
||||||
|
password: "PlzDontHackLain",
|
||||||
|
agreement: true
|
||||||
|
})
|
||||||
|
|
||||||
|
assert json_response(conn, 403) == %{"error" => "Rate limit exceeded."}
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -614,6 +614,27 @@ test "issues a token for request with HTTP basic auth client credentials" do
|
||||||
assert token.scopes == ["scope1", "scope2"]
|
assert token.scopes == ["scope1", "scope2"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "issue a token for client_credentials grant type" do
|
||||||
|
app = insert(:oauth_app, scopes: ["read", "write"])
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> post("/oauth/token", %{
|
||||||
|
"grant_type" => "client_credentials",
|
||||||
|
"client_id" => app.client_id,
|
||||||
|
"client_secret" => app.client_secret
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
|
||||||
|
json_response(conn, 200)
|
||||||
|
|
||||||
|
assert token
|
||||||
|
token_from_db = Repo.get_by(Token, token: token)
|
||||||
|
assert token_from_db
|
||||||
|
assert refresh
|
||||||
|
assert scope == "read write"
|
||||||
|
end
|
||||||
|
|
||||||
test "rejects token exchange with invalid client credentials" do
|
test "rejects token exchange with invalid client credentials" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
app = insert(:oauth_app)
|
app = insert(:oauth_app)
|
||||||
|
@ -644,7 +665,7 @@ test "rejects token exchange for valid credentials belonging to unconfirmed user
|
||||||
|
|
||||||
password = "testpassword"
|
password = "testpassword"
|
||||||
user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
|
user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
|
||||||
info_change = Pleroma.User.Info.confirmation_changeset(user.info, :unconfirmed)
|
info_change = Pleroma.User.Info.confirmation_changeset(user.info, need_confirmation: true)
|
||||||
|
|
||||||
{:ok, user} =
|
{:ok, user} =
|
||||||
user
|
user
|
||||||
|
|
53
test/web/oauth/token/utils_test.exs
Normal file
53
test/web/oauth/token/utils_test.exs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.OAuth.Token.UtilsTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.Web.OAuth.Token.Utils
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "fetch_app/1" do
|
||||||
|
test "returns error when credentials is invalid" do
|
||||||
|
assert {:error, :not_found} =
|
||||||
|
Utils.fetch_app(%Plug.Conn{params: %{"client_id" => 1, "client_secret" => "x"}})
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns App by params credentails" do
|
||||||
|
app = insert(:oauth_app)
|
||||||
|
|
||||||
|
assert {:ok, load_app} =
|
||||||
|
Utils.fetch_app(%Plug.Conn{
|
||||||
|
params: %{"client_id" => app.client_id, "client_secret" => app.client_secret}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert load_app == app
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns App by header credentails" do
|
||||||
|
app = insert(:oauth_app)
|
||||||
|
header = "Basic " <> Base.encode64("#{app.client_id}:#{app.client_secret}")
|
||||||
|
|
||||||
|
conn =
|
||||||
|
%Plug.Conn{}
|
||||||
|
|> Plug.Conn.put_req_header("authorization", header)
|
||||||
|
|
||||||
|
assert {:ok, load_app} = Utils.fetch_app(conn)
|
||||||
|
assert load_app == app
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "format_created_at/1" do
|
||||||
|
test "returns formatted created at" do
|
||||||
|
token = insert(:oauth_token)
|
||||||
|
date = Utils.format_created_at(token)
|
||||||
|
|
||||||
|
token_date =
|
||||||
|
token.inserted_at
|
||||||
|
|> DateTime.from_naive!("Etc/UTC")
|
||||||
|
|> DateTime.to_unix()
|
||||||
|
|
||||||
|
assert token_date == date
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -7,7 +7,9 @@ defmodule Pleroma.Web.Salmon.SalmonTest do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.Federator.Publisher
|
||||||
alias Pleroma.Web.Salmon
|
alias Pleroma.Web.Salmon
|
||||||
|
import Mock
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
@magickey "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwQhh-1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB"
|
@magickey "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwQhh-1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB"
|
||||||
|
@ -77,7 +79,10 @@ test "it gets a magic key" do
|
||||||
"RSA.uzg6r1peZU0vXGADWxGJ0PE34WvmhjUmydbX5YYdOiXfODVLwCMi1umGoqUDm-mRu4vNEdFBVJU1CpFA7dKzWgIsqsa501i2XqElmEveXRLvNRWFB6nG03Q5OUY2as8eE54BJm0p20GkMfIJGwP6TSFb-ICp3QjzbatuSPJ6xCE=.AQAB"
|
"RSA.uzg6r1peZU0vXGADWxGJ0PE34WvmhjUmydbX5YYdOiXfODVLwCMi1umGoqUDm-mRu4vNEdFBVJU1CpFA7dKzWgIsqsa501i2XqElmEveXRLvNRWFB6nG03Q5OUY2as8eE54BJm0p20GkMfIJGwP6TSFb-ICp3QjzbatuSPJ6xCE=.AQAB"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it pushes an activity to remote accounts it's addressed to" do
|
test_with_mock "it pushes an activity to remote accounts it's addressed to",
|
||||||
|
Publisher,
|
||||||
|
[:passthrough],
|
||||||
|
[] do
|
||||||
user_data = %{
|
user_data = %{
|
||||||
info: %{
|
info: %{
|
||||||
salmon: "http://test-example.org/salmon"
|
salmon: "http://test-example.org/salmon"
|
||||||
|
@ -102,10 +107,8 @@ test "it pushes an activity to remote accounts it's addressed to" do
|
||||||
user = User.get_cached_by_ap_id(activity.data["actor"])
|
user = User.get_cached_by_ap_id(activity.data["actor"])
|
||||||
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
|
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
|
||||||
|
|
||||||
poster = fn url, _data, _headers ->
|
Salmon.publish(user, activity)
|
||||||
assert url == "http://test-example.org/salmon"
|
|
||||||
end
|
|
||||||
|
|
||||||
Salmon.publish(user, activity, poster)
|
assert called(Publisher.enqueue_one(Salmon, %{recipient: mentioned_user}))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1094,7 +1094,7 @@ test "it returns 500 when user is not local", %{conn: conn, user: user} do
|
||||||
describe "GET /api/account/confirm_email/:id/:token" do
|
describe "GET /api/account/confirm_email/:id/:token" do
|
||||||
setup do
|
setup do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
info_change = User.Info.confirmation_changeset(user.info, :unconfirmed)
|
info_change = User.Info.confirmation_changeset(user.info, need_confirmation: true)
|
||||||
|
|
||||||
{:ok, user} =
|
{:ok, user} =
|
||||||
user
|
user
|
||||||
|
@ -1145,7 +1145,7 @@ test "it returns 500 if token is invalid", %{conn: conn, user: user} do
|
||||||
end
|
end
|
||||||
|
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
info_change = User.Info.confirmation_changeset(user.info, :unconfirmed)
|
info_change = User.Info.confirmation_changeset(user.info, need_confirmation: true)
|
||||||
|
|
||||||
{:ok, user} =
|
{:ok, user} =
|
||||||
user
|
user
|
||||||
|
|
|
@ -141,7 +141,7 @@ test "returns the state of safe_dm_mentions flag", %{conn: conn} do
|
||||||
|
|
||||||
test "it returns the managed config", %{conn: conn} do
|
test "it returns the managed config", %{conn: conn} do
|
||||||
Pleroma.Config.put([:instance, :managed_config], false)
|
Pleroma.Config.put([:instance, :managed_config], false)
|
||||||
Pleroma.Config.put([:fe], theme: "rei-ayanami-towel")
|
Pleroma.Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"})
|
||||||
|
|
||||||
response =
|
response =
|
||||||
conn
|
conn
|
||||||
|
@ -157,29 +157,7 @@ test "it returns the managed config", %{conn: conn} do
|
||||||
|> get("/api/statusnet/config.json")
|
|> get("/api/statusnet/config.json")
|
||||||
|> json_response(:ok)
|
|> json_response(:ok)
|
||||||
|
|
||||||
assert response["site"]["pleromafe"]
|
assert response["site"]["pleromafe"] == %{"theme" => "asuka-hospital"}
|
||||||
end
|
|
||||||
|
|
||||||
test "if :pleroma, :fe is false, it returns the new style config settings", %{conn: conn} do
|
|
||||||
Pleroma.Config.put([:instance, :managed_config], true)
|
|
||||||
Pleroma.Config.put([:fe, :theme], "rei-ayanami-towel")
|
|
||||||
Pleroma.Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"})
|
|
||||||
|
|
||||||
response =
|
|
||||||
conn
|
|
||||||
|> get("/api/statusnet/config.json")
|
|
||||||
|> json_response(:ok)
|
|
||||||
|
|
||||||
assert response["site"]["pleromafe"]["theme"] == "rei-ayanami-towel"
|
|
||||||
|
|
||||||
Pleroma.Config.put([:fe], false)
|
|
||||||
|
|
||||||
response =
|
|
||||||
conn
|
|
||||||
|> get("/api/statusnet/config.json")
|
|
||||||
|> json_response(:ok)
|
|
||||||
|
|
||||||
assert response["site"]["pleromafe"]["theme"] == "asuka-hospital"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -251,4 +229,22 @@ test "GET /api/pleroma/healthcheck", %{conn: conn} do
|
||||||
|
|
||||||
assert conn.status in [200, 503]
|
assert conn.status in [200, 503]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "POST /api/pleroma/disable_account" do
|
||||||
|
test "it returns HTTP 200", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/pleroma/disable_account", %{"password" => "test"})
|
||||||
|
|> json_response(:ok)
|
||||||
|
|
||||||
|
assert response == %{"status" => "success"}
|
||||||
|
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
|
||||||
|
assert user.info.deactivated == true
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.ErrorViewTest do
|
defmodule Pleroma.Web.ErrorViewTest do
|
||||||
use Pleroma.Web.ConnCase, async: true
|
use Pleroma.Web.ConnCase, async: true
|
||||||
|
import ExUnit.CaptureLog
|
||||||
|
|
||||||
# Bring render/3 and render_to_string/3 for testing custom views
|
# Bring render/3 and render_to_string/3 for testing custom views
|
||||||
import Phoenix.View
|
import Phoenix.View
|
||||||
|
@ -13,17 +14,23 @@ test "renders 404.json" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "render 500.json" do
|
test "render 500.json" do
|
||||||
assert render(Pleroma.Web.ErrorView, "500.json", []) ==
|
assert capture_log(fn ->
|
||||||
%{errors: %{detail: "Internal server error", reason: "nil"}}
|
assert render(Pleroma.Web.ErrorView, "500.json", []) ==
|
||||||
|
%{errors: %{detail: "Internal server error", reason: "nil"}}
|
||||||
|
end) =~ "[error] Internal server error: nil"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "render any other" do
|
test "render any other" do
|
||||||
assert render(Pleroma.Web.ErrorView, "505.json", []) ==
|
assert capture_log(fn ->
|
||||||
%{errors: %{detail: "Internal server error", reason: "nil"}}
|
assert render(Pleroma.Web.ErrorView, "505.json", []) ==
|
||||||
|
%{errors: %{detail: "Internal server error", reason: "nil"}}
|
||||||
|
end) =~ "[error] Internal server error: nil"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "render 500.json with reason" do
|
test "render 500.json with reason" do
|
||||||
assert render(Pleroma.Web.ErrorView, "500.json", reason: "test reason") ==
|
assert capture_log(fn ->
|
||||||
%{errors: %{detail: "Internal server error", reason: "\"test reason\""}}
|
assert render(Pleroma.Web.ErrorView, "500.json", reason: "test reason") ==
|
||||||
|
%{errors: %{detail: "Internal server error", reason: "\"test reason\""}}
|
||||||
|
end) =~ "[error] Internal server error: \"test reason\""
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue