mirror of
https://git.pleroma.social/pleroma/pleroma.git
synced 2024-12-23 00:26:30 +00:00
Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into rum-index
This commit is contained in:
commit
412a3d8a0f
173 changed files with 4238 additions and 1179 deletions
|
@ -52,6 +52,7 @@ unit-testing:
|
|||
- mix ecto.create
|
||||
- mix ecto.migrate
|
||||
- mix test --trace --preload-modules
|
||||
- mix coveralls
|
||||
|
||||
lint:
|
||||
stage: test
|
||||
|
|
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -10,24 +10,32 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- A [job queue](https://git.pleroma.social/pleroma/pleroma_job_queue) for federation, emails, web push, etc.
|
||||
- [Prometheus](https://prometheus.io/) metrics
|
||||
- Support for Mastodon's remote interaction
|
||||
- Mix Tasks: `mix pleroma.database bump_all_conversations`
|
||||
- Mix Tasks: `mix pleroma.database remove_embedded_objects`
|
||||
- Mix Tasks: `mix pleroma.database update_users_following_followers_counts`
|
||||
- Mix Tasks: `mix pleroma.user toggle_confirmed`
|
||||
- Federation: Support for reports
|
||||
- Configuration: `safe_dm_mentions` option
|
||||
- Configuration: `link_name` option
|
||||
- Configuration: `fetch_initial_posts` option
|
||||
- Configuration: `notify_email` option
|
||||
- Configuration: Media proxy `whitelist` option
|
||||
- Configuration: `report_uri` option
|
||||
- Pleroma API: User subscriptions
|
||||
- Pleroma API: Healthcheck endpoint
|
||||
- Admin API: Endpoints for listing/revoking invite tokens
|
||||
- Admin API: Endpoints for making users follow/unfollow each other
|
||||
- Admin API: added filters (role, tags, email, name) for users endpoint
|
||||
- Admin API: Endpoints for managing reports
|
||||
- Admin API: Endpoints for deleting and changing the scope of individual reported statuses
|
||||
- AdminFE: initial release with basic user management accessible at /pleroma/admin/
|
||||
- Mastodon API: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/)
|
||||
- Mastodon API: `/api/v1/notifications/destroy_multiple` (glitch-soc extension)
|
||||
- Mastodon API: `/api/v1/pleroma/accounts/:id/favourites` (API extension)
|
||||
- Mastodon API: [Reports](https://docs.joinmastodon.org/api/rest/reports/)
|
||||
- Mastodon API: `POST /api/v1/accounts` (account creation API)
|
||||
- ActivityPub C2S: OAuth endpoints
|
||||
- Metadata RelMe provider
|
||||
- Metadata: RelMe provider
|
||||
- OAuth: added support for refresh tokens
|
||||
- Emoji packs and emoji pack manager
|
||||
|
||||
|
@ -42,8 +50,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Federation: Removed `inReplyToStatusId` from objects
|
||||
- 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.
|
||||
- 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.
|
||||
- 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: Add `languages` and `registrations` to `/api/v1/instance`
|
||||
- Mastodon API: Provide plaintext versions of cw/content in the Status entity
|
||||
|
@ -57,17 +66,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: Actual reblog hiding instead of a dummy
|
||||
- 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 Ecto to 3.0.7
|
||||
- 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
|
||||
|
||||
### Fixed
|
||||
- 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
|
||||
- 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
|
||||
- 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
|
||||
- NodeInfo: Include admins in `staffAccounts`
|
||||
- ActivityPub: Crashing when requesting empty local user's outbox
|
||||
|
@ -91,6 +101,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: Correct `reblogged`, `favourited`, and `bookmarked` values in the reblog status JSON
|
||||
- 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
|
||||
### 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
|
||||
<https://pleroma.social/>, and are distributed under the Creative Commons
|
||||
Attribution-ShareAlike 4.0 International license, you should have received
|
||||
|
|
|
@ -12,7 +12,7 @@ For clients it supports both the [GNU Social API with Qvitter extensions](https:
|
|||
|
||||
- [Client Applications for Pleroma](https://docs-develop.pleroma.social/clients.html)
|
||||
|
||||
No release has been made yet, but several servers have been online for months already. If you want to run your own server, feel free to contact us at @lain@pleroma.soykaf.com or in our dev chat at #pleroma on freenode or via matrix at <https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org>.
|
||||
If you want to run your own server, feel free to contact us at @lain@pleroma.soykaf.com or in our dev chat at #pleroma on freenode or via matrix at <https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org>.
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
|
@ -48,7 +48,8 @@ config :pleroma, ecto_repos: [Pleroma.Repo]
|
|||
|
||||
config :pleroma, Pleroma.Repo,
|
||||
types: Pleroma.PostgresTypes,
|
||||
telemetry_event: [Pleroma.Repo.Instrumenter]
|
||||
telemetry_event: [Pleroma.Repo.Instrumenter],
|
||||
migration_lock: nil
|
||||
|
||||
config :pleroma, Pleroma.Captcha,
|
||||
enabled: false,
|
||||
|
@ -212,6 +213,11 @@ config :pleroma, :instance,
|
|||
registrations_open: true,
|
||||
federating: true,
|
||||
federation_reachability_timeout_days: 7,
|
||||
federation_publisher_modules: [
|
||||
Pleroma.Web.ActivityPub.Publisher,
|
||||
Pleroma.Web.Websub,
|
||||
Pleroma.Web.Salmon
|
||||
],
|
||||
allow_relay: true,
|
||||
rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
|
||||
public: true,
|
||||
|
@ -234,6 +240,8 @@ config :pleroma, :instance,
|
|||
safe_dm_mentions: false,
|
||||
healthcheck: false
|
||||
|
||||
config :pleroma, :app_account_creation, enabled: true, max_requests: 25, interval: 1800
|
||||
|
||||
config :pleroma, :markup,
|
||||
# XXX - unfortunately, inline images must be enabled by default right now, because
|
||||
# of custom emoji. Issue #275 discusses defanging that somehow.
|
||||
|
@ -246,25 +254,6 @@ config :pleroma, :markup,
|
|||
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,
|
||||
pleroma_fe: %{
|
||||
theme: "pleroma-dark",
|
||||
|
@ -478,6 +467,9 @@ config :pleroma, :oauth2,
|
|||
|
||||
config :pleroma, :database, rum_enabled: false
|
||||
|
||||
config :http_signatures,
|
||||
adapter: Pleroma.Signature
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
|
|
@ -59,6 +59,10 @@ config :pleroma, Pleroma.ScheduledActivity,
|
|||
total_user_limit: 3,
|
||||
enabled: false
|
||||
|
||||
config :pleroma, :app_account_creation, max_requests: 5
|
||||
|
||||
config :pleroma, :http_security, report_uri: "https://endpoint.com"
|
||||
|
||||
try do
|
||||
import_config "test.secret.exs"
|
||||
rescue
|
||||
|
|
|
@ -24,7 +24,7 @@ Authentication is required and the user must be an admin.
|
|||
- Example: `https://mypleroma.org/api/pleroma/admin/users?query=john&filters=local,active&page=1&page_size=10&tags[]=some_tag&tags[]=another_tag&name=display_name&email=email@example.com`
|
||||
- Response:
|
||||
|
||||
```JSON
|
||||
```json
|
||||
{
|
||||
"page_size": integer,
|
||||
"count": integer,
|
||||
|
@ -45,7 +45,7 @@ Authentication is required and the user must be an admin.
|
|||
}
|
||||
```
|
||||
|
||||
## `/api/pleroma/admin/user`
|
||||
## `/api/pleroma/admin/users`
|
||||
|
||||
### Remove a user
|
||||
|
||||
|
@ -63,7 +63,7 @@ Authentication is required and the user must be an admin.
|
|||
- `password`
|
||||
- Response: User’s nickname
|
||||
|
||||
## `/api/pleroma/admin/user/follow`
|
||||
## `/api/pleroma/admin/users/follow`
|
||||
### Make a user follow another user
|
||||
|
||||
- Methods: `POST`
|
||||
|
@ -73,7 +73,7 @@ Authentication is required and the user must be an admin.
|
|||
- Response:
|
||||
- "ok"
|
||||
|
||||
## `/api/pleroma/admin/user/unfollow`
|
||||
## `/api/pleroma/admin/users/unfollow`
|
||||
### Make a user unfollow another user
|
||||
|
||||
- Methods: `POST`
|
||||
|
@ -92,7 +92,7 @@ Authentication is required and the user must be an admin.
|
|||
- `nickname`
|
||||
- Response: User’s object
|
||||
|
||||
```JSON
|
||||
```json
|
||||
{
|
||||
"deactivated": bool,
|
||||
"id": integer,
|
||||
|
@ -106,17 +106,17 @@ Authentication is required and the user must be an admin.
|
|||
|
||||
- Method: `PUT`
|
||||
- Params:
|
||||
- `nickname`
|
||||
- `tags`
|
||||
- `nicknames` (array)
|
||||
- `tags` (array)
|
||||
|
||||
### Untag a list of users
|
||||
|
||||
- Method: `DELETE`
|
||||
- Params:
|
||||
- `nickname`
|
||||
- `tags`
|
||||
- `nicknames` (array)
|
||||
- `tags` (array)
|
||||
|
||||
## `/api/pleroma/admin/permission_group/:nickname`
|
||||
## `/api/pleroma/admin/users/:nickname/permission_group`
|
||||
|
||||
### Get user user permission groups membership
|
||||
|
||||
|
@ -124,14 +124,14 @@ Authentication is required and the user must be an admin.
|
|||
- Params: none
|
||||
- Response:
|
||||
|
||||
```JSON
|
||||
```json
|
||||
{
|
||||
"is_moderator": bool,
|
||||
"is_admin": bool
|
||||
}
|
||||
```
|
||||
|
||||
## `/api/pleroma/admin/permission_group/:nickname/:permission_group`
|
||||
## `/api/pleroma/admin/users/:nickname/permission_group/:permission_group`
|
||||
|
||||
Note: Available `:permission_group` is currently moderator and admin. 404 is returned when the permission group doesn’t exist.
|
||||
|
||||
|
@ -141,7 +141,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
- Params: none
|
||||
- Response:
|
||||
|
||||
```JSON
|
||||
```json
|
||||
{
|
||||
"is_moderator": bool,
|
||||
"is_admin": bool
|
||||
|
@ -165,7 +165,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
- On success: JSON of the `user.info`
|
||||
- Note: An admin cannot revoke their own admin status.
|
||||
|
||||
## `/api/pleroma/admin/activation_status/:nickname`
|
||||
## `/api/pleroma/admin/users/:nickname/activation_status`
|
||||
|
||||
### Active or deactivate a user
|
||||
|
||||
|
@ -203,7 +203,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
- Response:
|
||||
- On success: URL of the unfollowed relay
|
||||
|
||||
## `/api/pleroma/admin/invite_token`
|
||||
## `/api/pleroma/admin/users/invite_token`
|
||||
|
||||
### Get an account registration invite token
|
||||
|
||||
|
@ -215,7 +215,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
]
|
||||
- Response: invite token (base64 string)
|
||||
|
||||
## `/api/pleroma/admin/invites`
|
||||
## `/api/pleroma/admin/users/invites`
|
||||
|
||||
### Get a list of generated invites
|
||||
|
||||
|
@ -223,7 +223,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
- Params: none
|
||||
- Response:
|
||||
|
||||
```JSON
|
||||
```json
|
||||
{
|
||||
|
||||
"invites": [
|
||||
|
@ -241,7 +241,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
}
|
||||
```
|
||||
|
||||
## `/api/pleroma/admin/revoke_invite`
|
||||
## `/api/pleroma/admin/users/revoke_invite`
|
||||
|
||||
### Revoke invite by token
|
||||
|
||||
|
@ -250,7 +250,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
- `token`
|
||||
- Response:
|
||||
|
||||
```JSON
|
||||
```json
|
||||
{
|
||||
"id": integer,
|
||||
"token": string,
|
||||
|
@ -264,7 +264,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
```
|
||||
|
||||
|
||||
## `/api/pleroma/admin/email_invite`
|
||||
## `/api/pleroma/admin/users/email_invite`
|
||||
|
||||
### Sends registration invite via email
|
||||
|
||||
|
@ -273,10 +273,287 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
- `email`
|
||||
- `name`, optional
|
||||
|
||||
## `/api/pleroma/admin/password_reset`
|
||||
## `/api/pleroma/admin/users/:nickname/password_reset`
|
||||
|
||||
### Get a password reset token for a given nickname
|
||||
|
||||
- Methods: `GET`
|
||||
- Params: none
|
||||
- Response: password reset token (base64 string)
|
||||
|
||||
## `/api/pleroma/admin/reports`
|
||||
### Get a list of reports
|
||||
- Method `GET`
|
||||
- Params:
|
||||
- `state`: optional, the state of reports. Valid values are `open`, `closed` and `resolved`
|
||||
- `limit`: optional, the number of records to retrieve
|
||||
- `since_id`: optional, returns results that are more recent than the specified id
|
||||
- `max_id`: optional, returns results that are older than the specified id
|
||||
- Response:
|
||||
- On failure: 403 Forbidden error `{"error": "error_msg"}` when requested by anonymous or non-admin
|
||||
- On success: JSON, returns a list of reports, where:
|
||||
- `account`: the user who has been reported
|
||||
- `actor`: the user who has sent the report
|
||||
- `statuses`: list of statuses that have been included to the report
|
||||
|
||||
```json
|
||||
{
|
||||
"reports": [
|
||||
{
|
||||
"account": {
|
||||
"acct": "user",
|
||||
"avatar": "https://pleroma.example.org/images/avi.png",
|
||||
"avatar_static": "https://pleroma.example.org/images/avi.png",
|
||||
"bot": false,
|
||||
"created_at": "2019-04-23T17:32:04.000Z",
|
||||
"display_name": "User",
|
||||
"emojis": [],
|
||||
"fields": [],
|
||||
"followers_count": 1,
|
||||
"following_count": 1,
|
||||
"header": "https://pleroma.example.org/images/banner.png",
|
||||
"header_static": "https://pleroma.example.org/images/banner.png",
|
||||
"id": "9i6dAJqSGSKMzLG2Lo",
|
||||
"locked": false,
|
||||
"note": "",
|
||||
"pleroma": {
|
||||
"confirmation_pending": false,
|
||||
"hide_favorites": true,
|
||||
"hide_followers": false,
|
||||
"hide_follows": false,
|
||||
"is_admin": false,
|
||||
"is_moderator": false,
|
||||
"relationship": {},
|
||||
"tags": []
|
||||
},
|
||||
"source": {
|
||||
"note": "",
|
||||
"pleroma": {},
|
||||
"sensitive": false
|
||||
},
|
||||
"statuses_count": 3,
|
||||
"url": "https://pleroma.example.org/users/user",
|
||||
"username": "user"
|
||||
},
|
||||
"actor": {
|
||||
"acct": "lain",
|
||||
"avatar": "https://pleroma.example.org/images/avi.png",
|
||||
"avatar_static": "https://pleroma.example.org/images/avi.png",
|
||||
"bot": false,
|
||||
"created_at": "2019-03-28T17:36:03.000Z",
|
||||
"display_name": "Roger Braun",
|
||||
"emojis": [],
|
||||
"fields": [],
|
||||
"followers_count": 1,
|
||||
"following_count": 1,
|
||||
"header": "https://pleroma.example.org/images/banner.png",
|
||||
"header_static": "https://pleroma.example.org/images/banner.png",
|
||||
"id": "9hEkA5JsvAdlSrocam",
|
||||
"locked": false,
|
||||
"note": "",
|
||||
"pleroma": {
|
||||
"confirmation_pending": false,
|
||||
"hide_favorites": false,
|
||||
"hide_followers": false,
|
||||
"hide_follows": false,
|
||||
"is_admin": false,
|
||||
"is_moderator": false,
|
||||
"relationship": {},
|
||||
"tags": []
|
||||
},
|
||||
"source": {
|
||||
"note": "",
|
||||
"pleroma": {},
|
||||
"sensitive": false
|
||||
},
|
||||
"statuses_count": 1,
|
||||
"url": "https://pleroma.example.org/users/lain",
|
||||
"username": "lain"
|
||||
},
|
||||
"content": "Please delete it",
|
||||
"created_at": "2019-04-29T19:48:15.000Z",
|
||||
"id": "9iJGOv1j8hxuw19bcm",
|
||||
"state": "open",
|
||||
"statuses": [
|
||||
{
|
||||
"account": { ... },
|
||||
"application": {
|
||||
"name": "Web",
|
||||
"website": null
|
||||
},
|
||||
"bookmarked": false,
|
||||
"card": null,
|
||||
"content": "<span class=\"h-card\"><a data-user=\"9hEkA5JsvAdlSrocam\" class=\"u-url mention\" href=\"https://pleroma.example.org/users/lain\">@<span>lain</span></a></span> click on my link <a href=\"https://www.google.com/\">https://www.google.com/</a>",
|
||||
"created_at": "2019-04-23T19:15:47.000Z",
|
||||
"emojis": [],
|
||||
"favourited": false,
|
||||
"favourites_count": 0,
|
||||
"id": "9i6mQ9uVrrOmOime8m",
|
||||
"in_reply_to_account_id": null,
|
||||
"in_reply_to_id": null,
|
||||
"language": null,
|
||||
"media_attachments": [],
|
||||
"mentions": [
|
||||
{
|
||||
"acct": "lain",
|
||||
"id": "9hEkA5JsvAdlSrocam",
|
||||
"url": "https://pleroma.example.org/users/lain",
|
||||
"username": "lain"
|
||||
},
|
||||
{
|
||||
"acct": "user",
|
||||
"id": "9i6dAJqSGSKMzLG2Lo",
|
||||
"url": "https://pleroma.example.org/users/user",
|
||||
"username": "user"
|
||||
}
|
||||
],
|
||||
"muted": false,
|
||||
"pinned": false,
|
||||
"pleroma": {
|
||||
"content": {
|
||||
"text/plain": "@lain click on my link https://www.google.com/"
|
||||
},
|
||||
"conversation_id": 28,
|
||||
"in_reply_to_account_acct": null,
|
||||
"local": true,
|
||||
"spoiler_text": {
|
||||
"text/plain": ""
|
||||
}
|
||||
},
|
||||
"reblog": null,
|
||||
"reblogged": false,
|
||||
"reblogs_count": 0,
|
||||
"replies_count": 0,
|
||||
"sensitive": false,
|
||||
"spoiler_text": "",
|
||||
"tags": [],
|
||||
"uri": "https://pleroma.example.org/objects/8717b90f-8e09-4b58-97b0-e3305472b396",
|
||||
"url": "https://pleroma.example.org/notice/9i6mQ9uVrrOmOime8m",
|
||||
"visibility": "direct"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## `/api/pleroma/admin/reports/:id`
|
||||
### Get an individual report
|
||||
- Method `GET`
|
||||
- Params:
|
||||
- `id`
|
||||
- Response:
|
||||
- On failure:
|
||||
- 403 Forbidden `{"error": "error_msg"}`
|
||||
- 404 Not Found `"Not found"`
|
||||
- On success: JSON, Report object (see above)
|
||||
|
||||
## `/api/pleroma/admin/reports/:id`
|
||||
### Change the state of the report
|
||||
- Method `PUT`
|
||||
- Params:
|
||||
- `id`
|
||||
- `state`: required, the new state. Valid values are `open`, `closed` and `resolved`
|
||||
- Response:
|
||||
- On failure:
|
||||
- 400 Bad Request `"Unsupported state"`
|
||||
- 403 Forbidden `{"error": "error_msg"}`
|
||||
- 404 Not Found `"Not found"`
|
||||
- On success: JSON, Report object (see above)
|
||||
|
||||
## `/api/pleroma/admin/reports/:id/respond`
|
||||
### Respond to a report
|
||||
- Method `POST`
|
||||
- Params:
|
||||
- `id`
|
||||
- `status`: required, the message
|
||||
- Response:
|
||||
- On failure:
|
||||
- 400 Bad Request `"Invalid parameters"` when `status` is missing
|
||||
- 403 Forbidden `{"error": "error_msg"}`
|
||||
- 404 Not Found `"Not found"`
|
||||
- On success: JSON, created Mastodon Status entity
|
||||
|
||||
```json
|
||||
{
|
||||
"account": { ... },
|
||||
"application": {
|
||||
"name": "Web",
|
||||
"website": null
|
||||
},
|
||||
"bookmarked": false,
|
||||
"card": null,
|
||||
"content": "Your claim is going to be closed",
|
||||
"created_at": "2019-05-11T17:13:03.000Z",
|
||||
"emojis": [],
|
||||
"favourited": false,
|
||||
"favourites_count": 0,
|
||||
"id": "9ihuiSL1405I65TmEq",
|
||||
"in_reply_to_account_id": null,
|
||||
"in_reply_to_id": null,
|
||||
"language": null,
|
||||
"media_attachments": [],
|
||||
"mentions": [
|
||||
{
|
||||
"acct": "user",
|
||||
"id": "9i6dAJqSGSKMzLG2Lo",
|
||||
"url": "https://pleroma.example.org/users/user",
|
||||
"username": "user"
|
||||
},
|
||||
{
|
||||
"acct": "admin",
|
||||
"id": "9hEkA5JsvAdlSrocam",
|
||||
"url": "https://pleroma.example.org/users/admin",
|
||||
"username": "admin"
|
||||
}
|
||||
],
|
||||
"muted": false,
|
||||
"pinned": false,
|
||||
"pleroma": {
|
||||
"content": {
|
||||
"text/plain": "Your claim is going to be closed"
|
||||
},
|
||||
"conversation_id": 35,
|
||||
"in_reply_to_account_acct": null,
|
||||
"local": true,
|
||||
"spoiler_text": {
|
||||
"text/plain": ""
|
||||
}
|
||||
},
|
||||
"reblog": null,
|
||||
"reblogged": false,
|
||||
"reblogs_count": 0,
|
||||
"replies_count": 0,
|
||||
"sensitive": false,
|
||||
"spoiler_text": "",
|
||||
"tags": [],
|
||||
"uri": "https://pleroma.example.org/objects/cab0836d-9814-46cd-a0ea-529da9db5fcb",
|
||||
"url": "https://pleroma.example.org/notice/9ihuiSL1405I65TmEq",
|
||||
"visibility": "direct"
|
||||
}
|
||||
```
|
||||
|
||||
## `/api/pleroma/admin/statuses/:id`
|
||||
### Change the scope of an individual reported status
|
||||
- Method `PUT`
|
||||
- Params:
|
||||
- `id`
|
||||
- `sensitive`: optional, valid values are `true` or `false`
|
||||
- `visibility`: optional, valid values are `public`, `private` and `unlisted`
|
||||
- Response:
|
||||
- On failure:
|
||||
- 400 Bad Request `"Unsupported visibility"`
|
||||
- 403 Forbidden `{"error": "error_msg"}`
|
||||
- 404 Not Found `"Not found"`
|
||||
- On success: JSON, Mastodon Status entity
|
||||
|
||||
## `/api/pleroma/admin/statuses/:id`
|
||||
### Delete an individual reported status
|
||||
- Method `DELETE`
|
||||
- Params:
|
||||
- `id`
|
||||
- Response:
|
||||
- On failure:
|
||||
- 403 Forbidden `{"error": "error_msg"}`
|
||||
- 404 Not Found `"Not found"`
|
||||
- On success: 200 OK `{}`
|
||||
|
|
|
@ -87,3 +87,13 @@ Additional parameters can be added to the JSON body/Form data:
|
|||
|
||||
`POST /oauth/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
|
||||
* 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`
|
||||
### Register a new user
|
||||
* 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`)
|
||||
* `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
|
||||
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog, and `Quack.Logger` to log to Slack
|
||||
|
||||
|
@ -280,7 +286,8 @@ This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls start
|
|||
* ``sts``: Whether to additionally send a `Strict-Transport-Security` header
|
||||
* ``sts_max_age``: The maximum age for the `Strict-Transport-Security` header if sent
|
||||
* ``ct_max_age``: The maximum age for the `Expect-CT` header if sent
|
||||
* ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`.
|
||||
* ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`
|
||||
* ``report_uri``: Adds the specified url to `report-uri` and `report-to` group in CSP header.
|
||||
|
||||
## :mrf_user_allowlist
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ This guide will assume you are on Debian Stretch. This guide should also work wi
|
|||
* `erlang-tools`
|
||||
* `erlang-parsetools`
|
||||
* `erlang-eldap`, if you want to enable ldap authenticator
|
||||
* `erlang-ssh`
|
||||
* `erlang-xmerl`
|
||||
* `git`
|
||||
* `build-essential`
|
||||
|
@ -49,7 +50,7 @@ sudo dpkg -i /tmp/erlang-solutions_1.0_all.deb
|
|||
|
||||
```shell
|
||||
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
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
- erlang-dev
|
||||
- erlang-tools
|
||||
- erlang-parsetools
|
||||
- erlang-ssh
|
||||
- erlang-xmerl (Jessieではバックポートからインストールすること!)
|
||||
- git
|
||||
- build-essential
|
||||
|
@ -44,7 +45,7 @@ wget -P /tmp/ https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb
|
|||
|
||||
* 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 (バックエンド) をインストールします
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
# Introduction to Pleroma
|
||||
## What is Pleroma?
|
||||
Pleroma is a federated social networking platform, compatible with GNU social, Mastodon and other OStatus and ActivityPub implementations. It is free software licensed under the AGPLv3.
|
||||
It actually consists of two components: a backend, named simply Pleroma, and a user-facing frontend, named Pleroma-FE. It also includes the Mastodon frontend, if that's your thing.
|
||||
It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other.
|
||||
Pleroma is a federated social networking platform, compatible with GNU social, Mastodon and other OStatus and ActivityPub implementations. It is free software licensed under the AGPLv3.
|
||||
It actually consists of two components: a backend, named simply Pleroma, and a user-facing frontend, named Pleroma-FE. It also includes the Mastodon frontend, if that's your thing.
|
||||
It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other.
|
||||
One account on a instance is enough to talk to the entire fediverse!
|
||||
|
||||
|
||||
## How can I use it?
|
||||
|
||||
Pleroma instances are already widely deployed, a list can be found here:
|
||||
Pleroma instances are already widely deployed, a list can be found here:
|
||||
http://distsn.org/pleroma-instances.html
|
||||
|
||||
If you don't feel like joining an existing instance, but instead prefer to deploy your own instance, that's easy too!
|
||||
Installation instructions can be found here:
|
||||
If you don't feel like joining an existing instance, but instead prefer to deploy your own instance, that's easy too!
|
||||
Installation instructions can be found here:
|
||||
[main Pleroma wiki](/)
|
||||
|
||||
|
||||
## I got an account, now what?
|
||||
Great! Now you can explore the fediverse!
|
||||
- Open the login page for your Pleroma instance (for ex. https://pleroma.soykaf.com) and login with your username and password.
|
||||
(If you don't have one yet, click on Register) :slightly_smiling_face:
|
||||
Great! Now you can explore the fediverse!
|
||||
- Open the login page for your Pleroma instance (for ex. https://pleroma.soykaf.com) and login with your username and password.
|
||||
(If you don't have one yet, click on Register) :slightly_smiling_face:
|
||||
|
||||
At this point you will have two columns in front of you.
|
||||
|
||||
### Left column
|
||||
- first block: here you can see your avatar, your nickname a bio, and statistics (Statuses, Following, Followers).
|
||||
Under that you have a text form which allows you to post new statuses. The icon on the left is for uploading media files and attach them to your post. The number under the text form is a character counter, every instance can have a different character limit (the default is 5000).
|
||||
If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person. :slight_smile:
|
||||
- first block: here you can see your avatar, your nickname a bio, and statistics (Statuses, Following, Followers).
|
||||
Under that you have a text form which allows you to post new statuses. The icon on the left is for uploading media files and attach them to your post. The number under the text form is a character counter, every instance can have a different character limit (the default is 5000).
|
||||
If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person. :slight_smile:
|
||||
To post your status, simply press Submit.
|
||||
|
||||
- second block: Here you can switch between the different timelines:
|
||||
|
@ -38,7 +38,7 @@ To post your status, simply press Submit.
|
|||
- fourth block: This is the Notifications block, here you will get notified whenever somebody mentions you, follows you, repeats or favorites one of your statuses.
|
||||
|
||||
### Right column
|
||||
This is where the interesting stuff happens! :slight_smile:
|
||||
This is where the interesting stuff happens! :slight_smile:
|
||||
Depending on the timeline you will see different statuses, but each status has a standard structure:
|
||||
- Icon + name + link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the replied-to status).
|
||||
- A + button on the right allows you to Expand/Collapse an entire discussion thread. It also updates in realtime!
|
||||
|
@ -47,9 +47,9 @@ Depending on the timeline you will see different statuses, but each status has a
|
|||
- Four buttons (left to right): Reply, Repeat, Favorite, Delete.
|
||||
|
||||
## Mastodon interface
|
||||
If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too! :smile:
|
||||
Just add a "/web" after your instance url (for ex. https://pleroma.soycaf.com/web) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC! :fireworks:
|
||||
For more information on the Mastodon interface, please look here:
|
||||
If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too! :smile:
|
||||
Just add a "/web" after your instance url (for ex. https://pleroma.soycaf.com/web) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC! :fireworks:
|
||||
For more information on the Mastodon interface, please look here:
|
||||
https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md
|
||||
|
||||
Remember, what you see is only the frontend part of Mastodon, the backend is still Pleroma.
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
defmodule Mix.Tasks.Pleroma.Database do
|
||||
alias Mix.Tasks.Pleroma.Common
|
||||
alias Pleroma.Conversation
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
require Logger
|
||||
use Mix.Task
|
||||
|
||||
|
@ -19,6 +22,14 @@ defmodule Mix.Tasks.Pleroma.Database do
|
|||
|
||||
Options:
|
||||
- `--vacuum` - run `VACUUM FULL` after the embedded objects are replaced with their references
|
||||
|
||||
## Create a conversation for all existing DMs. Can be safely re-run.
|
||||
|
||||
mix pleroma.database bump_all_conversations
|
||||
|
||||
## Remove duplicated items from following and update followers count for all users
|
||||
|
||||
mix pleroma.database update_users_following_followers_counts
|
||||
"""
|
||||
def run(["remove_embedded_objects" | args]) do
|
||||
{options, [], []} =
|
||||
|
@ -32,7 +43,7 @@ defmodule Mix.Tasks.Pleroma.Database do
|
|||
Common.start_pleroma()
|
||||
Logger.info("Removing embedded objects")
|
||||
|
||||
Pleroma.Repo.query!(
|
||||
Repo.query!(
|
||||
"update activities set data = jsonb_set(data, '{object}'::text[], data->'object'->'id') where data->'object'->>'id' is not null;",
|
||||
[],
|
||||
timeout: :infinity
|
||||
|
@ -41,11 +52,24 @@ defmodule Mix.Tasks.Pleroma.Database do
|
|||
if Keyword.get(options, :vacuum) do
|
||||
Logger.info("Runnning VACUUM FULL")
|
||||
|
||||
Pleroma.Repo.query!(
|
||||
Repo.query!(
|
||||
"vacuum full;",
|
||||
[],
|
||||
timeout: :infinity
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def run(["bump_all_conversations"]) do
|
||||
Common.start_pleroma()
|
||||
Conversation.bump_for_all_activities()
|
||||
end
|
||||
|
||||
def run(["update_users_following_followers_counts"]) do
|
||||
Common.start_pleroma()
|
||||
|
||||
users = Repo.all(User)
|
||||
Enum.each(users, &User.remove_duplicated_following/1)
|
||||
Enum.each(users, &User.update_follower_count/1)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -137,7 +137,7 @@ defmodule Mix.Tasks.Pleroma.Emoji 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]))
|
||||
|
||||
|
@ -239,7 +239,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
|
||||
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("""
|
||||
|
||||
|
@ -248,11 +248,11 @@ defmodule Mix.Tasks.Pleroma.Emoji 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!(
|
||||
"index.json",
|
||||
Poison.encode!(
|
||||
Jason.encode!(
|
||||
Map.merge(
|
||||
existing_data,
|
||||
pack_json
|
||||
|
@ -263,14 +263,14 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
|
||||
IO.puts("index.json file has been update with the #{name} pack")
|
||||
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")
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch_manifest(from) do
|
||||
Poison.decode!(
|
||||
Jason.decode!(
|
||||
if String.starts_with?(from, "http") do
|
||||
Tesla.get!(client(), from).body
|
||||
else
|
||||
|
|
|
@ -77,6 +77,10 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
## Delete tags from a user.
|
||||
|
||||
mix pleroma.user untag NICKNAME TAGS
|
||||
|
||||
## Toggle confirmation of the user's account.
|
||||
|
||||
mix pleroma.user toggle_confirmed NICKNAME
|
||||
"""
|
||||
def run(["new", nickname, email | rest]) do
|
||||
{options, [], []} =
|
||||
|
@ -138,7 +142,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
bio: bio
|
||||
}
|
||||
|
||||
changeset = User.register_changeset(%User{}, params, confirmed: true)
|
||||
changeset = User.register_changeset(%User{}, params, need_confirmation: false)
|
||||
{:ok, _user} = User.register(changeset)
|
||||
|
||||
Mix.shell().info("User #{nickname} created")
|
||||
|
@ -388,6 +392,21 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def run(["toggle_confirmed", nickname]) do
|
||||
Common.start_pleroma()
|
||||
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||
{:ok, user} = User.toggle_confirmation(user)
|
||||
|
||||
message = if user.info.confirmation_pending, do: "needs", else: "doesn't need"
|
||||
|
||||
Mix.shell().info("#{nickname} #{message} confirmation.")
|
||||
else
|
||||
_ ->
|
||||
Mix.shell().error("No local user #{nickname}")
|
||||
end
|
||||
end
|
||||
|
||||
defp set_moderator(user, value) do
|
||||
info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value})
|
||||
|
||||
|
|
|
@ -60,21 +60,24 @@ defmodule Pleroma.Activity do
|
|||
timestamps()
|
||||
end
|
||||
|
||||
def with_preloaded_object(query) do
|
||||
query
|
||||
|> join(
|
||||
:inner,
|
||||
[activity],
|
||||
o in Object,
|
||||
def with_joined_object(query) do
|
||||
join(query, :inner, [activity], o in Object,
|
||||
on:
|
||||
fragment(
|
||||
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
|
||||
o.data,
|
||||
activity.data,
|
||||
activity.data
|
||||
)
|
||||
),
|
||||
as: :object
|
||||
)
|
||||
|> preload([activity, object], object: object)
|
||||
end
|
||||
|
||||
def with_preloaded_object(query) do
|
||||
query
|
||||
|> has_named_binding?(:object)
|
||||
|> if(do: query, else: with_joined_object(query))
|
||||
|> preload([activity, object: object], object: object)
|
||||
end
|
||||
|
||||
def with_preloaded_bookmark(query, %User{} = user) do
|
||||
|
@ -108,7 +111,7 @@ defmodule Pleroma.Activity do
|
|||
|
||||
def change(struct, params \\ %{}) do
|
||||
struct
|
||||
|> cast(params, [:data])
|
||||
|> cast(params, [:data, :recipients])
|
||||
|> validate_required([:data])
|
||||
|> unique_constraint(:ap_id, name: :activities_unique_apid_index)
|
||||
end
|
||||
|
@ -132,7 +135,10 @@ defmodule Pleroma.Activity do
|
|||
end
|
||||
|
||||
def get_by_id(id) do
|
||||
Repo.get(Activity, id)
|
||||
Activity
|
||||
|> where([a], a.id == ^id)
|
||||
|> restrict_deactivated_users()
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
def get_by_id_with_object(id) do
|
||||
|
@ -200,6 +206,7 @@ defmodule Pleroma.Activity do
|
|||
|
||||
def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do
|
||||
create_by_object_ap_id(ap_id)
|
||||
|> restrict_deactivated_users()
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
|
@ -314,4 +321,14 @@ defmodule Pleroma.Activity do
|
|||
def query_by_actor(actor) do
|
||||
from(a in Activity, where: a.actor == ^actor)
|
||||
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
|
||||
|
|
|
@ -95,7 +95,6 @@ defmodule Pleroma.BBS.Handler do
|
|||
activities =
|
||||
[user.ap_id | user.following]
|
||||
|> ActivityPub.fetch_activities(params)
|
||||
|> ActivityPub.contain_timeline(user)
|
||||
|
||||
Enum.each(activities, fn activity ->
|
||||
puts_activity(activity)
|
||||
|
|
|
@ -15,7 +15,7 @@ defmodule Pleroma.Captcha.Kocaptcha do
|
|||
%{error: "Kocaptcha service unavailable"}
|
||||
|
||||
{:ok, res} ->
|
||||
json_resp = Poison.decode!(res.body)
|
||||
json_resp = Jason.decode!(res.body)
|
||||
|
||||
%{
|
||||
type: :kocaptcha,
|
||||
|
|
|
@ -12,8 +12,12 @@ defmodule Pleroma.Config do
|
|||
def get([key], default), do: get(key, default)
|
||||
|
||||
def get([parent_key | keys], default) do
|
||||
Application.get_env(:pleroma, parent_key)
|
||||
|> get_in(keys) || default
|
||||
case :pleroma
|
||||
|> Application.get_env(parent_key)
|
||||
|> get_in(keys) do
|
||||
nil -> default
|
||||
any -> any
|
||||
end
|
||||
end
|
||||
|
||||
def get(key, default) do
|
||||
|
|
|
@ -5,15 +5,6 @@
|
|||
defmodule Pleroma.Config.DeprecationWarnings do
|
||||
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
|
||||
if Pleroma.Config.get([:mrf_hellthread, :threshold]) do
|
||||
Logger.warn("""
|
||||
|
@ -24,7 +15,6 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
|||
end
|
||||
|
||||
def warn do
|
||||
check_frontend_config_mechanism()
|
||||
check_hellthread_threshold()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -45,10 +45,10 @@ defmodule Pleroma.Conversation do
|
|||
2. Create a participation for all the people involved who don't have one already
|
||||
3. Bump all relevant participations to 'unread'
|
||||
"""
|
||||
def create_or_bump_for(activity) do
|
||||
def create_or_bump_for(activity, opts \\ []) do
|
||||
with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity),
|
||||
object <- Pleroma.Object.normalize(activity),
|
||||
"Create" <- activity.data["type"],
|
||||
object <- Pleroma.Object.normalize(activity),
|
||||
"Note" <- object.data["type"],
|
||||
ap_id when is_binary(ap_id) and byte_size(ap_id) > 0 <- object.data["context"] do
|
||||
{:ok, conversation} = create_for_ap_id(ap_id)
|
||||
|
@ -58,7 +58,7 @@ defmodule Pleroma.Conversation do
|
|||
participations =
|
||||
Enum.map(users, fn user ->
|
||||
{:ok, participation} =
|
||||
Participation.create_for_user_and_conversation(user, conversation)
|
||||
Participation.create_for_user_and_conversation(user, conversation, opts)
|
||||
|
||||
participation
|
||||
end)
|
||||
|
@ -72,4 +72,21 @@ defmodule Pleroma.Conversation do
|
|||
e -> {:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
This is only meant to be run by a mix task. It creates conversations/participations for all direct messages in the database.
|
||||
"""
|
||||
def bump_for_all_activities do
|
||||
stream =
|
||||
Pleroma.Web.ActivityPub.ActivityPub.fetch_direct_messages_query()
|
||||
|> Repo.stream()
|
||||
|
||||
Repo.transaction(
|
||||
fn ->
|
||||
stream
|
||||
|> Enum.each(fn a -> create_or_bump_for(a, read: true) end)
|
||||
end,
|
||||
timeout: :infinity
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,15 +22,17 @@ defmodule Pleroma.Conversation.Participation do
|
|||
|
||||
def creation_cng(struct, params) do
|
||||
struct
|
||||
|> cast(params, [:user_id, :conversation_id])
|
||||
|> cast(params, [:user_id, :conversation_id, :read])
|
||||
|> validate_required([:user_id, :conversation_id])
|
||||
end
|
||||
|
||||
def create_for_user_and_conversation(user, conversation) do
|
||||
def create_for_user_and_conversation(user, conversation, opts \\ []) do
|
||||
read = !!opts[:read]
|
||||
|
||||
%__MODULE__{}
|
||||
|> creation_cng(%{user_id: user.id, conversation_id: conversation.id})
|
||||
|> creation_cng(%{user_id: user.id, conversation_id: conversation.id, read: read})
|
||||
|> Repo.insert(
|
||||
on_conflict: [set: [read: false, updated_at: NaiveDateTime.utc_now()]],
|
||||
on_conflict: [set: [read: read, updated_at: NaiveDateTime.utc_now()]],
|
||||
returning: true,
|
||||
conflict_target: [:user_id, :conversation_id]
|
||||
)
|
||||
|
|
|
@ -29,7 +29,7 @@ defmodule Pleroma.Emails.AdminEmail do
|
|||
end
|
||||
|
||||
statuses_html =
|
||||
if length(statuses) > 0 do
|
||||
if is_list(statuses) && length(statuses) > 0 do
|
||||
statuses_list_html =
|
||||
statuses
|
||||
|> Enum.map(fn
|
||||
|
|
|
@ -38,7 +38,8 @@ defmodule Pleroma.Filter do
|
|||
query =
|
||||
from(
|
||||
f in Pleroma.Filter,
|
||||
where: f.user_id == ^user_id
|
||||
where: f.user_id == ^user_id,
|
||||
order_by: [desc: :id]
|
||||
)
|
||||
|
||||
Repo.all(query)
|
||||
|
|
|
@ -77,13 +77,13 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
|
|||
user = User.get_cached_by_ap_id(activity.data["actor"])
|
||||
|
||||
object = Object.normalize(activity)
|
||||
like_count = object["like_count"] || 0
|
||||
announcement_count = object["announcement_count"] || 0
|
||||
like_count = object.data["like_count"] || 0
|
||||
announcement_count = object.data["announcement_count"] || 0
|
||||
|
||||
link("Post ##{activity.id} by #{user.nickname}", "/notices/#{activity.id}") <>
|
||||
info("#{like_count} likes, #{announcement_count} repeats") <>
|
||||
"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)
|
||||
|> Enum.join("i\tfake\t(NULL)\t0\r\n")
|
||||
end
|
||||
|
|
|
@ -33,6 +33,13 @@ defmodule Pleroma.Notification do
|
|||
def for_user_query(user) do
|
||||
Notification
|
||||
|> 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(:left, [n, a], object in Object,
|
||||
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
|
|
@ -20,8 +20,9 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
|||
|
||||
defp headers do
|
||||
referrer_policy = Config.get([:http_security, :referrer_policy])
|
||||
report_uri = Config.get([:http_security, :report_uri])
|
||||
|
||||
[
|
||||
headers = [
|
||||
{"x-xss-protection", "1; mode=block"},
|
||||
{"x-permitted-cross-domain-policies", "none"},
|
||||
{"x-frame-options", "DENY"},
|
||||
|
@ -30,12 +31,27 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
|||
{"x-download-options", "noopen"},
|
||||
{"content-security-policy", csp_string() <> ";"}
|
||||
]
|
||||
|
||||
if report_uri do
|
||||
report_group = %{
|
||||
"group" => "csp-endpoint",
|
||||
"max-age" => 10_886_400,
|
||||
"endpoints" => [
|
||||
%{"url" => report_uri}
|
||||
]
|
||||
}
|
||||
|
||||
headers ++ [{"reply-to", Jason.encode!(report_group)}]
|
||||
else
|
||||
headers
|
||||
end
|
||||
end
|
||||
|
||||
defp csp_string do
|
||||
scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
|
||||
static_url = Pleroma.Web.Endpoint.static_url()
|
||||
websocket_url = Pleroma.Web.Endpoint.websocket_url()
|
||||
report_uri = Config.get([:http_security, :report_uri])
|
||||
|
||||
connect_src = "connect-src 'self' #{static_url} #{websocket_url}"
|
||||
|
||||
|
@ -53,7 +69,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
|||
"script-src 'self'"
|
||||
end
|
||||
|
||||
[
|
||||
main_part = [
|
||||
"default-src 'none'",
|
||||
"base-uri 'self'",
|
||||
"frame-ancestors 'none'",
|
||||
|
@ -63,11 +79,14 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
|||
"font-src 'self'",
|
||||
"manifest-src 'self'",
|
||||
connect_src,
|
||||
script_src,
|
||||
if scheme == "https" do
|
||||
"upgrade-insecure-requests"
|
||||
end
|
||||
script_src
|
||||
]
|
||||
|
||||
report = if report_uri, do: ["report-uri #{report_uri}; report-to csp-endpoint"], else: []
|
||||
|
||||
insecure = if scheme == "https", do: ["upgrade-insecure-requests"], else: []
|
||||
|
||||
(main_part ++ report ++ insecure)
|
||||
|> Enum.join("; ")
|
||||
end
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.HTTPSignatures
|
||||
import Plug.Conn
|
||||
require Logger
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Plugs.OAuthPlug do
|
|||
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
||||
@realm_reg Regex.compile!("Bearer\:?\s+(.*)$", "i")
|
||||
|
@ -22,18 +23,39 @@ defmodule Pleroma.Plugs.OAuthPlug do
|
|||
|> assign(:token, token_record)
|
||||
|> assign(:user, user)
|
||||
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
|
||||
|
||||
def call(conn, _) do
|
||||
with {:ok, token_str} <- fetch_token_str(conn),
|
||||
{:ok, user, token_record} <- fetch_user_and_token(token_str) do
|
||||
conn
|
||||
|> assign(:token, token_record)
|
||||
|> assign(:user, user)
|
||||
else
|
||||
_ -> conn
|
||||
case fetch_token_str(conn) do
|
||||
{:ok, token} ->
|
||||
with {:ok, user, token_record} <- fetch_user_and_token(token) do
|
||||
conn
|
||||
|> assign(:token, token_record)
|
||||
|> assign(:user, user)
|
||||
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
|
||||
|
||||
|
@ -54,6 +76,16 @@ defmodule Pleroma.Plugs.OAuthPlug do
|
|||
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
|
||||
#
|
||||
@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 @@ defmodule Pleroma.Uploaders.Swift.Keystone do
|
|||
|
||||
def process_response_body(body) do
|
||||
body
|
||||
|> Poison.decode!()
|
||||
|> Jason.decode!()
|
||||
end
|
||||
|
||||
def get_token do
|
||||
|
@ -38,7 +38,7 @@ defmodule Pleroma.Uploaders.Swift.Keystone do
|
|||
end
|
||||
|
||||
def make_auth_body(username, password, tenant) do
|
||||
Poison.encode!(%{
|
||||
Jason.encode!(%{
|
||||
:auth => %{
|
||||
:passwordCredentials => %{
|
||||
:username => username,
|
||||
|
|
|
@ -55,7 +55,7 @@ defmodule Pleroma.User do
|
|||
field(:last_refreshed_at, :naive_datetime_usec)
|
||||
has_many(:notifications, Notification)
|
||||
has_many(:registrations, Registration)
|
||||
embeds_one(:info, Pleroma.User.Info)
|
||||
embeds_one(:info, User.Info)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
@ -105,10 +105,8 @@ defmodule Pleroma.User do
|
|||
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
|
||||
|
||||
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,
|
||||
follower_count: user.info.follower_count,
|
||||
locked: user.info.locked,
|
||||
|
@ -117,6 +115,20 @@ defmodule Pleroma.User do
|
|||
}
|
||||
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
|
||||
params =
|
||||
params
|
||||
|
@ -154,7 +166,7 @@ defmodule Pleroma.User do
|
|||
|
||||
def update_changeset(struct, params \\ %{}) do
|
||||
struct
|
||||
|> cast(params, [:bio, :name, :avatar])
|
||||
|> cast(params, [:bio, :name, :avatar, :following])
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_format(:nickname, local_nickname_regex())
|
||||
|> validate_length(:bio, max: 5000)
|
||||
|
@ -204,14 +216,15 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||
confirmation_status =
|
||||
if opts[:confirmed] || !Pleroma.Config.get([:instance, :account_activation_required]) do
|
||||
:confirmed
|
||||
need_confirmation? =
|
||||
if is_nil(opts[:need_confirmation]) do
|
||||
Pleroma.Config.get([:instance, :account_activation_required])
|
||||
else
|
||||
:unconfirmed
|
||||
opts[:need_confirmation]
|
||||
end
|
||||
|
||||
info_change = User.Info.confirmation_changeset(%User.Info{}, confirmation_status)
|
||||
info_change =
|
||||
User.Info.confirmation_changeset(%User.Info{}, need_confirmation: need_confirmation?)
|
||||
|
||||
changeset =
|
||||
struct
|
||||
|
@ -220,7 +233,7 @@ defmodule Pleroma.User do
|
|||
|> validate_confirmation(:password)
|
||||
|> unique_constraint(:email)
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames]))
|
||||
|> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
|
||||
|> validate_format(:nickname, local_nickname_regex())
|
||||
|> validate_format(:email, @email_regex)
|
||||
|> validate_length(:bio, max: 1000)
|
||||
|
@ -254,7 +267,7 @@ defmodule Pleroma.User do
|
|||
candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
|
||||
|
||||
autofollowed_users =
|
||||
User.Query.build(%{nickname: candidates, local: true})
|
||||
User.Query.build(%{nickname: candidates, local: true, deactivated: false})
|
||||
|> Repo.all()
|
||||
|
||||
follow_all(user, autofollowed_users)
|
||||
|
@ -265,7 +278,7 @@ defmodule Pleroma.User do
|
|||
with {:ok, user} <- Repo.insert(changeset),
|
||||
{:ok, user} <- autofollow_users(user),
|
||||
{:ok, user} <- set_cache(user),
|
||||
{:ok, _} <- Pleroma.User.WelcomeMessage.post_welcome_message_to_user(user),
|
||||
{:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
|
||||
{:ok, _} <- try_send_confirmation_email(user) do
|
||||
{:ok, user}
|
||||
end
|
||||
|
@ -412,24 +425,6 @@ defmodule Pleroma.User do
|
|||
Enum.member?(follower.following, followed.follower_address)
|
||||
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
|
||||
user.info.locked || false
|
||||
end
|
||||
|
@ -551,8 +546,7 @@ defmodule Pleroma.User do
|
|||
with [_nick, _domain] <- String.split(nickname, "@"),
|
||||
{:ok, user} <- fetch_by_nickname(nickname) do
|
||||
if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
|
||||
# TODO turn into job
|
||||
{:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
|
||||
fetch_initial_posts(user)
|
||||
end
|
||||
|
||||
{:ok, user}
|
||||
|
@ -563,19 +557,12 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
@doc "Fetch some posts when the user has just been federated with"
|
||||
def fetch_initial_posts(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
|
||||
)
|
||||
end
|
||||
def fetch_initial_posts(user),
|
||||
do: PleromaJobQueue.enqueue(:background, __MODULE__, [:fetch_initial_posts, user])
|
||||
|
||||
@spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
|
||||
def get_followers_query(%User{} = user, nil) do
|
||||
User.Query.build(%{followers: user})
|
||||
User.Query.build(%{followers: user, deactivated: false})
|
||||
end
|
||||
|
||||
def get_followers_query(user, page) do
|
||||
|
@ -600,7 +587,7 @@ defmodule Pleroma.User do
|
|||
|
||||
@spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
|
||||
def get_friends_query(%User{} = user, nil) do
|
||||
User.Query.build(%{friends: user})
|
||||
User.Query.build(%{friends: user, deactivated: false})
|
||||
end
|
||||
|
||||
def get_friends_query(user, page) do
|
||||
|
@ -690,16 +677,16 @@ defmodule Pleroma.User do
|
|||
|
||||
info_cng = User.Info.set_note_count(user.info, note_count)
|
||||
|
||||
cng =
|
||||
change(user)
|
||||
|> put_embed(:info, info_cng)
|
||||
|
||||
update_and_set_cache(cng)
|
||||
user
|
||||
|> change()
|
||||
|> put_embed(:info, info_cng)
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
def update_follower_count(%User{} = user) do
|
||||
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
|
||||
|> where(id: ^user.id)
|
||||
|
@ -722,9 +709,21 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def remove_duplicated_following(%User{following: following} = user) do
|
||||
uniq_following = Enum.uniq(following)
|
||||
|
||||
if length(following) == length(uniq_following) do
|
||||
{:ok, user}
|
||||
else
|
||||
user
|
||||
|> update_changeset(%{following: uniq_following})
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
end
|
||||
|
||||
@spec get_users_from_set([String.t()], boolean()) :: [User.t()]
|
||||
def get_users_from_set(ap_ids, local_only \\ true) do
|
||||
criteria = %{ap_id: ap_ids}
|
||||
criteria = %{ap_id: ap_ids, deactivated: false}
|
||||
criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
|
||||
|
||||
User.Query.build(criteria)
|
||||
|
@ -733,7 +732,7 @@ defmodule Pleroma.User do
|
|||
|
||||
@spec get_recipients_from_activity(Activity.t()) :: [User.t()]
|
||||
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()
|
||||
end
|
||||
|
||||
|
@ -831,6 +830,7 @@ defmodule Pleroma.User do
|
|||
^processed_query
|
||||
)
|
||||
)
|
||||
|> restrict_deactivated()
|
||||
end
|
||||
|
||||
defp trigram_search_subquery(term) do
|
||||
|
@ -849,23 +849,7 @@ defmodule Pleroma.User do
|
|||
},
|
||||
where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term)
|
||||
)
|
||||
end
|
||||
|
||||
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
|
||||
)
|
||||
|> restrict_deactivated()
|
||||
end
|
||||
|
||||
def mute(muter, %User{ap_id: ap_id}) do
|
||||
|
@ -998,19 +982,19 @@ defmodule Pleroma.User do
|
|||
|
||||
@spec muted_users(User.t()) :: [User.t()]
|
||||
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()
|
||||
end
|
||||
|
||||
@spec blocked_users(User.t()) :: [User.t()]
|
||||
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()
|
||||
end
|
||||
|
||||
@spec subscribers(User.t()) :: [User.t()]
|
||||
def subscribers(user) do
|
||||
User.Query.build(%{ap_id: user.info.subscribers})
|
||||
User.Query.build(%{ap_id: user.info.subscribers, deactivated: false})
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
|
@ -1038,14 +1022,25 @@ defmodule Pleroma.User do
|
|||
update_and_set_cache(cng)
|
||||
end
|
||||
|
||||
def deactivate_async(user, status \\ true) do
|
||||
PleromaJobQueue.enqueue(:background, __MODULE__, [:deactivate_async, user, status])
|
||||
end
|
||||
|
||||
def deactivate(%User{} = user, status \\ true) do
|
||||
info_cng = User.Info.set_activation_status(user.info, status)
|
||||
|
||||
cng =
|
||||
change(user)
|
||||
|> put_embed(:info, info_cng)
|
||||
with {:ok, friends} <- User.get_friends(user),
|
||||
{:ok, followers} <- User.get_followers(user),
|
||||
{: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
|
||||
|
||||
def update_notification_settings(%User{} = user, settings \\ %{}) do
|
||||
|
@ -1076,11 +1071,79 @@ defmodule Pleroma.User do
|
|||
delete_user_activities(user)
|
||||
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
|
||||
stream =
|
||||
ap_id
|
||||
|> Activity.query_by_actor()
|
||||
|> Activity.with_preloaded_object()
|
||||
|> Repo.stream()
|
||||
|
||||
Repo.transaction(fn -> Enum.each(stream, &delete_activity(&1)) end, timeout: :infinity)
|
||||
|
@ -1129,8 +1192,8 @@ defmodule Pleroma.User do
|
|||
resp = fetch_by_ap_id(ap_id)
|
||||
|
||||
if should_fetch_initial do
|
||||
with {:ok, %User{} = user} = resp do
|
||||
{:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
|
||||
with {:ok, %User{} = user} <- resp do
|
||||
fetch_initial_posts(user)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1319,11 +1382,24 @@ defmodule Pleroma.User do
|
|||
|
||||
@spec all_superusers() :: [User.t()]
|
||||
def all_superusers do
|
||||
User.Query.build(%{super_users: true, local: true})
|
||||
User.Query.build(%{super_users: true, local: true, deactivated: false})
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def showing_reblogs?(%User{} = user, %User{} = target) do
|
||||
target.ap_id not in user.info.muted_reblogs
|
||||
end
|
||||
|
||||
@spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
|
||||
def toggle_confirmation(%User{} = user) do
|
||||
need_confirmation? = !user.info.confirmation_pending
|
||||
|
||||
info_changeset =
|
||||
User.Info.confirmation_changeset(user.info, need_confirmation: need_confirmation?)
|
||||
|
||||
user
|
||||
|> change()
|
||||
|> put_embed(:info, info_changeset)
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,6 +8,8 @@ defmodule Pleroma.User.Info do
|
|||
|
||||
alias Pleroma.User.Info
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
embedded_schema do
|
||||
field(:banner, :map, default: %{})
|
||||
field(:background, :map, default: %{})
|
||||
|
@ -210,21 +212,23 @@ defmodule Pleroma.User.Info do
|
|||
])
|
||||
end
|
||||
|
||||
def confirmation_changeset(info, :confirmed) do
|
||||
confirmation_changeset(info, %{
|
||||
confirmation_pending: false,
|
||||
confirmation_token: nil
|
||||
})
|
||||
end
|
||||
@spec confirmation_changeset(Info.t(), keyword()) :: Changeset.t()
|
||||
def confirmation_changeset(info, opts) do
|
||||
need_confirmation? = Keyword.get(opts, :need_confirmation)
|
||||
|
||||
def confirmation_changeset(info, :unconfirmed) do
|
||||
confirmation_changeset(info, %{
|
||||
confirmation_pending: true,
|
||||
confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
|
||||
})
|
||||
end
|
||||
params =
|
||||
if need_confirmation? do
|
||||
%{
|
||||
confirmation_pending: true,
|
||||
confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
|
||||
}
|
||||
else
|
||||
%{
|
||||
confirmation_pending: false,
|
||||
confirmation_token: nil
|
||||
}
|
||||
end
|
||||
|
||||
def confirmation_changeset(info, params) do
|
||||
cast(info, params, [:confirmation_pending, :confirmation_token])
|
||||
end
|
||||
|
||||
|
|
|
@ -118,7 +118,11 @@ defmodule Pleroma.User.Query do
|
|||
|> where([u], not is_nil(u.nickname))
|
||||
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([u], not is_nil(u.nickname))
|
||||
end
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Conversation
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Fetcher
|
||||
|
@ -15,7 +14,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.MRF
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.Federator
|
||||
alias Pleroma.Web.WebFinger
|
||||
|
||||
import Ecto.Query
|
||||
|
@ -24,8 +22,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
require Logger
|
||||
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
|
||||
# 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.
|
||||
defp get_recipients(%{"type" => "Announce"} = data) do
|
||||
|
@ -137,9 +133,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
activity
|
||||
end
|
||||
|
||||
Task.start(fn ->
|
||||
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
|
||||
end)
|
||||
PleromaJobQueue.enqueue(:background, Pleroma.Web.RichMedia.Helpers, [:fetch, activity])
|
||||
|
||||
Notification.create_notifications(activity)
|
||||
|
||||
|
@ -546,8 +540,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
)
|
||||
)
|
||||
|
||||
Ecto.Adapters.SQL.to_sql(:all, Repo, query)
|
||||
|
||||
query
|
||||
else
|
||||
Logger.error("Could not restrict visibility to #{visibility}")
|
||||
|
@ -563,8 +555,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
|
||||
)
|
||||
|
||||
Ecto.Adapters.SQL.to_sql(:all, Repo, query)
|
||||
|
||||
query
|
||||
end
|
||||
|
||||
|
@ -575,6 +565,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
defp restrict_visibility(query, _visibility), do: query
|
||||
|
||||
defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}) do
|
||||
query =
|
||||
from(
|
||||
a in query,
|
||||
where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
|
||||
)
|
||||
|
||||
query
|
||||
end
|
||||
|
||||
defp restrict_thread_visibility(query, _), do: query
|
||||
|
||||
def fetch_user_activities(user, reading_user, params \\ %{}) do
|
||||
params =
|
||||
params
|
||||
|
@ -701,6 +703,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
defp restrict_type(query, _), do: query
|
||||
|
||||
defp restrict_state(query, %{"state" => state}) do
|
||||
from(activity in query, where: fragment("?->>'state' = ?", activity.data, ^state))
|
||||
end
|
||||
|
||||
defp restrict_state(query, _), do: query
|
||||
|
||||
defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
|
||||
from(
|
||||
activity in query,
|
||||
|
@ -756,8 +764,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
blocks = info.blocks || []
|
||||
domain_blocks = info.domain_blocks || []
|
||||
|
||||
query =
|
||||
if has_named_binding?(query, :object), do: query, else: Activity.with_joined_object(query)
|
||||
|
||||
from(
|
||||
activity in query,
|
||||
[activity, object: o] in query,
|
||||
where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
|
||||
where: fragment("not (? && ?)", activity.recipients, ^blocks),
|
||||
where:
|
||||
|
@ -767,7 +778,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
activity.data,
|
||||
^blocks
|
||||
),
|
||||
where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks)
|
||||
where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks),
|
||||
where: fragment("not (split_part(?->>'actor', '/', 3) = ANY(?))", o.data, ^domain_blocks)
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -849,15 +861,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> restrict_local(opts)
|
||||
|> restrict_actor(opts)
|
||||
|> restrict_type(opts)
|
||||
|> restrict_state(opts)
|
||||
|> restrict_favorited_by(opts)
|
||||
|> restrict_blocked(opts)
|
||||
|> restrict_muted(opts)
|
||||
|> restrict_media(opts)
|
||||
|> restrict_visibility(opts)
|
||||
|> restrict_thread_visibility(opts)
|
||||
|> restrict_replies(opts)
|
||||
|> restrict_reblogs(opts)
|
||||
|> restrict_pinned(opts)
|
||||
|> restrict_muted_reblogs(opts)
|
||||
|> Activity.restrict_deactivated_users()
|
||||
end
|
||||
|
||||
def fetch_activities(recipients, opts \\ %{}) do
|
||||
|
@ -961,89 +976,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
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
|
||||
def contain_broken_threads(%Activity{} = activity, %User{} = user) do
|
||||
entire_thread_visible_for_user?(activity, user)
|
||||
|
@ -1054,11 +986,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
contain_broken_threads(activity, user)
|
||||
end
|
||||
|
||||
# do post-processing on a timeline
|
||||
def contain_timeline(timeline, user) do
|
||||
timeline
|
||||
|> Enum.filter(fn activity ->
|
||||
contain_activity(activity, user)
|
||||
end)
|
||||
def fetch_direct_messages_query do
|
||||
Activity
|
||||
|> restrict_type(%{"type" => "Create"})
|
||||
|> restrict_visibility(%{visibility: "direct"})
|
||||
|> order_by([activity], asc: activity.id)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -55,7 +55,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
|||
object =
|
||||
if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_nsfw]), actor_host) do
|
||||
tags = (child_object["tag"] || []) ++ ["nsfw"]
|
||||
child_object = Map.put(child_object, "tags", tags)
|
||||
child_object = Map.put(child_object, "tag", tags)
|
||||
child_object = Map.put(child_object, "sensitive", true)
|
||||
Map.put(object, "object", child_object)
|
||||
else
|
||||
|
|
|
@ -31,7 +31,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
|
|||
|
||||
object =
|
||||
object
|
||||
|> Map.put("tags", tags)
|
||||
|> Map.put("tag", tags)
|
||||
|> Map.put("sensitive", true)
|
||||
|
||||
message = Map.put(message, "object", object)
|
||||
|
|
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
|
|
@ -11,7 +11,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
alias Pleroma.Object.Containment
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
|
|
|
@ -20,6 +20,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
require Logger
|
||||
|
||||
@supported_object_types ["Article", "Note", "Video", "Page"]
|
||||
@supported_report_states ~w(open closed resolved)
|
||||
@valid_visibilities ~w(public unlisted private direct)
|
||||
|
||||
# Some implementations send the actor URI as the actor field, others send the entire actor object,
|
||||
# so figure out what the actor's URI is based on what we have.
|
||||
|
@ -670,7 +672,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
"actor" => params.actor.ap_id,
|
||||
"content" => params.content,
|
||||
"object" => object,
|
||||
"context" => params.context
|
||||
"context" => params.context,
|
||||
"state" => "open"
|
||||
}
|
||||
|> Map.merge(additional)
|
||||
end
|
||||
|
@ -682,7 +685,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
"""
|
||||
def fetch_ordered_collection(from, pages_left, acc \\ []) do
|
||||
with {:ok, response} <- Tesla.get(from),
|
||||
{:ok, collection} <- Poison.decode(response.body) do
|
||||
{:ok, collection} <- Jason.decode(response.body) do
|
||||
case collection["type"] do
|
||||
"OrderedCollection" ->
|
||||
# If we've encountered the OrderedCollection and not the page,
|
||||
|
@ -713,4 +716,77 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
#### Report-related helpers
|
||||
|
||||
def update_report_state(%Activity{} = activity, state) when state in @supported_report_states do
|
||||
with new_data <- Map.put(activity.data, "state", state),
|
||||
changeset <- Changeset.change(activity, data: new_data),
|
||||
{:ok, activity} <- Repo.update(changeset) do
|
||||
{:ok, activity}
|
||||
end
|
||||
end
|
||||
|
||||
def update_report_state(_, _), do: {:error, "Unsupported state"}
|
||||
|
||||
def update_activity_visibility(activity, visibility) when visibility in @valid_visibilities do
|
||||
[to, cc, recipients] =
|
||||
activity
|
||||
|> get_updated_targets(visibility)
|
||||
|> Enum.map(&Enum.uniq/1)
|
||||
|
||||
object_data =
|
||||
activity.object.data
|
||||
|> Map.put("to", to)
|
||||
|> Map.put("cc", cc)
|
||||
|
||||
{:ok, object} =
|
||||
activity.object
|
||||
|> Object.change(%{data: object_data})
|
||||
|> Object.update_and_set_cache()
|
||||
|
||||
activity_data =
|
||||
activity.data
|
||||
|> Map.put("to", to)
|
||||
|> Map.put("cc", cc)
|
||||
|
||||
activity
|
||||
|> Map.put(:object, object)
|
||||
|> Activity.change(%{data: activity_data, recipients: recipients})
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def update_activity_visibility(_, _), do: {:error, "Unsupported visibility"}
|
||||
|
||||
defp get_updated_targets(
|
||||
%Activity{data: %{"to" => to} = data, recipients: recipients},
|
||||
visibility
|
||||
) do
|
||||
cc = Map.get(data, "cc", [])
|
||||
follower_address = User.get_cached_by_ap_id(data["actor"]).follower_address
|
||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
case visibility do
|
||||
"public" ->
|
||||
to = [public | List.delete(to, follower_address)]
|
||||
cc = [follower_address | List.delete(cc, public)]
|
||||
recipients = [public | recipients]
|
||||
[to, cc, recipients]
|
||||
|
||||
"private" ->
|
||||
to = [follower_address | List.delete(to, public)]
|
||||
cc = List.delete(cc, public)
|
||||
recipients = List.delete(recipients, public)
|
||||
[to, cc, recipients]
|
||||
|
||||
"unlisted" ->
|
||||
to = [follower_address | List.delete(to, public)]
|
||||
cc = [public | List.delete(cc, follower_address)]
|
||||
recipients = recipients ++ [follower_address, public]
|
||||
[to, cc, recipients]
|
||||
|
||||
_ ->
|
||||
[to, cc, recipients]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
defmodule Pleroma.Web.ActivityPub.Visibility do
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
|
||||
|
@ -13,11 +14,12 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
|
|||
end
|
||||
|
||||
def is_private?(activity) do
|
||||
unless is_public?(activity) do
|
||||
follower_address = User.get_cached_by_ap_id(activity.data["actor"]).follower_address
|
||||
Enum.any?(activity.data["to"], &(&1 == follower_address))
|
||||
with false <- is_public?(activity),
|
||||
%User{follower_address: follower_address} <-
|
||||
User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||
follower_address in activity.data["to"]
|
||||
else
|
||||
false
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -38,24 +40,37 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
|
|||
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
|
||||
end
|
||||
|
||||
# guard
|
||||
def entire_thread_visible_for_user?(nil, _user), do: false
|
||||
def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do
|
||||
{:ok, %{rows: [[result]]}} =
|
||||
Ecto.Adapters.SQL.query(Repo, "SELECT thread_visibility($1, $2)", [
|
||||
user.ap_id,
|
||||
activity.data["id"]
|
||||
])
|
||||
|
||||
# XXX: Probably even more inefficient than the previous implementation intended to be a placeholder untill https://git.pleroma.social/pleroma/pleroma/merge_requests/971 is in develop
|
||||
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
|
||||
result
|
||||
end
|
||||
|
||||
def entire_thread_visible_for_user?(
|
||||
%Activity{} = tail,
|
||||
# %Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,
|
||||
user
|
||||
) do
|
||||
case Object.normalize(tail) do
|
||||
%{data: %{"inReplyTo" => parent_id}} when is_binary(parent_id) ->
|
||||
parent = Activity.get_in_reply_to_activity(tail)
|
||||
visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user)
|
||||
def get_visibility(object) do
|
||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||
to = object.data["to"] || []
|
||||
cc = object.data["cc"] || []
|
||||
|
||||
_ ->
|
||||
visible_for_user?(tail, user)
|
||||
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
|
||||
end
|
||||
|
|
|
@ -4,11 +4,16 @@
|
|||
|
||||
defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||
use Pleroma.Web, :controller
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserInviteToken
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.AdminAPI.AccountView
|
||||
alias Pleroma.Web.AdminAPI.ReportView
|
||||
alias Pleroma.Web.AdminAPI.Search
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||
|
||||
|
@ -59,7 +64,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
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)
|
||||
|
||||
conn
|
||||
|
@ -287,12 +292,88 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|> json(token.token)
|
||||
end
|
||||
|
||||
def list_reports(conn, params) do
|
||||
params =
|
||||
params
|
||||
|> Map.put("type", "Flag")
|
||||
|> Map.put("skip_preload", true)
|
||||
|
||||
reports =
|
||||
[]
|
||||
|> ActivityPub.fetch_activities(params)
|
||||
|> Enum.reverse()
|
||||
|
||||
conn
|
||||
|> put_view(ReportView)
|
||||
|> render("index.json", %{reports: reports})
|
||||
end
|
||||
|
||||
def report_show(conn, %{"id" => id}) do
|
||||
with %Activity{} = report <- Activity.get_by_id(id) do
|
||||
conn
|
||||
|> put_view(ReportView)
|
||||
|> render("show.json", %{report: report})
|
||||
else
|
||||
_ -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def report_update_state(conn, %{"id" => id, "state" => state}) do
|
||||
with {:ok, report} <- CommonAPI.update_report_state(id, state) do
|
||||
conn
|
||||
|> put_view(ReportView)
|
||||
|> render("show.json", %{report: report})
|
||||
end
|
||||
end
|
||||
|
||||
def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
|
||||
with false <- is_nil(params["status"]),
|
||||
%Activity{} <- Activity.get_by_id(id) do
|
||||
params =
|
||||
params
|
||||
|> Map.put("in_reply_to_status_id", id)
|
||||
|> Map.put("visibility", "direct")
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, params)
|
||||
|
||||
conn
|
||||
|> put_view(StatusView)
|
||||
|> render("status.json", %{activity: activity})
|
||||
else
|
||||
true ->
|
||||
{:param_cast, nil}
|
||||
|
||||
nil ->
|
||||
{:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def status_update(conn, %{"id" => id} = params) do
|
||||
with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
|
||||
conn
|
||||
|> put_view(StatusView)
|
||||
|> render("status.json", %{activity: activity})
|
||||
end
|
||||
end
|
||||
|
||||
def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
||||
json(conn, %{})
|
||||
end
|
||||
end
|
||||
|
||||
def errors(conn, {:error, :not_found}) do
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json("Not found")
|
||||
end
|
||||
|
||||
def errors(conn, {:error, reason}) do
|
||||
conn
|
||||
|> put_status(400)
|
||||
|> json(reason)
|
||||
end
|
||||
|
||||
def errors(conn, {:param_cast, _}) do
|
||||
conn
|
||||
|> put_status(400)
|
||||
|
|
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 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
|
|||
password_confirmation: random_password
|
||||
},
|
||||
external: true,
|
||||
confirmed: true
|
||||
need_confirmation: false
|
||||
)
|
||||
|> Repo.insert(),
|
||||
{:ok, _} <-
|
||||
|
|
|
@ -71,6 +71,9 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
{:ok, _} <- unpin(activity_id, user),
|
||||
{:ok, delete} <- ActivityPub.delete(object) do
|
||||
{:ok, delete}
|
||||
else
|
||||
_ ->
|
||||
{:error, "Could not delete"}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -116,32 +119,34 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
def get_visibility(%{"visibility" => visibility})
|
||||
def get_visibility(%{"visibility" => visibility}, in_reply_to)
|
||||
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
|
||||
case get_replied_to_activity(status_id) do
|
||||
nil ->
|
||||
"public"
|
||||
def get_visibility(_, in_reply_to) when not is_nil(in_reply_to) do
|
||||
visibility = get_replied_to_visibility(in_reply_to)
|
||||
{visibility, visibility}
|
||||
end
|
||||
|
||||
in_reply_to ->
|
||||
# XXX: these heuristics should be moved out of MastodonAPI.
|
||||
with %Object{} = object <- Object.normalize(in_reply_to) do
|
||||
Pleroma.Web.MastodonAPI.StatusView.get_visibility(object)
|
||||
end
|
||||
def get_visibility(_, in_reply_to), do: {"public", get_replied_to_visibility(in_reply_to)}
|
||||
|
||||
def get_replied_to_visibility(nil), do: nil
|
||||
|
||||
def get_replied_to_visibility(activity) do
|
||||
with %Object{} = object <- Object.normalize(activity) do
|
||||
Pleroma.Web.ActivityPub.Visibility.get_visibility(object)
|
||||
end
|
||||
end
|
||||
|
||||
def get_visibility(_), do: "public"
|
||||
|
||||
def post(user, %{"status" => status} = data) do
|
||||
visibility = get_visibility(data)
|
||||
limit = Pleroma.Config.get([:instance, :limit])
|
||||
|
||||
with status <- String.trim(status),
|
||||
attachments <- attachments_from_ids(data),
|
||||
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} <-
|
||||
make_content_html(
|
||||
status,
|
||||
|
@ -185,6 +190,8 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
)
|
||||
|
||||
res
|
||||
else
|
||||
e -> {:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -193,7 +200,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
user =
|
||||
with emoji <- emoji_from_profile(user),
|
||||
source_data <- (user.info.source_data || %{}) |> Map.put("tag", emoji),
|
||||
info_cng <- Pleroma.User.Info.set_source_data(user.info, source_data),
|
||||
info_cng <- User.Info.set_source_data(user.info, source_data),
|
||||
change <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
|
||||
{:ok, user} <- User.update_and_set_cache(change) do
|
||||
user
|
||||
|
@ -226,7 +233,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
true <- Enum.member?(object_to, "https://www.w3.org/ns/activitystreams#Public"),
|
||||
%{valid?: true} = info_changeset <-
|
||||
Pleroma.User.Info.add_pinnned_activity(user.info, activity),
|
||||
User.Info.add_pinnned_activity(user.info, activity),
|
||||
changeset <-
|
||||
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
|
||||
{:ok, _user} <- User.update_and_set_cache(changeset) do
|
||||
|
@ -243,7 +250,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
def unpin(id_or_ap_id, user) do
|
||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
%{valid?: true} = info_changeset <-
|
||||
Pleroma.User.Info.remove_pinnned_activity(user.info, activity),
|
||||
User.Info.remove_pinnned_activity(user.info, activity),
|
||||
changeset <-
|
||||
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
|
||||
{:ok, _user} <- User.update_and_set_cache(changeset) do
|
||||
|
@ -311,6 +318,60 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
def update_report_state(activity_id, state) do
|
||||
with %Activity{} = activity <- Activity.get_by_id(activity_id),
|
||||
{:ok, activity} <- Utils.update_report_state(activity, state) do
|
||||
{:ok, activity}
|
||||
else
|
||||
nil ->
|
||||
{:error, :not_found}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
|
||||
_ ->
|
||||
{:error, "Could not update state"}
|
||||
end
|
||||
end
|
||||
|
||||
def update_activity_scope(activity_id, opts \\ %{}) do
|
||||
with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
|
||||
{:ok, activity} <- toggle_sensitive(activity, opts),
|
||||
{:ok, activity} <- set_visibility(activity, opts) do
|
||||
{:ok, activity}
|
||||
else
|
||||
nil ->
|
||||
{:error, :not_found}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp toggle_sensitive(activity, %{"sensitive" => sensitive}) when sensitive in ~w(true false) do
|
||||
toggle_sensitive(activity, %{"sensitive" => String.to_existing_atom(sensitive)})
|
||||
end
|
||||
|
||||
defp toggle_sensitive(%Activity{object: object} = activity, %{"sensitive" => sensitive})
|
||||
when is_boolean(sensitive) do
|
||||
new_data = Map.put(object.data, "sensitive", sensitive)
|
||||
|
||||
{:ok, object} =
|
||||
object
|
||||
|> Object.change(%{data: new_data})
|
||||
|> Object.update_and_set_cache()
|
||||
|
||||
{:ok, Map.put(activity, :object, object)}
|
||||
end
|
||||
|
||||
defp toggle_sensitive(activity, _), do: {:ok, activity}
|
||||
|
||||
defp set_visibility(activity, %{"visibility" => visibility}) do
|
||||
Utils.update_activity_visibility(activity, visibility)
|
||||
end
|
||||
|
||||
defp set_visibility(activity, _), do: {:ok, activity}
|
||||
|
||||
def hide_reblogs(user, muted) do
|
||||
ap_id = muted.ap_id
|
||||
|
||||
|
|
|
@ -237,13 +237,11 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
"tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
|
||||
}
|
||||
|
||||
if in_reply_to do
|
||||
in_reply_to_object = Object.normalize(in_reply_to)
|
||||
|
||||
object
|
||||
|> Map.put("inReplyTo", in_reply_to_object.data["id"])
|
||||
with false <- is_nil(in_reply_to),
|
||||
%Object{} = in_reply_to_object <- Object.normalize(in_reply_to) do
|
||||
Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
|
||||
else
|
||||
object
|
||||
_ -> object
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -29,6 +29,13 @@ defmodule Pleroma.Web.Endpoint do
|
|||
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
|
||||
)
|
||||
|
||||
plug(Plug.Static.IndexHtml, at: "/pleroma/admin/")
|
||||
|
||||
plug(Plug.Static,
|
||||
at: "/pleroma/admin/",
|
||||
from: {:pleroma, "priv/static/adminfe/"}
|
||||
)
|
||||
|
||||
# Code reloading can be explicitly enabled under the
|
||||
# :code_reloader configuration of your endpoint.
|
||||
if code_reloading? do
|
||||
|
|
|
@ -7,13 +7,10 @@ defmodule Pleroma.Web.Federator do
|
|||
alias Pleroma.Object.Containment
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.Federator.Publisher
|
||||
alias Pleroma.Web.Federator.RetryQueue
|
||||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Web.Salmon
|
||||
alias Pleroma.Web.WebFinger
|
||||
alias Pleroma.Web.Websub
|
||||
|
||||
|
@ -42,14 +39,6 @@ defmodule Pleroma.Web.Federator do
|
|||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish, activity], priority)
|
||||
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
|
||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:verify_websub, websub])
|
||||
end
|
||||
|
@ -62,10 +51,6 @@ defmodule Pleroma.Web.Federator do
|
|||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:refresh_subscriptions])
|
||||
end
|
||||
|
||||
def publish_single_salmon(params) do
|
||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_salmon, params])
|
||||
end
|
||||
|
||||
# Job Worker Callbacks
|
||||
|
||||
def perform(:refresh_subscriptions) do
|
||||
|
@ -95,23 +80,7 @@ defmodule Pleroma.Web.Federator 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)
|
||||
|
||||
if Visibility.is_public?(activity) do
|
||||
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)
|
||||
Publisher.publish(actor, activity)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -148,25 +117,11 @@ defmodule Pleroma.Web.Federator do
|
|||
_e ->
|
||||
# Just drop those for now
|
||||
Logger.info("Unhandled activity")
|
||||
Logger.info(Poison.encode!(params, pretty: 2))
|
||||
Logger.info(Jason.encode!(params, pretty: true))
|
||||
:error
|
||||
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(
|
||||
:publish_single_websub,
|
||||
%{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(User.t(), Activity.t()) :: :ok | {:error, any()}
|
||||
|
||||
@spec publish(User.t(), 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(User.t()) :: list()
|
||||
|
||||
@spec gather_webfinger_links(User.t()) :: list()
|
||||
def gather_webfinger_links(%User{} = user) do
|
||||
Config.get([:instance, :federation_publisher_modules])
|
||||
|> Enum.reduce([], fn module, links ->
|
||||
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.Scopes
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
||||
|
||||
alias Pleroma.Web.ControllerHelper
|
||||
import Ecto.Query
|
||||
|
||||
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)
|
||||
@local_mastodon_name "Mastodon-Local"
|
||||
|
||||
|
@ -168,7 +178,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
@mastodon_api_level "2.6.5"
|
||||
@mastodon_api_level "2.7.2"
|
||||
|
||||
def masto_instance(conn, _params) do
|
||||
instance = Config.get(:instance)
|
||||
|
@ -293,7 +303,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
activities =
|
||||
[user.ap_id | user.following]
|
||||
|> ActivityPub.fetch_activities(params)
|
||||
|> ActivityPub.contain_timeline(user)
|
||||
|> Enum.reverse()
|
||||
|
||||
conn
|
||||
|
@ -1236,7 +1245,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
accounts
|
||||
|> Enum.each(fn account_id ->
|
||||
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
||||
%User{} = followed <- Pleroma.User.get_cached_by_id(account_id) do
|
||||
%User{} = followed <- User.get_cached_by_id(account_id) do
|
||||
Pleroma.List.unfollow(list, followed)
|
||||
end
|
||||
end)
|
||||
|
@ -1559,7 +1568,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
user_id: user.id,
|
||||
phrase: phrase,
|
||||
context: context,
|
||||
hide: Map.get(params, "irreversible", nil),
|
||||
hide: Map.get(params, "irreversible", false),
|
||||
whole_word: Map.get(params, "boolean", true)
|
||||
# expires_at
|
||||
}
|
||||
|
@ -1716,6 +1725,53 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
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
|
||||
participations = Participation.for_user_with_last_activity_id(user, params)
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
follow_activity = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, target)
|
||||
|
||||
requested =
|
||||
if follow_activity do
|
||||
if follow_activity && !User.following?(target, user) do
|
||||
follow_activity.data["state"] == "pending"
|
||||
else
|
||||
false
|
||||
|
|
|
@ -16,6 +16,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.MediaProxy
|
||||
|
||||
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1]
|
||||
|
||||
# TODO: Add cached version.
|
||||
defp get_replied_to_activities(activities) do
|
||||
activities
|
||||
|
@ -340,30 +342,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
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
|
||||
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"]}"
|
||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.ActivityPub.MRF
|
||||
alias Pleroma.Web.Federator.Publisher
|
||||
|
||||
plug(Pleroma.Web.FederatingPlug)
|
||||
|
||||
|
@ -137,7 +138,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||
name: Pleroma.Application.name() |> String.downcase(),
|
||||
version: Pleroma.Application.version()
|
||||
},
|
||||
protocols: ["ostatus", "activitypub"],
|
||||
protocols: Publisher.gather_nodeinfo_protocol_names(),
|
||||
services: %{
|
||||
inbound: [],
|
||||
outbound: []
|
||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.OAuth.App do
|
|||
import Ecto.Changeset
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
schema "apps" do
|
||||
field(:client_name, :string)
|
||||
field(:redirect_uris, :string)
|
||||
|
|
|
@ -14,39 +14,57 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
|||
import Ecto.Query
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
schema "oauth_authorizations" do
|
||||
field(:token, :string)
|
||||
field(:scopes, {:array, :string}, default: [])
|
||||
field(:valid_until, :naive_datetime_usec)
|
||||
field(:used, :boolean, default: false)
|
||||
belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId)
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
belongs_to(:app, App)
|
||||
|
||||
timestamps()
|
||||
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
|
||||
scopes = scopes || app.scopes
|
||||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
|
||||
|
||||
authorization = %Authorization{
|
||||
token: token,
|
||||
used: false,
|
||||
%{
|
||||
scopes: scopes || app.scopes,
|
||||
user_id: user.id,
|
||||
app_id: app.id,
|
||||
scopes: scopes,
|
||||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10)
|
||||
app_id: app.id
|
||||
}
|
||||
|
||||
Repo.insert(authorization)
|
||||
|> create_changeset()
|
||||
|> Repo.insert()
|
||||
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
|
||||
auth
|
||||
|> cast(params, [:used])
|
||||
|> validate_required([:used])
|
||||
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
|
||||
if NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) < 0 do
|
||||
Repo.update(use_changeset(auth, %{used: true}))
|
||||
|
@ -57,6 +75,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
|||
|
||||
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
|
||||
from(
|
||||
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)
|
||||
|
||||
@expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
|
||||
|
||||
plug(:fetch_session)
|
||||
plug(:fetch_flash)
|
||||
|
||||
|
@ -144,14 +142,14 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
@doc "Renew access_token with refresh_token"
|
||||
def token_exchange(
|
||||
conn,
|
||||
%{"grant_type" => "refresh_token", "refresh_token" => token} = params
|
||||
%{"grant_type" => "refresh_token", "refresh_token" => token} = _params
|
||||
) 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, token} <- RefreshToken.grant(token) do
|
||||
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
|
||||
_error ->
|
||||
put_status(conn, 400)
|
||||
|
@ -160,14 +158,14 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
end
|
||||
|
||||
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"]),
|
||||
{:ok, auth} <- Authorization.get_by_token(app, fixed_token),
|
||||
%User{} = user <- User.get_cached_by_id(auth.user_id),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
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
|
||||
_error ->
|
||||
put_status(conn, 400)
|
||||
|
@ -179,14 +177,14 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
conn,
|
||||
%{"grant_type" => "password"} = params
|
||||
) do
|
||||
with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},
|
||||
%App{} = app <- get_app_from_request(conn, params),
|
||||
with {:ok, %User{} = user} <- Authenticator.get_user(conn),
|
||||
{:ok, app} <- Token.Utils.fetch_app(conn),
|
||||
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
|
||||
{:user_active, true} <- {:user_active, !user.info.deactivated},
|
||||
{:ok, scopes} <- validate_scopes(app, params),
|
||||
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
json(conn, response_token(user, token))
|
||||
json(conn, Token.Response.build(user, token))
|
||||
else
|
||||
{:auth_active, false} ->
|
||||
# Per https://github.com/tootsuite/mastodon/blob/
|
||||
|
@ -218,11 +216,23 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
token_exchange(conn, params)
|
||||
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
|
||||
def token_exchange(conn, params), do: bad_request(conn, params)
|
||||
|
||||
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
|
||||
json(conn, %{})
|
||||
else
|
||||
|
@ -252,7 +262,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
auth_attrs
|
||||
|> Map.delete("scopes")
|
||||
|> Map.put("scope", scope)
|
||||
|> Poison.encode!()
|
||||
|> Jason.encode!()
|
||||
|
||||
params =
|
||||
auth_attrs
|
||||
|
@ -316,7 +326,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
end
|
||||
|
||||
defp callback_params(%{"state" => state} = params) do
|
||||
Map.merge(params, Poison.decode!(state))
|
||||
Map.merge(params, Jason.decode!(state))
|
||||
end
|
||||
|
||||
def registration_details(conn, %{"authorization" => auth_attrs}) do
|
||||
|
@ -405,33 +415,6 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
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
|
||||
defp redirect_uri(conn, "."), do: mastodon_api_url(conn, :login)
|
||||
|
||||
|
@ -442,18 +425,6 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
defp put_session_registration_id(conn, 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()) ::
|
||||
{:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
|
||||
defp validate_scopes(app, params) do
|
||||
|
|
|
@ -22,7 +22,7 @@ defmodule Pleroma.Web.OAuth.Token do
|
|||
field(:refresh_token, :string)
|
||||
field(:scopes, {:array, :string}, default: [])
|
||||
field(:valid_until, :naive_datetime_usec)
|
||||
belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId)
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
belongs_to(:app, App)
|
||||
|
||||
timestamps()
|
||||
|
@ -45,12 +45,16 @@ defmodule Pleroma.Web.OAuth.Token do
|
|||
|> Repo.find_resource()
|
||||
end
|
||||
|
||||
@spec exchange_token(App.t(), Authorization.t()) ::
|
||||
{:ok, Token.t()} | {:error, Changeset.t()}
|
||||
def exchange_token(app, auth) do
|
||||
with {:ok, auth} <- Authorization.use_token(auth),
|
||||
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(
|
||||
app,
|
||||
User.get_cached_by_id(auth.user_id),
|
||||
user,
|
||||
%{scopes: auth.scopes}
|
||||
)
|
||||
end
|
||||
|
@ -81,12 +85,13 @@ defmodule Pleroma.Web.OAuth.Token do
|
|||
|> validate_required([:valid_until])
|
||||
end
|
||||
|
||||
@spec create_token(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Changeset.t()}
|
||||
def create_token(%App{} = app, %User{} = user, attrs \\ %{}) do
|
||||
%__MODULE__{user_id: user.id, app_id: app.id}
|
||||
|> cast(%{scopes: attrs[:scopes] || app.scopes}, [:scopes])
|
||||
|> validate_required([:scopes, :user_id, :app_id])
|
||||
|> validate_required([:scopes, :app_id])
|
||||
|> put_valid_until(attrs)
|
||||
|> put_token
|
||||
|> put_token()
|
||||
|> put_refresh_token(attrs)
|
||||
|> Repo.insert()
|
||||
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.
|
||||
"""
|
||||
|
||||
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"
|
||||
def format_created_at(%{inserted_at: inserted_at} = _token) do
|
||||
inserted_at
|
||||
|
|
|
@ -18,15 +18,18 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
|||
end
|
||||
end
|
||||
|
||||
defp get_in_reply_to(%{"object" => %{"inReplyTo" => in_reply_to}}) do
|
||||
[
|
||||
{:"thr:in-reply-to",
|
||||
[ref: to_charlist(in_reply_to), href: to_charlist(get_href(in_reply_to))], []}
|
||||
]
|
||||
defp get_in_reply_to(activity) do
|
||||
with %Object{data: %{"inReplyTo" => in_reply_to}} <- Object.normalize(activity) do
|
||||
[
|
||||
{:"thr:in-reply-to",
|
||||
[ref: to_charlist(in_reply_to), href: to_charlist(get_href(in_reply_to))], []}
|
||||
]
|
||||
else
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp get_in_reply_to(_), do: []
|
||||
|
||||
defp get_mentions(to) do
|
||||
Enum.map(to, fn id ->
|
||||
cond do
|
||||
|
@ -98,7 +101,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
|||
[]}
|
||||
end)
|
||||
|
||||
in_reply_to = get_in_reply_to(activity.data)
|
||||
in_reply_to = get_in_reply_to(activity)
|
||||
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
||||
mentions = activity.recipients |> get_mentions
|
||||
|
||||
|
@ -146,7 +149,6 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
|||
updated_at = activity.data["published"]
|
||||
inserted_at = activity.data["published"]
|
||||
|
||||
_in_reply_to = get_in_reply_to(activity.data)
|
||||
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
||||
mentions = activity.recipients |> get_mentions
|
||||
|
||||
|
@ -177,7 +179,6 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
|||
updated_at = activity.data["published"]
|
||||
inserted_at = activity.data["published"]
|
||||
|
||||
_in_reply_to = get_in_reply_to(activity.data)
|
||||
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
||||
|
||||
retweeted_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
|
||||
|
|
|
@ -16,6 +16,7 @@ defmodule Pleroma.Web.OStatus do
|
|||
alias Pleroma.Web
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.OStatus.DeleteHandler
|
||||
alias Pleroma.Web.OStatus.FollowHandler
|
||||
alias Pleroma.Web.OStatus.NoteHandler
|
||||
|
@ -30,7 +31,7 @@ defmodule Pleroma.Web.OStatus do
|
|||
is_nil(object) ->
|
||||
false
|
||||
|
||||
object.data["type"] == "Note" ->
|
||||
Visibility.is_public?(activity) && object.data["type"] == "Note" ->
|
||||
true
|
||||
|
||||
true ->
|
||||
|
|
|
@ -34,4 +34,6 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|
|||
end
|
||||
|
||||
def fetch_data_for_activity(_), do: %{}
|
||||
|
||||
def perform(:fetch, %Activity{} = activity), do: fetch_data_for_activity(activity)
|
||||
end
|
||||
|
|
|
@ -84,11 +84,13 @@ defmodule Pleroma.Web.Router do
|
|||
plug(Pleroma.Plugs.EnsureUserKeyPlug)
|
||||
end
|
||||
|
||||
pipeline :oauth_read_or_unauthenticated do
|
||||
pipeline :oauth_read_or_public do
|
||||
plug(Pleroma.Plugs.OAuthScopesPlug, %{
|
||||
scopes: ["read"],
|
||||
fallback: :proceed_unauthenticated
|
||||
})
|
||||
|
||||
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
|
||||
end
|
||||
|
||||
pipeline :oauth_read do
|
||||
|
@ -146,34 +148,60 @@ defmodule Pleroma.Web.Router do
|
|||
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
|
||||
pipe_through([:admin_api, :oauth_write])
|
||||
|
||||
post("/user/follow", AdminAPIController, :user_follow)
|
||||
post("/user/unfollow", AdminAPIController, :user_unfollow)
|
||||
|
||||
get("/users", AdminAPIController, :list_users)
|
||||
get("/users/:nickname", AdminAPIController, :user_show)
|
||||
post("/users/follow", AdminAPIController, :user_follow)
|
||||
post("/users/unfollow", AdminAPIController, :user_unfollow)
|
||||
|
||||
# TODO: to be removed at version 1.0
|
||||
delete("/user", AdminAPIController, :user_delete)
|
||||
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
|
||||
post("/user", AdminAPIController, :user_create)
|
||||
|
||||
delete("/users", AdminAPIController, :user_delete)
|
||||
post("/users", AdminAPIController, :user_create)
|
||||
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
|
||||
put("/users/tag", AdminAPIController, :tag_users)
|
||||
delete("/users/tag", AdminAPIController, :untag_users)
|
||||
|
||||
# TODO: to be removed at version 1.0
|
||||
get("/permission_group/:nickname", AdminAPIController, :right_get)
|
||||
get("/permission_group/:nickname/:permission_group", AdminAPIController, :right_get)
|
||||
post("/permission_group/:nickname/:permission_group", AdminAPIController, :right_add)
|
||||
delete("/permission_group/:nickname/:permission_group", AdminAPIController, :right_delete)
|
||||
|
||||
put("/activation_status/:nickname", AdminAPIController, :set_activation_status)
|
||||
get("/users/:nickname/permission_group", AdminAPIController, :right_get)
|
||||
get("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_get)
|
||||
post("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_add)
|
||||
|
||||
delete(
|
||||
"/users/:nickname/permission_group/:permission_group",
|
||||
AdminAPIController,
|
||||
:right_delete
|
||||
)
|
||||
|
||||
put("/users/:nickname/activation_status", AdminAPIController, :set_activation_status)
|
||||
|
||||
post("/relay", AdminAPIController, :relay_follow)
|
||||
delete("/relay", AdminAPIController, :relay_unfollow)
|
||||
|
||||
get("/invite_token", AdminAPIController, :get_invite_token)
|
||||
get("/invites", AdminAPIController, :invites)
|
||||
post("/revoke_invite", AdminAPIController, :revoke_invite)
|
||||
post("/email_invite", AdminAPIController, :email_invite)
|
||||
get("/users/invite_token", AdminAPIController, :get_invite_token)
|
||||
get("/users/invites", AdminAPIController, :invites)
|
||||
post("/users/revoke_invite", AdminAPIController, :revoke_invite)
|
||||
post("/users/email_invite", AdminAPIController, :email_invite)
|
||||
|
||||
# TODO: to be removed at version 1.0
|
||||
get("/password_reset", AdminAPIController, :get_password_reset)
|
||||
|
||||
get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
|
||||
|
||||
get("/users", AdminAPIController, :list_users)
|
||||
get("/users/:nickname", AdminAPIController, :user_show)
|
||||
|
||||
get("/reports", AdminAPIController, :list_reports)
|
||||
get("/reports/:id", AdminAPIController, :report_show)
|
||||
put("/reports/:id", AdminAPIController, :report_update_state)
|
||||
post("/reports/:id/respond", AdminAPIController, :report_respond)
|
||||
|
||||
put("/statuses/:id", AdminAPIController, :status_update)
|
||||
delete("/statuses/:id", AdminAPIController, :status_delete)
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web.TwitterAPI do
|
||||
|
@ -197,6 +225,7 @@ defmodule Pleroma.Web.Router do
|
|||
post("/change_password", UtilController, :change_password)
|
||||
post("/delete_account", UtilController, :delete_account)
|
||||
put("/notification_settings", UtilController, :update_notificaton_settings)
|
||||
post("/disable_account", UtilController, :disable_account)
|
||||
end
|
||||
|
||||
scope [] do
|
||||
|
@ -367,6 +396,8 @@ defmodule Pleroma.Web.Router do
|
|||
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
||||
pipe_through(:api)
|
||||
|
||||
post("/accounts", MastodonAPIController, :account_register)
|
||||
|
||||
get("/instance", MastodonAPIController, :masto_instance)
|
||||
get("/instance/peers", MastodonAPIController, :peers)
|
||||
post("/apps", MastodonAPIController, :create_app)
|
||||
|
@ -383,7 +414,7 @@ defmodule Pleroma.Web.Router do
|
|||
get("/accounts/search", MastodonAPIController, :account_search)
|
||||
|
||||
scope [] do
|
||||
pipe_through(:oauth_read_or_unauthenticated)
|
||||
pipe_through(:oauth_read_or_public)
|
||||
|
||||
get("/timelines/public", MastodonAPIController, :public_timeline)
|
||||
get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline)
|
||||
|
@ -404,7 +435,7 @@ defmodule Pleroma.Web.Router do
|
|||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
|
@ -434,7 +465,7 @@ defmodule Pleroma.Web.Router do
|
|||
)
|
||||
|
||||
scope [] do
|
||||
pipe_through(:oauth_read_or_unauthenticated)
|
||||
pipe_through(:oauth_read_or_public)
|
||||
|
||||
get("/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
|
||||
get("/qvitter/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
|
||||
|
@ -452,7 +483,7 @@ defmodule Pleroma.Web.Router do
|
|||
end
|
||||
|
||||
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)
|
||||
|
||||
|
@ -466,7 +497,7 @@ defmodule Pleroma.Web.Router do
|
|||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
|
@ -650,7 +681,7 @@ defmodule Pleroma.Web.Router do
|
|||
delete("/auth/sign_out", MastodonAPIController, :logout)
|
||||
|
||||
scope [] do
|
||||
pipe_through(:oauth_read_or_unauthenticated)
|
||||
pipe_through(:oauth_read_or_public)
|
||||
get("/web/*path", MastodonAPIController, :index)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,12 +3,18 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Salmon do
|
||||
@behaviour Pleroma.Web.Federator.Publisher
|
||||
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
|
||||
use Bitwise
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Instances
|
||||
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.XML
|
||||
|
||||
|
@ -165,12 +171,12 @@ defmodule Pleroma.Web.Salmon do
|
|||
end
|
||||
|
||||
@doc "Pushes an activity to remote account."
|
||||
def send_to_user(%{recipient: %{info: %{salmon: salmon}}} = params),
|
||||
do: send_to_user(Map.put(params, :recipient, salmon))
|
||||
def publish_one(%{recipient: %{info: %{salmon: salmon}}} = params),
|
||||
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 <-
|
||||
poster.(
|
||||
@httpoison.post(
|
||||
url,
|
||||
feed,
|
||||
[{"Content-Type", "application/magic-envelope+xml"}]
|
||||
|
@ -184,11 +190,11 @@ defmodule Pleroma.Web.Salmon do
|
|||
e ->
|
||||
unless params[:unreachable_since], do: Instances.set_reachable(url)
|
||||
Logger.debug(fn -> "Pushing Salmon to #{url} failed, #{inspect(e)}" end)
|
||||
:error
|
||||
{:error, "Unreachable instance"}
|
||||
end
|
||||
end
|
||||
|
||||
def send_to_user(_), do: :noop
|
||||
def publish_one(_), do: :noop
|
||||
|
||||
@supported_activities [
|
||||
"Create",
|
||||
|
@ -199,13 +205,19 @@ defmodule Pleroma.Web.Salmon do
|
|||
"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 """
|
||||
Publishes an activity to remote accounts
|
||||
"""
|
||||
@spec publish(User.t(), Pleroma.Activity.t(), Pleroma.HTTP.t()) :: none
|
||||
def publish(user, activity, poster \\ &@httpoison.post/3)
|
||||
@spec publish(User.t(), Pleroma.Activity.t()) :: none
|
||||
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
|
||||
feed = ActivityRepresenter.to_simple_form(activity, user, true)
|
||||
|
||||
|
@ -229,15 +241,29 @@ defmodule Pleroma.Web.Salmon do
|
|||
|> Enum.each(fn remote_user ->
|
||||
Logger.debug(fn -> "Sending Salmon to #{remote_user.ap_id}" end)
|
||||
|
||||
Pleroma.Web.Federator.publish_single_salmon(%{
|
||||
Publisher.enqueue_one(__MODULE__, %{
|
||||
recipient: remote_user,
|
||||
feed: feed,
|
||||
poster: poster,
|
||||
unreachable_since: reachable_urls_metadata[remote_user.info.salmon]
|
||||
})
|
||||
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
|
||||
|
|
|
@ -173,8 +173,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
|
||||
def config(conn, _params) do
|
||||
instance = Pleroma.Config.get(:instance)
|
||||
instance_fe = Pleroma.Config.get(:fe)
|
||||
instance_chat = Pleroma.Config.get(:chat)
|
||||
|
||||
case get_format(conn) do
|
||||
"xml" ->
|
||||
|
@ -219,31 +217,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
if(Pleroma.Config.get([:instance, :safe_dm_mentions]), do: "1", else: "0")
|
||||
}
|
||||
|
||||
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
|
||||
pleroma_fe = Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
|
||||
|
||||
managed_config = Keyword.get(instance, :managed_config)
|
||||
|
||||
|
@ -309,8 +283,13 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
Enum.map(lines, fn line ->
|
||||
String.split(line, ",") |> List.first()
|
||||
end)
|
||||
|> List.delete("Account address"),
|
||||
{:ok, _} = Task.start(fn -> User.follow_import(follower, followed_identifiers) end) do
|
||||
|> List.delete("Account address") do
|
||||
PleromaJobQueue.enqueue(:background, User, [
|
||||
:follow_import,
|
||||
follower,
|
||||
followed_identifiers
|
||||
])
|
||||
|
||||
json(conn, "job started")
|
||||
end
|
||||
end
|
||||
|
@ -320,8 +299,13 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
end
|
||||
|
||||
def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do
|
||||
with blocked_identifiers <- String.split(list),
|
||||
{:ok, _} = Task.start(fn -> User.blocks_import(blocker, blocked_identifiers) end) do
|
||||
with blocked_identifiers <- String.split(list) do
|
||||
PleromaJobQueue.enqueue(:background, User, [
|
||||
:blocks_import,
|
||||
blocker,
|
||||
blocked_identifiers
|
||||
])
|
||||
|
||||
json(conn, "job started")
|
||||
end
|
||||
end
|
||||
|
@ -360,6 +344,17 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
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
|
||||
json(conn, Pleroma.Captcha.new())
|
||||
end
|
||||
|
|
|
@ -128,7 +128,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
end
|
||||
end
|
||||
|
||||
def register_user(params) do
|
||||
def register_user(params, opts \\ []) do
|
||||
token = params["token"]
|
||||
|
||||
params = %{
|
||||
|
@ -162,13 +162,22 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
# I have no idea how this error handling works
|
||||
{:error, %{error: Jason.encode!(%{captcha: [error]})}}
|
||||
else
|
||||
registrations_open = Pleroma.Config.get([:instance, :registrations_open])
|
||||
registration_process(registrations_open, params, token)
|
||||
registration_process(
|
||||
params,
|
||||
%{
|
||||
registrations_open: Pleroma.Config.get([:instance, :registrations_open]),
|
||||
token: token
|
||||
},
|
||||
opts
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp registration_process(registration_open, params, token)
|
||||
when registration_open == false or is_nil(registration_open) do
|
||||
defp registration_process(params, %{registrations_open: true}, opts) do
|
||||
create_user(params, opts)
|
||||
end
|
||||
|
||||
defp registration_process(params, %{token: token}, opts) do
|
||||
invite =
|
||||
unless is_nil(token) do
|
||||
Repo.get_by(UserInviteToken, %{token: token})
|
||||
|
@ -182,19 +191,15 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
|
||||
invite when valid_invite? ->
|
||||
UserInviteToken.update_usage!(invite)
|
||||
create_user(params)
|
||||
create_user(params, opts)
|
||||
|
||||
_ ->
|
||||
{:error, "Expired token"}
|
||||
end
|
||||
end
|
||||
|
||||
defp registration_process(true, params, _token) do
|
||||
create_user(params)
|
||||
end
|
||||
|
||||
defp create_user(params) do
|
||||
changeset = User.register_changeset(%User{}, params)
|
||||
defp create_user(params, opts) do
|
||||
changeset = User.register_changeset(%User{}, params, opts)
|
||||
|
||||
case User.register(changeset) do
|
||||
{:ok, user} ->
|
||||
|
@ -231,12 +236,15 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
def get_user(user \\ nil, params) do
|
||||
case params do
|
||||
%{"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 ->
|
||||
{:error, "No user with such user_id"}
|
||||
|
||||
_ ->
|
||||
{:ok, target}
|
||||
%User{info: %{deactivated: true}} ->
|
||||
{:error, "User has been disabled"}
|
||||
|
||||
user ->
|
||||
{:ok, user}
|
||||
end
|
||||
|
||||
%{"screen_name" => nickname} ->
|
||||
|
|
|
@ -101,9 +101,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("user", user)
|
||||
|
||||
activities =
|
||||
ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
||||
|> ActivityPub.contain_timeline(user)
|
||||
activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
||||
|
||||
conn
|
||||
|> put_view(ActivityView)
|
||||
|
@ -440,7 +438,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
true <- user.local,
|
||||
true <- user.info.confirmation_pending,
|
||||
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),
|
||||
{:ok, _} <- User.update_and_set_cache(changeset) do
|
||||
conn
|
||||
|
|
|
@ -310,7 +310,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
|
|||
"tags" => tags,
|
||||
"activity_type" => "post",
|
||||
"possibly_sensitive" => possibly_sensitive,
|
||||
"visibility" => StatusView.get_visibility(object),
|
||||
"visibility" => Pleroma.Web.ActivityPub.Visibility.get_visibility(object),
|
||||
"summary" => summary,
|
||||
"summary_html" => summary |> Formatter.emojify(object.data["emoji"]),
|
||||
"card" => card,
|
||||
|
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Web.WebFinger do
|
|||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Web.Federator.Publisher
|
||||
alias Pleroma.Web.Salmon
|
||||
alias Pleroma.Web.XML
|
||||
alias Pleroma.XmlBuilder
|
||||
|
@ -50,70 +50,40 @@ defmodule Pleroma.Web.WebFinger do
|
|||
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
|
||||
{: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()}",
|
||||
"aliases" => [user.ap_id],
|
||||
"links" => [
|
||||
%{
|
||||
"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()
|
||||
}
|
||||
]
|
||||
"links" => gather_links(user)
|
||||
}
|
||||
end
|
||||
|
||||
def represent_user(user, "XML") do
|
||||
{: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,
|
||||
%{xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0"},
|
||||
[
|
||||
{:Subject, "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}"},
|
||||
{:Alias, user.ap_id},
|
||||
{:Link,
|
||||
%{
|
||||
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()}}
|
||||
]
|
||||
{:Alias, user.ap_id}
|
||||
] ++ links
|
||||
}
|
||||
|> XmlBuilder.to_doc()
|
||||
end
|
||||
|
@ -129,7 +99,7 @@ defmodule Pleroma.Web.WebFinger do
|
|||
|
||||
info_cng =
|
||||
info
|
||||
|> Pleroma.User.Info.set_keys(pem)
|
||||
|> User.Info.set_keys(pem)
|
||||
|
||||
cng =
|
||||
Ecto.Changeset.change(user)
|
||||
|
|
|
@ -4,10 +4,14 @@
|
|||
|
||||
defmodule Pleroma.Web.Websub do
|
||||
alias Ecto.Changeset
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.Federator
|
||||
alias Pleroma.Web.Federator.Publisher
|
||||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Web.OStatus.FeedRepresenter
|
||||
alias Pleroma.Web.Router.Helpers
|
||||
|
@ -18,6 +22,8 @@ defmodule Pleroma.Web.Websub do
|
|||
|
||||
import Ecto.Query
|
||||
|
||||
@behaviour Pleroma.Web.Federator.Publisher
|
||||
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
|
||||
def verify(subscription, getter \\ &@httpoison.get/3) do
|
||||
|
@ -56,6 +62,13 @@ defmodule Pleroma.Web.Websub do
|
|||
"Undo",
|
||||
"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)
|
||||
when type in @supported_activities do
|
||||
response =
|
||||
|
@ -88,12 +101,14 @@ defmodule Pleroma.Web.Websub do
|
|||
unreachable_since: reachable_callbacks_metadata[sub.callback]
|
||||
}
|
||||
|
||||
Federator.publish_single_websub(data)
|
||||
Publisher.enqueue_one(__MODULE__, data)
|
||||
end)
|
||||
end
|
||||
|
||||
def publish(_, _, _), do: ""
|
||||
|
||||
def publish(actor, activity), do: publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
|
||||
|
||||
def sign(secret, doc) do
|
||||
:crypto.hmac(:sha, secret, to_string(doc)) |> Base.encode16() |> String.downcase()
|
||||
end
|
||||
|
@ -299,4 +314,20 @@ defmodule Pleroma.Web.Websub do
|
|||
{:error, response}
|
||||
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
|
||||
|
|
|
@ -35,6 +35,7 @@ defmodule Pleroma.XmlBuilder do
|
|||
defp make_open_tag(tag, attributes) do
|
||||
attributes_string =
|
||||
for {attribute, value} <- attributes do
|
||||
value = String.replace(value, "\"", """)
|
||||
"#{attribute}=\"#{value}\""
|
||||
end
|
||||
|> Enum.join(" ")
|
||||
|
|
11
mix.exs
11
mix.exs
|
@ -13,6 +13,7 @@ defmodule Pleroma.Mixfile do
|
|||
start_permanent: Mix.env() == :prod,
|
||||
aliases: aliases(),
|
||||
deps: deps(),
|
||||
test_coverage: [tool: ExCoveralls],
|
||||
|
||||
# Docs
|
||||
name: "Pleroma",
|
||||
|
@ -67,7 +68,7 @@ defmodule Pleroma.Mixfile do
|
|||
{:phoenix_ecto, "~> 4.0"},
|
||||
{:ecto_sql,
|
||||
git: "https://github.com/elixir-ecto/ecto_sql",
|
||||
ref: "e839a9a327b632d73533ac8105ba360bc831cf83",
|
||||
ref: "14cb065a74c488d737d973f7a91bc036c6245f78",
|
||||
override: true},
|
||||
{:postgrex, ">= 0.13.5"},
|
||||
{:gettext, "~> 0.15"},
|
||||
|
@ -106,6 +107,9 @@ defmodule Pleroma.Mixfile do
|
|||
{:auto_linker,
|
||||
git: "https://git.pleroma.social/pleroma/auto_linker.git",
|
||||
ref: "c00c4e75b35367fa42c95ffd9b8c455bf9995829"},
|
||||
{:http_signatures,
|
||||
git: "https://git.pleroma.social/pleroma/http_signatures.git",
|
||||
ref: "9789401987096ead65646b52b5a2ca6bf52fc531"},
|
||||
{:pleroma_job_queue, "~> 0.2.0"},
|
||||
{:telemetry, "~> 0.3"},
|
||||
{:prometheus_ex, "~> 3.0"},
|
||||
|
@ -116,7 +120,10 @@ defmodule Pleroma.Mixfile do
|
|||
{:recon, github: "ferd/recon", tag: "2.4.0"},
|
||||
{:quack, "~> 0.1.1"},
|
||||
{:benchee, "~> 1.0"},
|
||||
{:esshd, "~> 0.1.0"}
|
||||
{:esshd, "~> 0.1.0"},
|
||||
{:ex_rated, "~> 1.2"},
|
||||
{:plug_static_index_html, "~> 1.0.0"},
|
||||
{:excoveralls, "~> 0.11.1", only: :test}
|
||||
] ++ oauth_deps
|
||||
end
|
||||
|
||||
|
|
9
mix.lock
9
mix.lock
|
@ -21,20 +21,24 @@
|
|||
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
|
||||
"earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"},
|
||||
"ecto": {:hex, :ecto, "3.1.4", "69d852da7a9f04ede725855a35ede48d158ca11a404fe94f8b2fb3b2162cd3c9", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"ecto_sql": {:git, "https://github.com/elixir-ecto/ecto_sql", "e839a9a327b632d73533ac8105ba360bc831cf83", [ref: "e839a9a327b632d73533ac8105ba360bc831cf83"]},
|
||||
"ecto_sql": {:git, "https://github.com/elixir-ecto/ecto_sql", "14cb065a74c488d737d973f7a91bc036c6245f78", [ref: "14cb065a74c488d737d973f7a91bc036c6245f78"]},
|
||||
"esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"},
|
||||
"eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"},
|
||||
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
|
||||
"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_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_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"]},
|
||||
"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"},
|
||||
"gen_smtp": {:hex, :gen_smtp, "0.13.0", "11f08504c4bdd831dc520b8f84a1dce5ce624474a797394e7aafd3c29f5dcd25", [:rebar3], [], "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"},
|
||||
"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"},
|
||||
"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"},
|
||||
"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"},
|
||||
|
@ -59,6 +63,7 @@
|
|||
"plug": {:hex, :plug, "1.7.2", "d7b7db7fbd755e8283b6c0a50be71ec0a3d67d9213d74422d9372effc8e87fd1", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.0.1", "d798f8ee5acc86b7d42dbe4450b8b0dadf665ce588236eb0a751a132417a980e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
|
||||
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
|
||||
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"},
|
||||
"postgrex": {:hex, :postgrex, "0.14.3", "5754dee2fdf6e9e508cbf49ab138df964278700b764177e8f3871e658b345a1e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
|
@ -78,7 +83,7 @@
|
|||
"tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"timex": {:hex, :timex, "3.5.0", "b0a23167da02d0fe4f1a4e104d1f929a00d348502b52432c05de875d0b9cffa5", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"tzdata": {:hex, :tzdata, "0.5.17", "50793e3d85af49736701da1a040c415c97dc1caf6464112fd9bd18f425d3053b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"tzdata": {:hex, :tzdata, "0.5.20", "304b9e98a02840fb32a43ec111ffbe517863c8566eb04a061f1c4dbb90b4d84c", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"ueberauth": {:hex, :ueberauth, "0.6.1", "9e90d3337dddf38b1ca2753aca9b1e53d8a52b890191cdc55240247c89230412", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
|
||||
"unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
|
||||
|
|
|
@ -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/adminfe/favicon.ico
Normal file
BIN
priv/static/adminfe/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
1
priv/static/adminfe/index.html
Normal file
1
priv/static/adminfe/index.html
Normal file
|
@ -0,0 +1 @@
|
|||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=static/css/chunk-elementUI.4296cedf.css rel=stylesheet><link href=static/css/chunk-libs.bd17d456.css rel=stylesheet><link href=static/css/app.cea15678.css rel=stylesheet></head><body><script src=/pleroma/admin/static/tinymce4.7.5/tinymce.min.js></script><div id=app></div><script type=text/javascript src=static/js/runtime.7144b2cf.js></script><script type=text/javascript src=static/js/chunk-elementUI.d388c21d.js></script><script type=text/javascript src=static/js/chunk-libs.48e79a9e.js></script><script type=text/javascript src=static/js/app.25699e3d.js></script></body></html>
|
1
priv/static/adminfe/static/css/app.cea15678.css
Normal file
1
priv/static/adminfe/static/css/app.cea15678.css
Normal file
File diff suppressed because one or more lines are too long
1
priv/static/adminfe/static/css/chunk-18e1.6aaab273.css
Normal file
1
priv/static/adminfe/static/css/chunk-18e1.6aaab273.css
Normal file
|
@ -0,0 +1 @@
|
|||
.errPage-container[data-v-ab9be52c]{width:800px;max-width:100%;margin:100px auto}.errPage-container .pan-back-btn[data-v-ab9be52c]{background:#008489;color:#fff;border:none!important}.errPage-container .pan-gif[data-v-ab9be52c]{margin:0 auto;display:block}.errPage-container .pan-img[data-v-ab9be52c]{display:block;margin:0 auto;width:100%}.errPage-container .text-jumbo[data-v-ab9be52c]{font-size:60px;font-weight:700;color:#484848}.errPage-container .list-unstyled[data-v-ab9be52c]{font-size:14px}.errPage-container .list-unstyled li[data-v-ab9be52c]{padding-bottom:5px}.errPage-container .list-unstyled a[data-v-ab9be52c]{color:#008489;text-decoration:none}.errPage-container .list-unstyled a[data-v-ab9be52c]:hover{text-decoration:underline}
|
1
priv/static/adminfe/static/css/chunk-50cf.1db1ed5b.css
Normal file
1
priv/static/adminfe/static/css/chunk-50cf.1db1ed5b.css
Normal file
|
@ -0,0 +1 @@
|
|||
.select-field[data-v-71bc6b38]{width:350px}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.select-field[data-v-71bc6b38]{width:100%;margin-bottom:5px}}.active-tag[data-v-693dba04]{color:#409eff;font-weight:700}.active-tag .el-icon-check[data-v-693dba04]{color:#409eff;float:right;margin:7px 0 0 15px}.users-container h1[data-v-693dba04]{margin:22px 0 0 15px}.users-container .pagination[data-v-693dba04]{margin:25px 0;text-align:center}.users-container .search[data-v-693dba04]{width:350px;float:right}.users-container .search-container[data-v-693dba04]{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:22px 15px}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.users-container h1[data-v-693dba04]{margin:7px 10px}.users-container .el-dropdown-link[data-v-693dba04]{cursor:pointer;color:#409eff}.users-container .el-icon-arrow-down[data-v-693dba04]{font-size:12px}.users-container .search[data-v-693dba04]{width:100%}.users-container .search-container[data-v-693dba04]{display:-webkit-box;display:-ms-flexbox;display:flex;height:82px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:0 10px 7px}.users-container .el-tag[data-v-693dba04]{width:30px;display:inline-block;margin-bottom:4px;font-weight:700}.users-container .el-tag.el-tag--danger[data-v-693dba04],.users-container .el-tag.el-tag--success[data-v-693dba04]{padding-left:8px}}
|
1
priv/static/adminfe/static/css/chunk-8b70.9ba0945c.css
Normal file
1
priv/static/adminfe/static/css/chunk-8b70.9ba0945c.css
Normal file
|
@ -0,0 +1 @@
|
|||
@supports (-webkit-mask:none) and (not (cater-color:#fff)){.login-container .el-input input{color:#fff}.login-container .el-input input:first-line{color:#eee}}.login-container .el-input{display:inline-block;height:47px;width:85%}.login-container .el-input input{background:transparent;border:0;-webkit-appearance:none;border-radius:0;padding:12px 5px 12px 15px;color:#eee;height:47px;caret-color:#fff}.login-container .el-input input:-webkit-autofill{-webkit-box-shadow:0 0 0 1000px #283443 inset!important;-webkit-text-fill-color:#fff!important}.login-container .el-form-item{border:1px solid hsla(0,0%,100%,.1);background:rgba(0,0,0,.1);border-radius:5px;color:#454545}.login-container[data-v-57350b8e]{min-height:100%;width:100%;background-color:#2d3a4b;overflow:hidden}.login-container .login-form[data-v-57350b8e]{position:relative;width:520px;max-width:100%;padding:160px 35px 0;margin:0 auto;overflow:hidden}.login-container .tips[data-v-57350b8e]{font-size:14px;color:#fff;margin-bottom:10px}.login-container .tips span[data-v-57350b8e]:first-of-type{margin-right:16px}.login-container .svg-container[data-v-57350b8e]{padding:6px 5px 6px 15px;color:#889aa4;vertical-align:middle;width:30px;display:inline-block}.login-container .title-container[data-v-57350b8e]{position:relative}.login-container .title-container .title[data-v-57350b8e]{font-size:26px;color:#eee;margin:0 auto 40px;text-align:center;font-weight:700}.login-container .title-container .set-language[data-v-57350b8e]{color:#fff;position:absolute;top:3px;font-size:18px;right:0;cursor:pointer}.login-container .show-pwd[data-v-57350b8e]{position:absolute;right:10px;top:7px;font-size:16px;color:#889aa4;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.login-container .thirdparty-button[data-v-57350b8e]{position:absolute;right:0;bottom:6px}
|
File diff suppressed because one or more lines are too long
1
priv/static/adminfe/static/css/chunk-f018.0d22684d.css
Normal file
1
priv/static/adminfe/static/css/chunk-f018.0d22684d.css
Normal file
|
@ -0,0 +1 @@
|
|||
.wscn-http404-container[data-v-b8c8aa9a]{-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);position:absolute;top:40%;left:50%}.wscn-http404[data-v-b8c8aa9a]{position:relative;width:1200px;padding:0 50px;overflow:hidden}.wscn-http404 .pic-404[data-v-b8c8aa9a]{position:relative;float:left;width:600px;overflow:hidden}.wscn-http404 .pic-404__parent[data-v-b8c8aa9a]{width:100%}.wscn-http404 .pic-404__child[data-v-b8c8aa9a]{position:absolute}.wscn-http404 .pic-404__child.left[data-v-b8c8aa9a]{width:80px;top:17px;left:220px;opacity:0;-webkit-animation-name:cloudLeft-data-v-b8c8aa9a;animation-name:cloudLeft-data-v-b8c8aa9a;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1s;animation-delay:1s}.wscn-http404 .pic-404__child.mid[data-v-b8c8aa9a]{width:46px;top:10px;left:420px;opacity:0;-webkit-animation-name:cloudMid-data-v-b8c8aa9a;animation-name:cloudMid-data-v-b8c8aa9a;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1.2s;animation-delay:1.2s}.wscn-http404 .pic-404__child.right[data-v-b8c8aa9a]{width:62px;top:100px;left:500px;opacity:0;-webkit-animation-name:cloudRight-data-v-b8c8aa9a;animation-name:cloudRight-data-v-b8c8aa9a;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1s;animation-delay:1s}@-webkit-keyframes cloudLeft-data-v-b8c8aa9a{0%{top:17px;left:220px;opacity:0}20%{top:33px;left:188px;opacity:1}80%{top:81px;left:92px;opacity:1}to{top:97px;left:60px;opacity:0}}@keyframes cloudLeft-data-v-b8c8aa9a{0%{top:17px;left:220px;opacity:0}20%{top:33px;left:188px;opacity:1}80%{top:81px;left:92px;opacity:1}to{top:97px;left:60px;opacity:0}}@-webkit-keyframes cloudMid-data-v-b8c8aa9a{0%{top:10px;left:420px;opacity:0}20%{top:40px;left:360px;opacity:1}70%{top:130px;left:180px;opacity:1}to{top:160px;left:120px;opacity:0}}@keyframes cloudMid-data-v-b8c8aa9a{0%{top:10px;left:420px;opacity:0}20%{top:40px;left:360px;opacity:1}70%{top:130px;left:180px;opacity:1}to{top:160px;left:120px;opacity:0}}@-webkit-keyframes cloudRight-data-v-b8c8aa9a{0%{top:100px;left:500px;opacity:0}20%{top:120px;left:460px;opacity:1}80%{top:180px;left:340px;opacity:1}to{top:200px;left:300px;opacity:0}}@keyframes cloudRight-data-v-b8c8aa9a{0%{top:100px;left:500px;opacity:0}20%{top:120px;left:460px;opacity:1}80%{top:180px;left:340px;opacity:1}to{top:200px;left:300px;opacity:0}}.wscn-http404 .bullshit[data-v-b8c8aa9a]{position:relative;float:left;width:300px;padding:30px 0;overflow:hidden}.wscn-http404 .bullshit__oops[data-v-b8c8aa9a]{font-size:32px;line-height:40px;color:#1482f0;margin-bottom:20px;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__headline[data-v-b8c8aa9a],.wscn-http404 .bullshit__oops[data-v-b8c8aa9a]{font-weight:700;opacity:0;-webkit-animation-name:slideUp-data-v-b8c8aa9a;animation-name:slideUp-data-v-b8c8aa9a;-webkit-animation-duration:.5s;animation-duration:.5s}.wscn-http404 .bullshit__headline[data-v-b8c8aa9a]{font-size:20px;line-height:24px;color:#222;margin-bottom:10px;-webkit-animation-delay:.1s;animation-delay:.1s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__info[data-v-b8c8aa9a]{font-size:13px;line-height:21px;color:grey;margin-bottom:30px;-webkit-animation-delay:.2s;animation-delay:.2s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__info[data-v-b8c8aa9a],.wscn-http404 .bullshit__return-home[data-v-b8c8aa9a]{opacity:0;-webkit-animation-name:slideUp-data-v-b8c8aa9a;animation-name:slideUp-data-v-b8c8aa9a;-webkit-animation-duration:.5s;animation-duration:.5s}.wscn-http404 .bullshit__return-home[data-v-b8c8aa9a]{display:block;float:left;width:110px;height:36px;background:#1482f0;border-radius:100px;text-align:center;color:#fff;font-size:14px;line-height:36px;cursor:pointer;-webkit-animation-delay:.3s;animation-delay:.3s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}@-webkit-keyframes slideUp-data-v-b8c8aa9a{0%{-webkit-transform:translateY(60px);transform:translateY(60px);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}@keyframes slideUp-data-v-b8c8aa9a{0%{-webkit-transform:translateY(60px);transform:translateY(60px);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}
|
1
priv/static/adminfe/static/css/chunk-libs.bd17d456.css
Normal file
1
priv/static/adminfe/static/css/chunk-libs.bd17d456.css
Normal file
|
@ -0,0 +1 @@
|
|||
/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit;font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}[hidden],template{display:none}#nprogress{pointer-events:none}#nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;-webkit-box-shadow:0 0 10px #29d,0 0 5px #29d;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;-webkit-transform:rotate(3deg) translateY(-4px);transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;-webkit-box-sizing:border-box;box-sizing:border-box;border-color:#29d transparent transparent #29d;border-style:solid;border-width:2px;border-radius:50%;-webkit-animation:nprogress-spinner .4s linear infinite;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@-webkit-keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(1turn)}}@keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}
|
BIN
priv/static/adminfe/static/fonts/element-icons.2fad952.woff
Normal file
BIN
priv/static/adminfe/static/fonts/element-icons.2fad952.woff
Normal file
Binary file not shown.
BIN
priv/static/adminfe/static/fonts/element-icons.6f0a763.ttf
Normal file
BIN
priv/static/adminfe/static/fonts/element-icons.6f0a763.ttf
Normal file
Binary file not shown.
BIN
priv/static/adminfe/static/img/401.089007e.gif
Normal file
BIN
priv/static/adminfe/static/img/401.089007e.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 160 KiB |
BIN
priv/static/adminfe/static/img/404.a57b6f3.png
Normal file
BIN
priv/static/adminfe/static/img/404.a57b6f3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 96 KiB |
1
priv/static/adminfe/static/js/7zzA.e1ae1c94.js
Normal file
1
priv/static/adminfe/static/js/7zzA.e1ae1c94.js
Normal file
|
@ -0,0 +1 @@
|
|||
(window.webpackJsonp=window.webpackJsonp||[]).push([["7zzA"],{"7zzA":function(e,r,n){"use strict";n.r(r);var t={beforeCreate:function(){var e=this.$route,r=e.params,n=e.query,t=r.path;this.$router.replace({path:"/"+t,query:n})},render:function(e){return e()}},o=n("KHd+"),u=Object(o.a)(t,void 0,void 0,!1,null,null,null);u.options.__file="index.vue";r.default=u.exports}}]);
|
1
priv/static/adminfe/static/js/JEtC.f9ba4594.js
Normal file
1
priv/static/adminfe/static/js/JEtC.f9ba4594.js
Normal file
|
@ -0,0 +1 @@
|
|||
(window.webpackJsonp=window.webpackJsonp||[]).push([["JEtC"],{JEtC:function(o,n,i){"use strict";i.r(n);var e={name:"AuthRedirect",created:function(){var o=window.location.search.slice(1);window.opener.location.href=window.location.origin+"/login#"+o,window.close()}},t=i("KHd+"),c=Object(t.a)(e,void 0,void 0,!1,null,null,null);c.options.__file="authredirect.vue";n.default=c.exports}}]);
|
1
priv/static/adminfe/static/js/app.25699e3d.js
Normal file
1
priv/static/adminfe/static/js/app.25699e3d.js
Normal file
File diff suppressed because one or more lines are too long
1
priv/static/adminfe/static/js/chunk-18e1.7f9c377c.js
Normal file
1
priv/static/adminfe/static/js/chunk-18e1.7f9c377c.js
Normal file
|
@ -0,0 +1 @@
|
|||
(window.webpackJsonp=window.webpackJsonp||[]).push([["chunk-18e1"],{BF41:function(t,a,i){},"UUO+":function(t,a,i){"use strict";i.r(a);var e=i("zGwZ"),s=i.n(e),r={name:"Page401",data:function(){return{errGif:s.a+"?"+ +new Date,ewizardClap:"https://wpimg.wallstcn.com/007ef517-bafd-4066-aae4-6883632d9646",dialogVisible:!1}},methods:{back:function(){this.$route.query.noGoBack?this.$router.push({path:"/dashboard"}):this.$router.go(-1)}}},n=(i("UrVv"),i("KHd+")),l=Object(n.a)(r,function(){var t=this,a=t.$createElement,i=t._self._c||a;return i("div",{staticClass:"errPage-container"},[i("el-button",{staticClass:"pan-back-btn",attrs:{icon:"arrow-left"},on:{click:t.back}},[t._v("返回")]),t._v(" "),i("el-row",[i("el-col",{attrs:{span:12}},[i("h1",{staticClass:"text-jumbo text-ginormous"},[t._v("Oops!")]),t._v("\n gif来源"),i("a",{attrs:{href:"https://zh.airbnb.com/",target:"_blank"}},[t._v("airbnb")]),t._v(" 页面\n "),i("h2",[t._v("你没有权限去该页面")]),t._v(" "),i("h6",[t._v("如有不满请联系你领导")]),t._v(" "),i("ul",{staticClass:"list-unstyled"},[i("li",[t._v("或者你可以去:")]),t._v(" "),i("li",{staticClass:"link-type"},[i("router-link",{attrs:{to:"/dashboard"}},[t._v("回首页")])],1),t._v(" "),i("li",{staticClass:"link-type"},[i("a",{attrs:{href:"https://www.taobao.com/"}},[t._v("随便看看")])]),t._v(" "),i("li",[i("a",{attrs:{href:"#"},on:{click:function(a){a.preventDefault(),t.dialogVisible=!0}}},[t._v("点我看图")])])])]),t._v(" "),i("el-col",{attrs:{span:12}},[i("img",{attrs:{src:t.errGif,width:"313",height:"428",alt:"Girl has dropped her ice cream."}})])],1),t._v(" "),i("el-dialog",{attrs:{visible:t.dialogVisible,title:"随便看"},on:{"update:visible":function(a){t.dialogVisible=a}}},[i("img",{staticClass:"pan-img",attrs:{src:t.ewizardClap}})])],1)},[],!1,null,"ab9be52c",null);l.options.__file="401.vue";a.default=l.exports},UrVv:function(t,a,i){"use strict";var e=i("BF41");i.n(e).a},zGwZ:function(t,a,i){t.exports=i.p+"static/img/401.089007e.gif"}}]);
|
1
priv/static/adminfe/static/js/chunk-50cf.b9b1df43.js
Normal file
1
priv/static/adminfe/static/js/chunk-50cf.b9b1df43.js
Normal file
File diff suppressed because one or more lines are too long
1
priv/static/adminfe/static/js/chunk-8b70.46525646.js
Normal file
1
priv/static/adminfe/static/js/chunk-8b70.46525646.js
Normal file
|
@ -0,0 +1 @@
|
|||
(window.webpackJsonp=window.webpackJsonp||[]).push([["chunk-8b70"],{K3CD:function(e,t,s){},ZvHC:function(e,t,s){"use strict";var n=s("K3CD");s.n(n).a},c11S:function(e,t,s){"use strict";var n=s("gTgX");s.n(n).a},gTgX:function(e,t,s){},ntYl:function(e,t,s){"use strict";s.r(t);var n=s("J4zp"),o=s.n(n),a=s("XJYT"),r=s("wAo7"),i=s("mSNy"),l={name:"Login",components:{"svg-icon":r.a},data:function(){return{loginForm:{username:"",password:""},passwordType:"password",loading:!1,showDialog:!1,redirect:void 0}},watch:{$route:{handler:function(e){this.redirect=e.query&&e.query.redirect},immediate:!0}},methods:{showPwd:function(){"password"===this.passwordType?this.passwordType="":this.passwordType="password"},handleLogin:function(){var e=this;if(this.loading=!0,this.checkUsername()){var t=this.getLoginData();this.$store.dispatch("LoginByUsername",t).then(function(){e.loading=!1,e.$router.push({path:e.redirect||"/users/index"})}).catch(function(){e.loading=!1})}else Object(a.Message)({message:i.a.t("login.errorMessage"),type:"error",duration:7e3}),this.$store.dispatch("addErrorLog",{message:i.a.t("login.errorMessage")}),this.loading=!1},checkUsername:function(){return this.loginForm.username.includes("@")},getLoginData:function(){var e=this.loginForm.username.split("@"),t=o()(e,2),s=t[0],n=t[1];return{username:s.trim(),authHost:n.trim(),password:this.loginForm.password}}}},c=(s("c11S"),s("ZvHC"),s("KHd+")),p=Object(c.a)(l,function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("div",{staticClass:"login-container"},[s("el-form",{ref:"loginForm",staticClass:"login-form",attrs:{model:e.loginForm,"auto-complete":"on","label-position":"left"}},[s("div",{staticClass:"title-container"},[s("h3",{staticClass:"title"},[e._v("\n "+e._s(e.$t("login.title"))+"\n ")])]),e._v(" "),s("el-form-item",{attrs:{prop:"username"}},[s("span",{staticClass:"svg-container"},[s("svg-icon",{attrs:{"icon-class":"user"}})],1),e._v(" "),s("el-input",{attrs:{placeholder:e.$t("login.username"),name:"username",type:"text","auto-complete":"on"},model:{value:e.loginForm.username,callback:function(t){e.$set(e.loginForm,"username",t)},expression:"loginForm.username"}})],1),e._v(" "),s("el-form-item",{attrs:{prop:"password"}},[s("span",{staticClass:"svg-container"},[s("svg-icon",{attrs:{"icon-class":"password"}})],1),e._v(" "),s("el-input",{attrs:{type:e.passwordType,placeholder:e.$t("login.password"),name:"password","auto-complete":"on"},nativeOn:{keyup:function(t){return!t.type.indexOf("key")&&e._k(t.keyCode,"enter",13,t.key,"Enter")?null:e.handleLogin(t)}},model:{value:e.loginForm.password,callback:function(t){e.$set(e.loginForm,"password",t)},expression:"loginForm.password"}}),e._v(" "),s("span",{staticClass:"show-pwd",on:{click:e.showPwd}},[s("svg-icon",{attrs:{"icon-class":"password"===e.passwordType?"eye":"eye-open"}})],1)],1),e._v(" "),s("el-button",{staticStyle:{width:"100%","margin-bottom":"30px"},attrs:{loading:e.loading,type:"primary"},nativeOn:{click:function(t){return t.preventDefault(),e.handleLogin(t)}}},[e._v("\n "+e._s(e.$t("login.logIn"))+"\n ")])],1)],1)},[],!1,null,"57350b8e",null);p.options.__file="index.vue";t.default=p.exports}}]);
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue