Merge remote-tracking branch 'origin/develop' into shigusegubu

* origin/develop: (208 commits)
  Web.OAuth.OAuthControllerTest: Add test against token formatting
  Web.OAuth.OAuthController: Fix scopes Enum.join for OAuth response
  activitypub: fix date header format
  Added migration for setting default tags in existing users records
  config: fix chat endpoint path
  mastodon websocket: return errors using ok, not stop
  mastodon websocket: bring back infinity timeout
  mastodon websocket: use pattern match to get query data, robustly handle errors
  config: update config for cowboy 2 endpoints
  mastodon api: websocket: update code for cowboy 2.x
  mix: update dependencies for cowboy 2.0
  Renamed *DatabaseAuthenticator to *Authenticator.
  Added `auth_template/0` to DatabaseAuthenticator.
  Made auth customization be runtime-configurable.
  Added deactivated to the user view
  Remove parts of the old activity view.
  Add user muted status info to twitterapi.
  Add user muted status info to MastodonAPI.
  Add `with_muted` param.
  Follower requests: Utilize object index.
  ...
This commit is contained in:
Henry Jameson 2019-03-02 20:20:45 +02:00
commit ae07bb4779
563 changed files with 6342 additions and 2064 deletions

View file

@ -19,7 +19,7 @@
# #
# You can give explicit globs or simply directories. # You can give explicit globs or simply directories.
# In the latter case `**/*.{ex,exs}` will be used. # In the latter case `**/*.{ex,exs}` will be used.
included: ["lib/", "src/", "web/", "apps/"], included: ["lib/", "src/", "web/", "apps/", "test/"],
excluded: [~r"/_build/", ~r"/deps/"] excluded: [~r"/_build/", ~r"/deps/"]
}, },
# #
@ -57,7 +57,7 @@
# For some checks, like AliasUsage, you can only customize the priority # For some checks, like AliasUsage, you can only customize the priority
# Priority values are: `low, normal, high, higher` # Priority values are: `low, normal, high, higher`
{Credo.Check.Design.AliasUsage, priority: :low}, {Credo.Check.Design.AliasUsage, priority: :low, if_called_more_often_than: 3},
# For others you can set parameters # For others you can set parameters
@ -104,7 +104,8 @@
{Credo.Check.Warning.BoolOperationOnSameValues}, {Credo.Check.Warning.BoolOperationOnSameValues},
{Credo.Check.Warning.IExPry}, {Credo.Check.Warning.IExPry},
{Credo.Check.Warning.IoInspect}, {Credo.Check.Warning.IoInspect},
{Credo.Check.Warning.LazyLogging}, # Got too much of them, not sure if relevant
{Credo.Check.Warning.LazyLogging, false},
{Credo.Check.Warning.OperationOnSameValues}, {Credo.Check.Warning.OperationOnSameValues},
{Credo.Check.Warning.OperationWithConstantResult}, {Credo.Check.Warning.OperationWithConstantResult},
{Credo.Check.Warning.UnusedEnumOperation}, {Credo.Check.Warning.UnusedEnumOperation},

View file

@ -1,7 +1,8 @@
image: elixir:1.7.2 image: elixir:1.7.2
services: services:
- postgres:9.6.2 - name: postgres:9.6.2
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
variables: variables:
POSTGRES_DB: pleroma_test POSTGRES_DB: pleroma_test
@ -35,4 +36,4 @@ lint:
unit-testing: unit-testing:
stage: test stage: test
script: script:
- mix test --trace - mix test --trace --preload-modules

View file

@ -8,19 +8,7 @@ Pleroma is written in Elixir, high-performance and can run on small devices like
For clients it supports both the [GNU Social API with Qvitter extensions](https://twitter-api.readthedocs.io/en/latest/index.html) and the [Mastodon client API](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md). For clients it supports both the [GNU Social API with Qvitter extensions](https://twitter-api.readthedocs.io/en/latest/index.html) and the [Mastodon client API](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md).
Client applications that are committed to supporting Pleroma: - [Client Applications for Pleroma](docs/Clients.md)
* Mastalab (Android, Streaming Ready)
* Tusky (Android, No Streaming)
* Twidere (Android, No Streaming)
* Mast (iOS)
* Amaroq (iOS)
Client applications that are known to work well:
* Tootdon (Android + iOS)
* Tootle (iOS)
* Whalebird (Windows + Mac + Linux)
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>. 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>.

View file

@ -93,10 +93,11 @@ config :pleroma, Pleroma.Web.Endpoint,
dispatch: [ dispatch: [
{:_, {:_,
[ [
{"/api/v1/streaming", Elixir.Pleroma.Web.MastodonAPI.WebsocketHandler, []}, {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
{"/socket/websocket", Phoenix.Endpoint.CowboyWebSocket, {"/websocket", Phoenix.Endpoint.CowboyWebSocket,
{nil, {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}}, {Phoenix.Transports.WebSocket,
{:_, Plug.Adapters.Cowboy.Handler, {Pleroma.Web.Endpoint, []}} {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}},
{:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
]} ]}
] ]
], ],
@ -160,7 +161,10 @@ config :pleroma, :instance,
mrf_transparency: true, mrf_transparency: true,
autofollowed_nicknames: [], autofollowed_nicknames: [],
max_pinned_statuses: 1, max_pinned_statuses: 1,
no_attachment_links: false no_attachment_links: false,
welcome_user_nickname: nil,
welcome_message: nil,
max_report_comment_size: 1000
config :pleroma, :markup, config :pleroma, :markup,
# XXX - unfortunately, inline images must be enabled by default right now, because # XXX - unfortunately, inline images must be enabled by default right now, because
@ -226,8 +230,8 @@ config :pleroma, :mrf_rejectnonpublic,
allow_direct: false allow_direct: false
config :pleroma, :mrf_hellthread, config :pleroma, :mrf_hellthread,
delist_threshold: 5, delist_threshold: 10,
reject_threshold: 10 reject_threshold: 20
config :pleroma, :mrf_simple, config :pleroma, :mrf_simple,
media_removal: [], media_removal: [],
@ -236,6 +240,11 @@ config :pleroma, :mrf_simple,
reject: [], reject: [],
accept: [] accept: []
config :pleroma, :mrf_keyword,
reject: [],
federated_timeline_removal: [],
replace: []
config :pleroma, :rich_media, enabled: true config :pleroma, :rich_media, enabled: true
config :pleroma, :media_proxy, config :pleroma, :media_proxy,
@ -323,14 +332,27 @@ config :pleroma, Pleroma.User,
"web" "web"
] ]
config :pleroma, Pleroma.Web.Federator, max_jobs: 50
config :pleroma, Pleroma.Web.Federator.RetryQueue, config :pleroma, Pleroma.Web.Federator.RetryQueue,
enabled: false, enabled: false,
max_jobs: 20, max_jobs: 20,
initial_timeout: 30, initial_timeout: 30,
max_retries: 5 max_retries: 5
config :pleroma, Pleroma.Jobs,
federator_incoming: [max_jobs: 50],
federator_outgoing: [max_jobs: 50],
mailer: [max_jobs: 10]
config :auto_linker,
opts: [
scheme: true,
extra: true,
class: false,
strip_prefix: false,
new_window: false,
rel: false
]
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs" import_config "#{Mix.env()}.exs"

View file

@ -16,7 +16,8 @@ config :pleroma, Pleroma.Web.Endpoint,
debug_errors: true, debug_errors: true,
code_reloader: true, code_reloader: true,
check_origin: false, check_origin: false,
watchers: [] watchers: [],
secure_cookie_flag: false
config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Local config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Local

View file

@ -44,6 +44,8 @@ config :web_push_encryption, :vapid_details,
"BLH1qVhJItRGCfxgTtONfsOKDc9VRAraXw-3NsmjMngWSh7NxOizN6bkuRA7iLTMPS82PjwJAr3UoK9EC1IFrz4", "BLH1qVhJItRGCfxgTtONfsOKDc9VRAraXw-3NsmjMngWSh7NxOizN6bkuRA7iLTMPS82PjwJAr3UoK9EC1IFrz4",
private_key: "_-XZ0iebPrRfZ_o0-IatTdszYa8VCH1yLN-JauK7HHA" private_key: "_-XZ0iebPrRfZ_o0-IatTdszYa8VCH1yLN-JauK7HHA"
config :pleroma, Pleroma.Jobs, testing: [max_jobs: 2]
try do try do
import_config "test.secret.exs" import_config "test.secret.exs"
rescue rescue

View file

@ -1,100 +1,173 @@
# Admin API # Admin API
Authentication is required and the user must be an admin. Authentication is required and the user must be an admin.
## `/api/pleroma/admin/users`
### List users
- Method `GET`
- Response:
```JSON
[
{
"deactivated": bool,
"id": integer,
"nickname": string
},
...
]
```
## `/api/pleroma/admin/user` ## `/api/pleroma/admin/user`
### Remove a user ### Remove a user
* Method `DELETE`
* Params: - Method `DELETE`
* `nickname` - Params:
* Response: Users nickname - `nickname`
- Response: Users nickname
### Create a user ### Create a user
* Method: `POST`
* Params:
* `nickname`
* `email`
* `password`
* Response: Users nickname
## `/api/pleroma/admin/users/tag` - Method: `POST`
### Tag a list of users - Params:
* Method: `PUT` - `nickname`
* Params: - `email`
* `nickname` - `password`
* `tags` - Response: Users nickname
### Untag a list of users
* Method: `DELETE` ## `/api/pleroma/admin/users/:nickname/toggle_activation`
* Params:
* `nickname` ### Toggle user activation
* `tags`
- Method: `PATCH`
- Params:
- `nickname`
- Response: Users object
## `/api/pleroma/admin/permission_group/:nickname`
### Get user user permission groups membership
* Method: `GET`
* Params: none
* Response:
```JSON ```JSON
{ {
"is_moderator": bool, "deactivated": bool,
"is_admin": bool "id": integer,
"nickname": string
}
```
## `/api/pleroma/admin/users/tag`
### Tag a list of users
- Method: `PUT`
- Params:
- `nickname`
- `tags`
### Untag a list of users
- Method: `DELETE`
- Params:
- `nickname`
- `tags`
## `/api/pleroma/admin/permission_group/:nickname`
### Get user user permission groups membership
- Method: `GET`
- Params: none
- Response:
```JSON
{
"is_moderator": bool,
"is_admin": bool
} }
``` ```
## `/api/pleroma/admin/permission_group/:nickname/:permission_group` ## `/api/pleroma/admin/permission_group/:nickname/:permission_group`
Note: Available `:permission_group` is currently moderator and admin. 404 is returned when the permission group doesnt exist. Note: Available `:permission_group` is currently moderator and admin. 404 is returned when the permission group doesnt exist.
### Get user user permission groups membership ### Get user user permission groups membership
* Method: `GET`
* Params: none - Method: `GET`
* Response: - Params: none
- Response:
```JSON ```JSON
{ {
"is_moderator": bool, "is_moderator": bool,
"is_admin": bool "is_admin": bool
} }
``` ```
### Add user in permission group ### Add user in permission group
* Method: `POST`
* Params: none - Method: `POST`
* Response: - Params: none
* On failure: ``{"error": "…"}`` - Response:
* On success: JSON of the ``user.info`` - On failure: `{"error": "…"}`
- On success: JSON of the `user.info`
### Remove user from permission group ### Remove user from permission group
* Method: `DELETE`
* Params: none - Method: `DELETE`
* Response: - Params: none
* On failure: ``{"error": "…"}`` - Response:
* On success: JSON of the ``user.info`` - On failure: `{"error": "…"}`
* Note: An admin cannot revoke their own admin status. - On success: JSON of the `user.info`
- Note: An admin cannot revoke their own admin status.
## `/api/pleroma/admin/activation_status/:nickname`
### Active or deactivate a user
- Method: `PUT`
- Params:
- `nickname`
- `status` BOOLEAN field, false value means deactivation.
## `/api/pleroma/admin/relay` ## `/api/pleroma/admin/relay`
### Follow a Relay ### Follow a Relay
* Methods: `POST`
* Params: - Methods: `POST`
* `relay_url` - Params:
* Response: - `relay_url`
* On success: URL of the followed relay - Response:
- On success: URL of the followed relay
### Unfollow a Relay ### Unfollow a Relay
* Methods: `DELETE`
* Params: - Methods: `DELETE`
* `relay_url` - Params:
* Response: - `relay_url`
* On success: URL of the unfollowed relay - Response:
- On success: URL of the unfollowed relay
## `/api/pleroma/admin/invite_token` ## `/api/pleroma/admin/invite_token`
### Get a account registeration invite token ### Get a account registeration invite token
* Methods: `GET`
* Params: none - Methods: `GET`
* Response: invite token (base64 string) - Params: none
- Response: invite token (base64 string)
## `/api/pleroma/admin/email_invite` ## `/api/pleroma/admin/email_invite`
### Sends registration invite via email ### Sends registration invite via email
* Methods: `POST`
* Params: - Methods: `POST`
* `email` - Params:
* `name`, optionnal - `email`
- `name`, optionnal
## `/api/pleroma/admin/password_reset` ## `/api/pleroma/admin/password_reset`
### Get a password reset token for a given nickname ### Get a password reset token for a given nickname
* Methods: `GET`
* Params: none - Methods: `GET`
* Response: password reset token (base64 string) - Params: none
- Response: password reset token (base64 string)

100
docs/Clients.md Normal file
View file

@ -0,0 +1,100 @@
# Pleroma Clients
Note: Additionnal clients may be working but theses are officially supporting Pleroma.
Feel free to contact us to be added to this list!
## Desktop
### Roma for Desktop
- Homepage: <http://www.pleroma.com/desktop-app/>
- Source Code: ???
- Platforms: Windows, Mac, (Linux?)
- Features: Streaming Ready
### Social
- Source Code: <https://gitlab.gnome.org/BrainBlasted/Social>
- Contact: [@brainblasted@social.libre.fi](https://social.libre.fi/users/brainblasted)
- Platforms: Linux (GNOME)
- Note(2019-01-28): Not at a pre-alpha stage yet
### Whalebird
- Homepage: <https://whalebird.org/>
- Source Code: <https://github.com/h3poteto/whalebird-desktop>
- Contact: [@h3poteto@pleroma.io](https://pleroma.io/users/h3poteto)
- Platforms: Windows, Mac, Linux
- Features: Streaming Ready
## Handheld
### Amaroq
- Homepage: <https://itunes.apple.com/us/app/amaroq-for-mastodon/id1214116200>
- Source Code: <https://github.com/ReticentJohn/Amaroq>
- Contact: [@eurasierboy@mastodon.social](https://mastodon.social/users/eurasierboy)
- Platforms: iOS
- Features: No Streaming
### Nekonium
- Homepage: [F-Droid Repository](https://repo.gdgd.jp.net/), [Google Play](https://play.google.com/store/apps/details?id=com.apps.nekonium), [Amazon](https://www.amazon.co.jp/dp/B076FXPRBC/)
- Source: <https://git.gdgd.jp.net/lin/nekonium/>
- Contact: [@lin@pleroma.gdgd.jp.net](https://pleroma.gdgd.jp.net/users/lin)
- Platforms: Android
- Features: Streaming Ready
### Mastalab
- Source Code: <https://gitlab.com/tom79/mastalab/>
- Contact: [@tom79@mastodon.social](https://mastodon.social/users/tom79)
- Platforms: Android
- Features: Streaming Ready
### Roma
- Homepage: <http://www.pleroma.com/>
- Source Code: ???
- Platforms: iOS, Android
- Features: No Streaming
### Tootdon
- Homepage: <http://tootdon.club/>, <http://blog.mastodon-tootdon.com/>
- Source Code: ???
- Contact: [@tootdon@mstdn.jp](https://mstdn.jp/users/tootdon)
- Platforms: Android, iOS
- Features: No Streaming
### Tusky
- Homepage: <https://tuskyapp.github.io/>
- Source Code: <https://github.com/tuskyapp/Tusky>
- Contact: [@ConnyDuck@mastodon.social](https://mastodon.social/users/ConnyDuck)
- Platforms: Android
- Features: No Streaming
### Twidere
- Homepage: <https://twidere.mariotaku.org/>
- Source Code: <https://github.com/TwidereProject/Twidere-Android/>, <https://github.com/TwidereProject/Twidere-iOS/>
- Contact: <me@mariotaku.org>
- Platform: Android, iOS
- Features: No Streaming
## Alternative Web Interfaces
### Brutaldon
- Homepage: <https://jfm.carcosa.net/projects/software/brutaldon/>
- Source Code: <https://github.com/jfmcbrayer/brutaldon>
- Contact: [@gcupc@glitch.social](https://glitch.social/users/gcupc)
- Features: No Streaming
### Feather
- Source Code: <https://github.com/kaniini/feather>
- Contact: [@kaniini@pleroma.site](https://pleroma.site/kaniini)
- Features: No Streaming
### Halcyon
- Source Code: <https://notabug.org/halcyon-suite/halcyon>
- Contact: [@halcyon@social.csswg.org](https://social.csswg.org/users/halcyon)
- Features: Streaming Ready
### Pinafore
- Homepage: <https://pinafore.social/>
- Source Code: <https://github.com/nolanlawson/pinafore>
- Contact: [@pinafore@mastodon.technology](https://mastodon.technology/users/pinafore)
- Note: Pleroma support is a secondary goal
- Features: No Streaming
### Sengi
- Source Code: <https://github.com/NicolasConstant/sengi>
- Contact: [@sengi_app@mastodon.social](https://mastodon.social/users/sengi_app)
- Note(2019-01-28): The development is currently in a early stage.

View file

@ -0,0 +1,15 @@
# Differences in Mastodon API responses from vanilla Mastodon
A Pleroma instance can be identified by "<Mastodon version> (compatible; Pleroma <version>)" present in `version` field in response from `/api/v1/instance`
## Flake IDs
Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are sortable strings
## Attachment cap
Some apps operate under the assumption that no more than 4 attachments can be returned or uploaded. Pleroma however does not enforce any limits on attachment count neither when returning the status object nor when posting.
## Timelines
Adding the parameter `with_muted=true` to the timeline queries will also return activities by muted (not by blocked!) users.

View file

@ -94,3 +94,17 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
## `/api/pleroma/admin/` ## `/api/pleroma/admin/`
See [Admin-API](Admin-API.md) See [Admin-API](Admin-API.md)
## `/api/v1/pleroma/flavour/:flavour`
* Method `POST`
* Authentication: required
* Response: JSON string. Returns the user flavour or the default one on success, otherwise returns `{"error": "error_msg"}`
* Example response: "glitch"
* Note: This is intended to be used only by mastofe
## `/api/v1/pleroma/flavour`
* Method `GET`
* Authentication: required
* Response: JSON string. Returns the user flavour or the default one.
* Example response: "glitch"
* Note: This is intended to be used only by mastofe

View file

@ -36,14 +36,15 @@ This filter replaces the filename (not the path) of an upload. For complete obfu
An example for Sendgrid adapter: An example for Sendgrid adapter:
``` ```exs
config :pleroma, Pleroma.Mailer, config :pleroma, Pleroma.Mailer,
adapter: Swoosh.Adapters.Sendgrid, adapter: Swoosh.Adapters.Sendgrid,
api_key: "YOUR_API_KEY" api_key: "YOUR_API_KEY"
``` ```
An example for SMTP adapter: An example for SMTP adapter:
```
```exs
config :pleroma, Pleroma.Mailer, config :pleroma, Pleroma.Mailer,
adapter: Swoosh.Adapters.SMTP, adapter: Swoosh.Adapters.SMTP,
relay: "smtp.gmail.com", relay: "smtp.gmail.com",
@ -97,13 +98,16 @@ config :pleroma, Pleroma.Mailer,
* `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature. * `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature.
* `autofollowed_nicknames`: Set to nicknames of (local) users that every new user should automatically follow. * `autofollowed_nicknames`: Set to nicknames of (local) users that every new user should automatically follow.
* `no_attachment_links`: Set to true to disable automatically adding attachment link text to statuses * `no_attachment_links`: Set to true to disable automatically adding attachment link text to statuses
* `welcome_message`: A message that will be send to a newly registered users as a direct message.
* `welcome_user_nickname`: The nickname of the local user that sends the welcome message.
* `max_report_size`: The maximum size of the report comment (Default: `1000`)
## :logger ## :logger
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog * `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog
An example to enable ONLY ExSyslogger (f/ex in ``prod.secret.exs``) with info and debug suppressed: An example to enable ONLY ExSyslogger (f/ex in ``prod.secret.exs``) with info and debug suppressed:
``` ```
config :logger, config :logger,
backends: [{ExSyslogger, :ex_syslogger}] backends: [{ExSyslogger, :ex_syslogger}]
config :logger, :ex_syslogger, config :logger, :ex_syslogger,
@ -171,6 +175,11 @@ This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:i
* `delist_threshold`: Number of mentioned users after which the message gets delisted (the message can still be seen, but it will not show up in public timelines and mentioned users won't get notifications about it). Set to 0 to disable. * `delist_threshold`: Number of mentioned users after which the message gets delisted (the message can still be seen, but it will not show up in public timelines and mentioned users won't get notifications about it). Set to 0 to disable.
* `reject_threshold`: Number of mentioned users after which the messaged gets rejected. Set to 0 to disable. * `reject_threshold`: Number of mentioned users after which the messaged gets rejected. Set to 0 to disable.
## :mrf_keyword
* `reject`: A list of patterns which result in message being rejected, each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)
* `federated_timeline_removal`: A list of patterns which result in message being removed from federated timelines (a.k.a unlisted), each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)
* `replace`: A list of tuples containing `{pattern, replacement}`, `pattern` can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)
## :media_proxy ## :media_proxy
* `enabled`: Enables proxying of remote media to the instances proxy * `enabled`: Enables proxying of remote media to the instances proxy
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts. * `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.
@ -202,7 +211,7 @@ their ActivityPub ID.
An example: An example:
``` ```exs
config :pleroma, :mrf_user_allowlist, config :pleroma, :mrf_user_allowlist,
"example.org": ["https://example.org/users/admin"] "example.org": ["https://example.org/users/admin"]
``` ```
@ -231,18 +240,34 @@ the source code is here: https://github.com/koto-bank/kocaptcha. The default end
Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the 'admin_token' parameter. Example: Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the 'admin_token' parameter. Example:
``` ```exs
config :pleroma, :admin_token, "somerandomtoken" config :pleroma, :admin_token, "somerandomtoken"
``` ```
You can then do You can then do
```
```sh
curl "http://localhost:4000/api/pleroma/admin/invite_token?admin_token=somerandomtoken" curl "http://localhost:4000/api/pleroma/admin/invite_token?admin_token=somerandomtoken"
``` ```
## Pleroma.Web.Federator ## Pleroma.Jobs
A list of job queues and their settings.
Job queue settings:
* `max_jobs`: The maximum amount of parallel jobs running at the same time.
Example:
```exs
config :pleroma, Pleroma.Jobs,
federator_incoming: [max_jobs: 50],
federator_outgoing: [max_jobs: 50]
```
This config contains two queues: `federator_incoming` and `federator_outgoing`. Both have the `max_jobs` set to `50`.
* `max_jobs`: The maximum amount of parallel federation jobs running at the same time.
## Pleroma.Web.Federator.RetryQueue ## Pleroma.Web.Federator.RetryQueue
@ -276,3 +301,28 @@ For each pool, the options are:
* `max_connections` - how much connections a pool can hold * `max_connections` - how much connections a pool can hold
* `timeout` - retention duration for connections * `timeout` - retention duration for connections
## :auto_linker
Configuration for the `auto_linker` library:
* `class: "auto-linker"` - specify the class to be added to the generated link. false to clear
* `rel: "noopener noreferrer"` - override the rel attribute. false to clear
* `new_window: true` - set to false to remove `target='_blank'` attribute
* `scheme: false` - Set to true to link urls with schema `http://google.com`
* `truncate: false` - Set to a number to truncate urls longer then the number. Truncated urls will end in `..`
* `strip_prefix: true` - Strip the scheme prefix
* `extra: false` - link urls with rarely used schemes (magnet, ipfs, irc, etc.)
Example:
```exs
config :auto_linker,
opts: [
scheme: true,
extra: true,
class: false,
strip_prefix: false,
new_window: false,
rel: false
]
```

View file

@ -23,6 +23,11 @@ example.tld {
# If you do not want to use the mediaproxy function, remove these lines. # If you do not want to use the mediaproxy function, remove these lines.
# To use this directive, you need the http.cache plugin for Caddy. # To use this directive, you need the http.cache plugin for Caddy.
cache {
match_path /media
default_max_age 720m
}
cache { cache {
match_path /proxy match_path /proxy
default_max_age 720m default_max_age 720m

View file

@ -1,7 +1,7 @@
#!/sbin/openrc-run #!/sbin/openrc-run
# Requires OpenRC >= 0.35 # Requires OpenRC >= 0.35
directory=~pleroma/pleroma directory=/opt/pleroma
command=/usr/bin/mix command=/usr/bin/mix
command_args="phx.server" command_args="phx.server"
@ -18,4 +18,4 @@ pidfile="/var/run/pleroma.pid"
depend() { depend() {
need nginx postgresql need nginx postgresql
} }

View file

@ -1,6 +1,7 @@
# default Apache site config for Pleroma # default Apache site config for Pleroma
# #
# needed modules: define headers proxy proxy_http proxy_wstunnel rewrite ssl # needed modules: define headers proxy proxy_http proxy_wstunnel rewrite ssl
# optional modules: cache cache_disk
# #
# Simple installation instructions: # Simple installation instructions:
# 1. Install your TLS certificate, possibly using Let's Encrypt. # 1. Install your TLS certificate, possibly using Let's Encrypt.
@ -8,6 +9,14 @@
# 3. This assumes a Debian style Apache config. Copy this file to # 3. This assumes a Debian style Apache config. Copy this file to
# /etc/apache2/sites-available/ and then add a symlink to it in # /etc/apache2/sites-available/ and then add a symlink to it in
# /etc/apache2/sites-enabled/ by running 'a2ensite pleroma-apache.conf', then restart Apache. # /etc/apache2/sites-enabled/ by running 'a2ensite pleroma-apache.conf', then restart Apache.
#
# Optional: enable disk-based caching for the media proxy
# For details, see https://git.pleroma.social/pleroma/pleroma/wikis/How%20to%20activate%20mediaproxy
#
# 1. Create the directory listed below as the CacheRoot, and make sure
# the Apache user can write to it.
# 2. Configure Apache's htcacheclean to clean the directory periodically.
# 3. Run 'a2enmod cache cache_disk' and restart Apache.
Define servername example.tld Define servername example.tld
@ -34,6 +43,15 @@ CustomLog ${APACHE_LOG_DIR}/access.log combined
SSLCompression off SSLCompression off
SSLSessionTickets off SSLSessionTickets off
# uncomment the following to enable mediaproxy caching on disk
# <IfModule mod_cache_disk.c>
# CacheRoot /var/cache/apache2/mod_cache_disk
# CacheDirLevels 1
# CacheDirLength 2
# CacheEnable disk /proxy
# CacheLock on
# </IfModule>
RewriteEngine On RewriteEngine On
RewriteCond %{HTTP:Connection} Upgrade [NC] RewriteCond %{HTTP:Connection} Upgrade [NC]
RewriteCond %{HTTP:Upgrade} websocket [NC] RewriteCond %{HTTP:Upgrade} websocket [NC]

View file

@ -11,16 +11,19 @@ proxy_cache_path /tmp/pleroma-media-cache levels=1:2 keys_zone=pleroma_media_cac
server { server {
server_name example.tld; server_name example.tld;
listen 80; listen 80;
listen [::]:80;
return 301 https://$server_name$request_uri; return 301 https://$server_name$request_uri;
# Uncomment this if you need to use the 'webroot' method with certbot. Make sure # Uncomment this if you need to use the 'webroot' method with certbot. Make sure
# that you also create the .well-known/acme-challenge directory structure in pleroma/priv/static and # that the directory exists and that it is accessible by the webserver. If you followed
# that is is accessible by the webserver. You may need to load this file with the ssl # the guide, you already ran 'sudo mkdir -p /var/lib/letsencrypt' to create the folder.
# server block commented out, run certbot to get the certificate, and then uncomment it. # You may need to load this file with the ssl server block commented out, run certbot
# to get the certificate, and then uncomment it.
# #
# location ~ /\.well-known/acme-challenge { # location ~ /\.well-known/acme-challenge {
# root <path to install>/pleroma/priv/static/; # root /var/lib/letsencrypt/.well-known/acme-challenge;
# } # }
} }
@ -28,7 +31,10 @@ server {
ssl_session_cache shared:ssl_session_cache:10m; ssl_session_cache shared:ssl_session_cache:10m;
server { server {
server_name example.tld;
listen 443 ssl http2; listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_session_timeout 5m; ssl_session_timeout 5m;
ssl_trusted_certificate /etc/letsencrypt/live/example.tld/fullchain.pem; ssl_trusted_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
@ -47,8 +53,6 @@ server {
ssl_stapling on; ssl_stapling on;
ssl_stapling_verify on; ssl_stapling_verify on;
server_name example.tld;
gzip_vary on; gzip_vary on;
gzip_proxied any; gzip_proxied any;
gzip_comp_level 6; gzip_comp_level 6;

View file

@ -15,15 +15,17 @@ Environment="MIX_ENV=prod"
; Make sure that all paths fit your installation. ; Make sure that all paths fit your installation.
; Path to the home directory of the user running the Pleroma service. ; Path to the home directory of the user running the Pleroma service.
Environment="HOME=/home/pleroma" Environment="HOME=/var/lib/pleroma"
; Path to the folder containing the Pleroma installation. ; Path to the folder containing the Pleroma installation.
WorkingDirectory=/home/pleroma/pleroma WorkingDirectory=/opt/pleroma
; Path to the Mix binary. ; Path to the Mix binary.
ExecStart=/usr/bin/mix phx.server ExecStart=/usr/bin/mix phx.server
; Some security directives. ; Some security directives.
; Use private /tmp and /var/tmp folders inside a new file system namespace, which are discarded after the process stops. ; Use private /tmp and /var/tmp folders inside a new file system namespace, which are discarded after the process stops.
PrivateTmp=true PrivateTmp=true
; The /home, /root, and /run/user folders can not be accessed by this service anymore. If your Pleroma user has its home folder in one of the restricted places, or use one of these folders as its working directory, you have to set this to false.
ProtectHome=true
; Mount /usr, /boot, and /etc as read-only for processes invoked by this service. ; Mount /usr, /boot, and /etc as read-only for processes invoked by this service.
ProtectSystem=full ProtectSystem=full
; Sets up a new /dev mount for the process and only adds API pseudo devices like /dev/null, /dev/zero or /dev/random but not physical devices. Disabled by default because it may not work on devices like the Raspberry Pi. ; Sets up a new /dev mount for the process and only adds API pseudo devices like /dev/null, /dev/zero or /dev/random but not physical devices. Disabled by default because it may not work on devices like the Raspberry Pi.

View file

@ -4,7 +4,8 @@
defmodule Mix.Tasks.Pleroma.Uploads do defmodule Mix.Tasks.Pleroma.Uploads do
use Mix.Task use Mix.Task
alias Pleroma.{Upload, Uploaders.Local} alias Pleroma.Upload
alias Pleroma.Uploaders.Local
alias Mix.Tasks.Pleroma.Common alias Mix.Tasks.Pleroma.Common
require Logger require Logger
@ -20,7 +21,7 @@ defmodule Mix.Tasks.Pleroma.Uploads do
- `--delete` - delete local uploads after migrating them to the target uploader - `--delete` - delete local uploads after migrating them to the target uploader
A list of avalible uploaders can be seen in config.exs A list of available uploaders can be seen in config.exs
""" """
def run(["migrate_local", target_uploader | args]) do def run(["migrate_local", target_uploader | args]) do
delete? = Enum.member?(args, "--delete") delete? = Enum.member?(args, "--delete")
@ -96,6 +97,7 @@ defmodule Mix.Tasks.Pleroma.Uploads do
timeout: 150_000 timeout: 150_000
) )
|> Stream.chunk_every(@log_every) |> Stream.chunk_every(@log_every)
# credo:disable-for-next-line Credo.Check.Warning.UnusedEnumOperation
|> Enum.reduce(0, fn done, count -> |> Enum.reduce(0, fn done, count ->
count = count + length(done) count = count + length(done)
Mix.shell().info("Uploaded #{count}/#{total_count} files") Mix.shell().info("Uploaded #{count}/#{total_count} files")

View file

@ -5,7 +5,8 @@
defmodule Mix.Tasks.Pleroma.User do defmodule Mix.Tasks.Pleroma.User do
use Mix.Task use Mix.Task
import Ecto.Changeset import Ecto.Changeset
alias Pleroma.{Repo, User} alias Pleroma.Repo
alias Pleroma.User
alias Mix.Tasks.Pleroma.Common alias Mix.Tasks.Pleroma.Common
@shortdoc "Manages Pleroma users" @shortdoc "Manages Pleroma users"
@ -211,7 +212,7 @@ defmodule Mix.Tasks.Pleroma.User do
user = Repo.get(User, user.id) user = Repo.get(User, user.id)
if length(user.following) == 0 do if Enum.empty?(user.following) do
Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}") Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}")
end end
else else

View file

@ -7,7 +7,9 @@ defmodule Pleroma.PasswordResetToken do
import Ecto.Changeset import Ecto.Changeset
alias Pleroma.{User, PasswordResetToken, Repo} alias Pleroma.User
alias Pleroma.Repo
alias Pleroma.PasswordResetToken
schema "password_reset_tokens" do schema "password_reset_tokens" do
belongs_to(:user, User, type: Pleroma.FlakeId) belongs_to(:user, User, type: Pleroma.FlakeId)

View file

@ -4,7 +4,11 @@
defmodule Pleroma.Activity do defmodule Pleroma.Activity do
use Ecto.Schema use Ecto.Schema
alias Pleroma.{Repo, Activity, Notification}
alias Pleroma.Repo
alias Pleroma.Activity
alias Pleroma.Notification
import Ecto.Query import Ecto.Query
@type t :: %__MODULE__{} @type t :: %__MODULE__{}
@ -109,4 +113,14 @@ defmodule Pleroma.Activity do
end end
def mastodon_notification_type(%Activity{}), do: nil def mastodon_notification_type(%Activity{}), do: nil
def all_by_actor_and_id(actor, status_ids \\ [])
def all_by_actor_and_id(_actor, []), do: []
def all_by_actor_and_id(actor, status_ids) do
Activity
|> where([s], s.id in ^status_ids)
|> where([s], s.actor == ^actor)
|> Repo.all()
end
end end

View file

@ -108,9 +108,10 @@ defmodule Pleroma.Application do
hackney_pool_children() ++ hackney_pool_children() ++
[ [
worker(Pleroma.Web.Federator.RetryQueue, []), worker(Pleroma.Web.Federator.RetryQueue, []),
worker(Pleroma.Web.Federator, []),
worker(Pleroma.Stats, []), worker(Pleroma.Stats, []),
worker(Pleroma.Web.Push, []) worker(Pleroma.Web.Push, []),
worker(Pleroma.Jobs, []),
worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary)
] ++ ] ++
streamer_child() ++ streamer_child() ++
chat_child() ++ chat_child() ++

View file

@ -3,9 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Captcha do defmodule Pleroma.Captcha do
alias Calendar.DateTime
alias Plug.Crypto.KeyGenerator alias Plug.Crypto.KeyGenerator
alias Plug.Crypto.MessageEncryptor alias Plug.Crypto.MessageEncryptor
alias Calendar.DateTime
use GenServer use GenServer

View file

@ -0,0 +1,63 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.AdminEmail do
@moduledoc "Admin emails"
import Swoosh.Email
alias Pleroma.Web.Router.Helpers
defp instance_config, do: Pleroma.Config.get(:instance)
defp instance_name, do: instance_config()[:name]
defp instance_email, do: instance_config()[:email]
defp user_url(user) do
Helpers.o_status_url(Pleroma.Web.Endpoint, :feed_redirect, user.nickname)
end
def report(to, reporter, account, statuses, comment) do
comment_html =
if comment do
"<p>Comment: #{comment}"
else
""
end
statuses_html =
if length(statuses) > 0 do
statuses_list_html =
statuses
|> Enum.map(fn %{id: id} ->
status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, id)
"<li><a href=\"#{status_url}\">#{status_url}</li>"
end)
|> Enum.join("\n")
"""
<p> Statuses:
<ul>
#{statuses_list_html}
</ul>
</p>
"""
else
""
end
html_body = """
<p>Reported by: <a href="#{user_url(reporter)}">#{reporter.nickname}</a></p>
<p>Reported Account: <a href="#{user_url(account)}">#{account.nickname}</a></p>
#{comment_html}
#{statuses_html}
"""
new()
|> to({to.name, to.email})
|> from({instance_name(), instance_email()})
|> reply_to({reporter.name, reporter.email})
|> subject("#{instance_name()} Report")
|> html_body(html_body)
end
end

View file

@ -4,4 +4,10 @@
defmodule Pleroma.Mailer do defmodule Pleroma.Mailer do
use Swoosh.Mailer, otp_app: :pleroma use Swoosh.Mailer, otp_app: :pleroma
def deliver_async(email, config \\ []) do
Pleroma.Jobs.enqueue(:mailer, __MODULE__, [:deliver_async, email, config])
end
def perform(:deliver_async, email, config), do: deliver(email, config)
end end

View file

@ -7,7 +7,8 @@ defmodule Pleroma.UserEmail do
import Swoosh.Email import Swoosh.Email
alias Pleroma.Web.{Endpoint, Router} alias Pleroma.Web.Endpoint
alias Pleroma.Web.Router
defp instance_config, do: Pleroma.Config.get(:instance) defp instance_config, do: Pleroma.Config.get(:instance)

View file

@ -4,8 +4,12 @@
defmodule Pleroma.Filter do defmodule Pleroma.Filter do
use Ecto.Schema use Ecto.Schema
import Ecto.{Changeset, Query}
alias Pleroma.{User, Repo} import Ecto.Changeset
import Ecto.Query
alias Pleroma.User
alias Pleroma.Repo
schema "filters" do schema "filters" do
belongs_to(:user, User, type: Pleroma.FlakeId) belongs_to(:user, User, type: Pleroma.FlakeId)

View file

@ -27,7 +27,7 @@ defmodule Pleroma.FlakeId do
Kernel.to_string(id) Kernel.to_string(id)
end end
def to_string(flake = <<_::integer-size(64), _::integer-size(48), _::integer-size(16)>>) do def to_string(<<_::integer-size(64), _::integer-size(48), _::integer-size(16)>> = flake) do
encode_base62(flake) encode_base62(flake)
end end
@ -42,7 +42,7 @@ defmodule Pleroma.FlakeId do
def from_string(unquote(Kernel.to_string(i))), do: <<0::integer-size(128)>> def from_string(unquote(Kernel.to_string(i))), do: <<0::integer-size(128)>>
end end
def from_string(flake = <<_::integer-size(128)>>), do: flake def from_string(<<_::integer-size(128)>> = flake), do: flake
def from_string(string) when is_binary(string) and byte_size(string) < 18 do def from_string(string) when is_binary(string) and byte_size(string) < 18 do
case Integer.parse(string) do case Integer.parse(string) do

View file

@ -3,38 +3,56 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Formatter do defmodule Pleroma.Formatter do
alias Pleroma.Emoji
alias Pleroma.HTML
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
alias Pleroma.HTML
alias Pleroma.Emoji
@tag_regex ~r/((?<=[^&])|\A)(\#)(\w+)/u
@markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/ @markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/
@link_regex ~r{((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+}ui
# Modified from https://www.w3.org/TR/html5/forms.html#valid-e-mail-address @auto_linker_config hashtag: true,
@mentions_regex ~r/@[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]*@?[a-zA-Z0-9_-](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*/u hashtag_handler: &Pleroma.Formatter.hashtag_handler/4,
mention: true,
mention_handler: &Pleroma.Formatter.mention_handler/4
def parse_tags(text, data \\ %{}) do def mention_handler("@" <> nickname, buffer, opts, acc) do
Regex.scan(@tag_regex, text) case User.get_cached_by_nickname(nickname) do
|> Enum.map(fn ["#" <> tag = full_tag | _] -> {full_tag, String.downcase(tag)} end) %User{id: id} = user ->
|> (fn map -> ap_id = get_ap_id(user)
if data["sensitive"] in [true, "True", "true", "1"], nickname_text = get_nickname_text(nickname, opts) |> maybe_escape(opts)
do: [{"#nsfw", "nsfw"}] ++ map,
else: map link =
end).() "<span class='h-card'><a data-user='#{id}' class='u-url mention' href='#{ap_id}'>@<span>#{
nickname_text
}</span></a></span>"
{link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}}
_ ->
{buffer, acc}
end
end end
@doc "Parses mentions text and returns list {nickname, user}." def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do
@spec parse_mentions(binary()) :: list({binary(), User.t()}) tag = String.downcase(tag)
def parse_mentions(text) do url = "#{Pleroma.Web.base_url()}/tag/#{tag}"
Regex.scan(@mentions_regex, text) link = "<a class='hashtag' data-tag='#{tag}' href='#{url}' rel='tag'>#{tag_text}</a>"
|> List.flatten()
|> Enum.uniq() {link, %{acc | tags: MapSet.put(acc.tags, {tag_text, tag})}}
|> Enum.map(fn nickname -> end
with nickname <- String.trim_leading(nickname, "@"),
do: {"@" <> nickname, User.get_cached_by_nickname(nickname)} @doc """
end) Parses a text and replace plain text links with HTML. Returns a tuple with a result text, mentions, and hashtags.
|> Enum.filter(fn {_match, user} -> user end) """
@spec linkify(String.t(), keyword()) ::
{String.t(), [{String.t(), User.t()}], [{String.t(), String.t()}]}
def linkify(text, options \\ []) do
options = options ++ @auto_linker_config
acc = %{mentions: MapSet.new(), tags: MapSet.new()}
{text, %{mentions: mentions, tags: tags}} = AutoLinker.link_map(text, acc, options)
{text, MapSet.to_list(mentions), MapSet.to_list(tags)}
end end
def emojify(text) do def emojify(text) do
@ -48,9 +66,7 @@ defmodule Pleroma.Formatter do
emoji = HTML.strip_tags(emoji) emoji = HTML.strip_tags(emoji)
file = HTML.strip_tags(file) file = HTML.strip_tags(file)
String.replace( html =
text,
":#{emoji}:",
if not strip do if not strip do
"<img height='32px' width='32px' alt='#{emoji}' title='#{emoji}' src='#{ "<img height='32px' width='32px' alt='#{emoji}' title='#{emoji}' src='#{
MediaProxy.url(file) MediaProxy.url(file)
@ -58,8 +74,8 @@ defmodule Pleroma.Formatter do
else else
"" ""
end end
)
|> HTML.filter_tags() String.replace(text, ":#{emoji}:", html) |> HTML.filter_tags()
end) end)
end end
@ -75,12 +91,10 @@ defmodule Pleroma.Formatter do
def get_emoji(_), do: [] def get_emoji(_), do: []
@link_regex ~r/[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+/ui def html_escape({text, mentions, hashtags}, type) do
{html_escape(text, type), mentions, hashtags}
end
@uri_schemes Application.get_env(:pleroma, :uri_schemes, [])
@valid_schemes Keyword.get(@uri_schemes, :valid_schemes, [])
# TODO: make it use something other than @link_regex
def html_escape(text, "text/html") do def html_escape(text, "text/html") do
HTML.filter_tags(text) HTML.filter_tags(text)
end end
@ -94,112 +108,6 @@ defmodule Pleroma.Formatter do
|> Enum.join("") |> Enum.join("")
end end
@doc """
Escapes a special characters in mention names.
"""
@spec mentions_escape(String.t(), list({String.t(), any()})) :: String.t()
def mentions_escape(text, mentions) do
mentions
|> Enum.reduce(text, fn {name, _}, acc ->
escape_name = String.replace(name, @markdown_characters_regex, "\\\\\\1")
String.replace(acc, name, escape_name)
end)
end
@doc "changes scheme:... urls to html links"
def add_links({subs, text}) do
links =
text
|> String.split([" ", "\t", "<br>"])
|> Enum.filter(fn word -> String.starts_with?(word, @valid_schemes) end)
|> Enum.filter(fn word -> Regex.match?(@link_regex, word) end)
|> Enum.map(fn url -> {Ecto.UUID.generate(), url} end)
|> Enum.sort_by(fn {_, url} -> -String.length(url) end)
uuid_text =
links
|> Enum.reduce(text, fn {uuid, url}, acc -> String.replace(acc, url, uuid) end)
subs =
subs ++
Enum.map(links, fn {uuid, url} ->
{uuid, "<a href=\"#{url}\">#{url}</a>"}
end)
{subs, uuid_text}
end
@doc "Adds the links to mentioned users"
def add_user_links({subs, text}, mentions, options \\ []) do
mentions =
mentions
|> Enum.sort_by(fn {name, _} -> -String.length(name) end)
|> Enum.map(fn {name, user} -> {name, user, Ecto.UUID.generate()} end)
uuid_text =
mentions
|> Enum.reduce(text, fn {match, _user, uuid}, text ->
String.replace(text, match, uuid)
end)
subs =
subs ++
Enum.map(mentions, fn {match, %User{id: id, ap_id: ap_id, info: info}, uuid} ->
ap_id =
if is_binary(info.source_data["url"]) do
info.source_data["url"]
else
ap_id
end
nickname =
if options[:format] == :full do
User.full_nickname(match)
else
User.local_nickname(match)
end
{uuid,
"<span class='h-card'><a data-user='#{id}' class='u-url mention' href='#{ap_id}'>" <>
"@<span>#{nickname}</span></a></span>"}
end)
{subs, uuid_text}
end
@doc "Adds the hashtag links"
def add_hashtag_links({subs, text}, tags) do
tags =
tags
|> Enum.sort_by(fn {name, _} -> -String.length(name) end)
|> Enum.map(fn {name, short} -> {name, short, Ecto.UUID.generate()} end)
uuid_text =
tags
|> Enum.reduce(text, fn {match, _short, uuid}, text ->
String.replace(text, ~r/((?<=[^&])|(\A))#{match}/, uuid)
end)
subs =
subs ++
Enum.map(tags, fn {tag_text, tag, uuid} ->
url =
"<a class='hashtag' data-tag='#{tag}' href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>#{
tag_text
}</a>"
{uuid, url}
end)
{subs, uuid_text}
end
def finalize({subs, text}) do
Enum.reduce(subs, text, fn {uuid, replacement}, result_text ->
String.replace(result_text, uuid, replacement)
end)
end
def truncate(text, max_length \\ 200, omission \\ "...") do def truncate(text, max_length \\ 200, omission \\ "...") do
# Remove trailing whitespace # Remove trailing whitespace
text = Regex.replace(~r/([^ \t\r\n])([ \t]+$)/u, text, "\\g{1}") text = Regex.replace(~r/([^ \t\r\n])([ \t]+$)/u, text, "\\g{1}")
@ -211,4 +119,16 @@ defmodule Pleroma.Formatter do
String.slice(text, 0, length_with_omission) <> omission String.slice(text, 0, length_with_omission) <> omission
end end
end end
defp get_ap_id(%User{info: %{source_data: %{"url" => url}}}) when is_binary(url), do: url
defp get_ap_id(%User{ap_id: ap_id}), do: ap_id
defp get_nickname_text(nickname, %{mentions_format: :full}), do: User.full_nickname(nickname)
defp get_nickname_text(nickname, _), do: User.local_nickname(nickname)
defp maybe_escape(str, %{mentions_escape: true}) do
String.replace(str, @markdown_characters_regex, "\\\\\\1")
end
defp maybe_escape(str, _), do: str
end end

View file

@ -37,17 +37,18 @@ end
defmodule Pleroma.Gopher.Server.ProtocolHandler do defmodule Pleroma.Gopher.Server.ProtocolHandler do
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.User alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Repo
alias Pleroma.HTML alias Pleroma.HTML
alias Pleroma.User
alias Pleroma.Repo
def start_link(ref, socket, transport, opts) do def start_link(ref, socket, transport, opts) do
pid = spawn_link(__MODULE__, :init, [ref, socket, transport, opts]) pid = spawn_link(__MODULE__, :init, [ref, socket, transport, opts])
{:ok, pid} {:ok, pid}
end end
def init(ref, socket, transport, _Opts = []) do def init(ref, socket, transport, [] = _Opts) do
:ok = :ranch.accept_ack(ref) :ok = :ranch.accept_ack(ref)
loop(socket, transport) loop(socket, transport)
end end
@ -110,7 +111,7 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
def response("/notices/" <> id) do def response("/notices/" <> id) do
with %Activity{} = activity <- Repo.get(Activity, id), with %Activity{} = activity <- Repo.get(Activity, id),
true <- ActivityPub.is_public?(activity) do true <- Visibility.is_public?(activity) do
activities = activities =
ActivityPub.fetch_activities_for_context(activity.data["context"]) ActivityPub.fetch_activities_for_context(activity.data["context"])
|> render_activities |> render_activities

View file

@ -83,8 +83,7 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
""" """
@markup Application.get_env(:pleroma, :markup) @markup Application.get_env(:pleroma, :markup)
@uri_schemes Application.get_env(:pleroma, :uri_schemes, []) @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
@valid_schemes Keyword.get(@uri_schemes, :valid_schemes, [])
require HtmlSanitizeEx.Scrubber.Meta require HtmlSanitizeEx.Scrubber.Meta
alias HtmlSanitizeEx.Scrubber.Meta alias HtmlSanitizeEx.Scrubber.Meta
@ -126,10 +125,11 @@ defmodule Pleroma.HTML.Scrubber.Default do
require HtmlSanitizeEx.Scrubber.Meta require HtmlSanitizeEx.Scrubber.Meta
alias HtmlSanitizeEx.Scrubber.Meta alias HtmlSanitizeEx.Scrubber.Meta
# credo:disable-for-previous-line
# No idea how to fix this one…
@markup Application.get_env(:pleroma, :markup) @markup Application.get_env(:pleroma, :markup)
@uri_schemes Application.get_env(:pleroma, :uri_schemes, []) @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
@valid_schemes Keyword.get(@uri_schemes, :valid_schemes, [])
Meta.remove_cdata_sections_before_scrub() Meta.remove_cdata_sections_before_scrub()
Meta.strip_comments() Meta.strip_comments()

View file

@ -2,13 +2,13 @@ defmodule Pleroma.Instances.Instance do
@moduledoc "Instance." @moduledoc "Instance."
alias Pleroma.Instances alias Pleroma.Instances
alias Pleroma.Repo
alias Pleroma.Instances.Instance alias Pleroma.Instances.Instance
use Ecto.Schema use Ecto.Schema
import Ecto.{Query, Changeset} import Ecto.Query
import Ecto.Changeset
alias Pleroma.Repo
schema "instances" do schema "instances" do
field(:host, :string) field(:host, :string)

152
lib/pleroma/jobs.ex Normal file
View 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.Jobs do
@moduledoc """
A basic job queue
"""
use GenServer
require Logger
def init(args) do
{:ok, args}
end
def start_link do
queues =
Pleroma.Config.get(Pleroma.Jobs)
|> Enum.map(fn {name, _} -> create_queue(name) end)
|> Enum.into(%{})
state = %{
queues: queues,
refs: %{}
}
GenServer.start_link(__MODULE__, state, name: __MODULE__)
end
def create_queue(name) do
{name, {:sets.new(), []}}
end
@doc """
Enqueues a job.
Returns `:ok`.
## Arguments
- `queue_name` - a queue name(must be specified in the config).
- `mod` - a worker module (must have `perform` function).
- `args` - a list of arguments for the `perform` function of the worker module.
- `priority` - a job priority (`0` by default).
## Examples
Enqueue `Module.perform/0` with `priority=1`:
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [])
:ok
Enqueue `Module.perform(:job_name)` with `priority=5`:
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [:job_name], 5)
:ok
Enqueue `Module.perform(:another_job, data)` with `priority=1`:
iex> data = "foobar"
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [:another_job, data])
:ok
Enqueue `Module.perform(:foobar_job, :foo, :bar, 42)` with `priority=1`:
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [:foobar_job, :foo, :bar, 42])
:ok
"""
def enqueue(queue_name, mod, args, priority \\ 1)
if Mix.env() == :test do
def enqueue(_queue_name, mod, args, _priority) do
apply(mod, :perform, args)
end
else
@spec enqueue(atom(), atom(), [any()], integer()) :: :ok
def enqueue(queue_name, mod, args, priority) do
GenServer.cast(__MODULE__, {:enqueue, queue_name, mod, args, priority})
end
end
def handle_cast({:enqueue, queue_name, mod, args, priority}, state) do
{running_jobs, queue} = state[:queues][queue_name]
queue = enqueue_sorted(queue, {mod, args}, priority)
state =
state
|> update_queue(queue_name, {running_jobs, queue})
|> maybe_start_job(queue_name, running_jobs, queue)
{:noreply, state}
end
def handle_info({:DOWN, ref, :process, _pid, _reason}, state) do
queue_name = state.refs[ref]
{running_jobs, queue} = state[:queues][queue_name]
running_jobs = :sets.del_element(ref, running_jobs)
state =
state
|> remove_ref(ref)
|> update_queue(queue_name, {running_jobs, queue})
|> maybe_start_job(queue_name, running_jobs, queue)
{:noreply, state}
end
def maybe_start_job(state, queue_name, running_jobs, queue) do
if :sets.size(running_jobs) < Pleroma.Config.get([__MODULE__, queue_name, :max_jobs]) &&
queue != [] do
{{mod, args}, queue} = queue_pop(queue)
{:ok, pid} = Task.start(fn -> apply(mod, :perform, args) end)
mref = Process.monitor(pid)
state
|> add_ref(queue_name, mref)
|> update_queue(queue_name, {:sets.add_element(mref, running_jobs), queue})
else
state
end
end
def enqueue_sorted(queue, element, priority) do
[%{item: element, priority: priority} | queue]
|> Enum.sort_by(fn %{priority: priority} -> priority end)
end
def queue_pop([%{item: element} | queue]) do
{element, queue}
end
defp add_ref(state, queue_name, ref) do
refs = Map.put(state[:refs], ref, queue_name)
Map.put(state, :refs, refs)
end
defp remove_ref(state, ref) do
refs = Map.delete(state[:refs], ref)
Map.put(state, :refs, refs)
end
defp update_queue(state, queue_name, data) do
queues = Map.put(state[:queues], queue_name, data)
Map.put(state, :queues, queues)
end
end

View file

@ -4,8 +4,13 @@
defmodule Pleroma.List do defmodule Pleroma.List do
use Ecto.Schema use Ecto.Schema
import Ecto.{Changeset, Query}
alias Pleroma.{User, Repo, Activity} import Ecto.Query
import Ecto.Changeset
alias Pleroma.Activity
alias Pleroma.Repo
alias Pleroma.User
schema "lists" do schema "lists" do
belongs_to(:user, User, type: Pleroma.FlakeId) belongs_to(:user, User, type: Pleroma.FlakeId)

View file

@ -4,8 +4,14 @@
defmodule Pleroma.Notification do defmodule Pleroma.Notification do
use Ecto.Schema use Ecto.Schema
alias Pleroma.{User, Activity, Notification, Repo}
alias Pleroma.User
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Repo
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.CommonAPI
import Ecto.Query import Ecto.Query
schema "notifications" do schema "notifications" do
@ -112,7 +118,7 @@ defmodule Pleroma.Notification do
# TODO move to sql, too. # TODO move to sql, too.
def create_notification(%Activity{} = activity, %User{} = user) do def create_notification(%Activity{} = activity, %User{} = user) do
unless User.blocks?(user, %{ap_id: activity.data["actor"]}) or unless User.blocks?(user, %{ap_id: activity.data["actor"]}) or
user.ap_id == activity.data["actor"] or CommonAPI.thread_muted?(user, activity) or user.ap_id == activity.data["actor"] or
(activity.data["type"] == "Follow" and (activity.data["type"] == "Follow" and
Enum.any?(Notification.for_user(user), fn notif -> Enum.any?(Notification.for_user(user), fn notif ->
notif.activity.data["type"] == "Follow" and notif.activity.data["type"] == "Follow" and

View file

@ -4,8 +4,15 @@
defmodule Pleroma.Object do defmodule Pleroma.Object do
use Ecto.Schema use Ecto.Schema
alias Pleroma.{Repo, Object, User, Activity, ObjectTombstone}
import Ecto.{Query, Changeset} alias Pleroma.Repo
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Activity
alias Pleroma.ObjectTombstone
import Ecto.Query
import Ecto.Changeset
schema "objects" do schema "objects" do
field(:data, :map) field(:data, :map)

View file

@ -33,7 +33,22 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
end end
defp csp_string do defp csp_string do
protocol = Config.get([Pleroma.Web.Endpoint, :protocol]) scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
websocket_url = String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws")
connect_src =
if Mix.env() == :dev do
"connect-src 'self' http://localhost:3035/ " <> websocket_url
else
"connect-src 'self' " <> websocket_url
end
script_src =
if Mix.env() == :dev do
"script-src 'self' 'unsafe-eval'"
else
"script-src 'self'"
end
[ [
"default-src 'none'", "default-src 'none'",
@ -43,10 +58,10 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
"media-src 'self' https:", "media-src 'self' https:",
"style-src 'self' 'unsafe-inline'", "style-src 'self' 'unsafe-inline'",
"font-src 'self'", "font-src 'self'",
"script-src 'self'",
"connect-src 'self' " <> String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
"manifest-src 'self'", "manifest-src 'self'",
if protocol == "https" do connect_src,
script_src,
if scheme == "https" do
"upgrade-insecure-requests" "upgrade-insecure-requests"
end end
] ]

View file

@ -33,7 +33,7 @@ defmodule Pleroma.Plugs.InstanceStatic do
for only <- @only do for only <- @only do
at = Plug.Router.Utils.split("/") at = Plug.Router.Utils.split("/")
def call(conn = %{request_path: "/" <> unquote(only) <> _}, opts) do def call(%{request_path: "/" <> unquote(only) <> _} = conn, opts) do
call_static( call_static(
conn, conn,
opts, opts,

View file

@ -6,11 +6,9 @@ defmodule Pleroma.Plugs.OAuthPlug do
import Plug.Conn import Plug.Conn
import Ecto.Query import Ecto.Query
alias Pleroma.{ alias Pleroma.User
User, alias Pleroma.Repo
Repo, alias Pleroma.Web.OAuth.Token
Web.OAuth.Token
}
@realm_reg Regex.compile!("Bearer\:?\s+(.*)$", "i") @realm_reg Regex.compile!("Bearer\:?\s+(.*)$", "i")

View 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.Plugs.OAuthScopesPlug do
import Plug.Conn
@behaviour Plug
def init(%{scopes: _} = options), do: options
def call(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
op = options[:op] || :|
token = assigns[:token]
cond do
is_nil(token) ->
conn
op == :| && scopes -- token.scopes != scopes ->
conn
op == :& && scopes -- token.scopes == [] ->
conn
options[:fallback] == :proceed_unauthenticated ->
conn
|> assign(:user, nil)
|> assign(:token, nil)
true ->
missing_scopes = scopes -- token.scopes
error_message = "Insufficient permissions: #{Enum.join(missing_scopes, " #{op} ")}."
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{error: error_message}))
|> halt()
end
end
end

View file

@ -23,7 +23,7 @@ defmodule Pleroma.Plugs.UploadedMedia do
%{static_plug_opts: static_plug_opts} %{static_plug_opts: static_plug_opts}
end end
def call(conn = %{request_path: <<"/", @path, "/", file::binary>>}, opts) do def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do
config = Pleroma.Config.get([Pleroma.Upload]) config = Pleroma.Config.get([Pleroma.Upload])
with uploader <- Keyword.fetch!(config, :uploader), with uploader <- Keyword.fetch!(config, :uploader),

View file

@ -3,9 +3,10 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.UserFetcherPlug do defmodule Pleroma.Plugs.UserFetcherPlug do
import Plug.Conn
alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Repo
import Plug.Conn
def init(options) do def init(options) do
options options

View file

@ -4,7 +4,8 @@
defmodule Pleroma.Stats do defmodule Pleroma.Stats do
import Ecto.Query import Ecto.Query
alias Pleroma.{User, Repo} alias Pleroma.User
alias Pleroma.Repo
def start_link do def start_link do
agent = Agent.start_link(fn -> {[], %{}} end, name: __MODULE__) agent = Agent.start_link(fn -> {[], %{}} end, name: __MODULE__)
@ -23,7 +24,7 @@ defmodule Pleroma.Stats do
def schedule_update do def schedule_update do
spawn(fn -> spawn(fn ->
# 1 hour # 1 hour
Process.sleep(1000 * 60 * 60 * 1) Process.sleep(1000 * 60 * 60)
schedule_update() schedule_update()
end) end)

View file

@ -0,0 +1,45 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ThreadMute do
use Ecto.Schema
alias Pleroma.{Repo, User, ThreadMute}
require Ecto.Query
schema "thread_mutes" do
belongs_to(:user, User, type: Pleroma.FlakeId)
field(:context, :string)
end
def changeset(mute, params \\ %{}) do
mute
|> Ecto.Changeset.cast(params, [:user_id, :context])
|> Ecto.Changeset.foreign_key_constraint(:user_id)
|> Ecto.Changeset.unique_constraint(:user_id, name: :unique_index)
end
def query(user_id, context) do
user_id = Pleroma.FlakeId.from_string(user_id)
ThreadMute
|> Ecto.Query.where(user_id: ^user_id)
|> Ecto.Query.where(context: ^context)
end
def add_mute(user_id, context) do
%ThreadMute{}
|> changeset(%{user_id: user_id, context: context})
|> Repo.insert()
end
def remove_mute(user_id, context) do
query(user_id, context)
|> Repo.delete_all()
end
def check_muted(user_id, context) do
query(user_id, context)
|> Repo.all()
end
end

View file

@ -180,7 +180,7 @@ defmodule Pleroma.Upload do
end end
# For Mix.Tasks.MigrateLocalUploads # For Mix.Tasks.MigrateLocalUploads
defp prepare_upload(upload = %__MODULE__{tempfile: path}, _opts) do defp prepare_upload(%__MODULE__{tempfile: path} = upload, _opts) do
with {:ok, content_type} <- Pleroma.MIME.file_mime_type(path) do with {:ok, content_type} <- Pleroma.MIME.file_mime_type(path) do
{:ok, %__MODULE__{upload | content_type: content_type}} {:ok, %__MODULE__{upload | content_type: content_type}}
end end

View file

@ -6,7 +6,7 @@ defmodule Pleroma.Upload.Filter.Dedupe do
@behaviour Pleroma.Upload.Filter @behaviour Pleroma.Upload.Filter
alias Pleroma.Upload alias Pleroma.Upload
def filter(upload = %Upload{name: name}) do def filter(%Upload{name: name} = upload) do
extension = String.split(name, ".") |> List.last() extension = String.split(name, ".") |> List.last()
shasum = :crypto.hash(:sha256, File.read!(upload.tempfile)) |> Base.encode16(case: :lower) shasum = :crypto.hash(:sha256, File.read!(upload.tempfile)) |> Base.encode16(case: :lower)
filename = shasum <> "." <> extension filename = shasum <> "." <> extension

View file

@ -25,7 +25,7 @@ defmodule Pleroma.Uploaders.MDII do
query = "#{cgi}?#{extension}" query = "#{cgi}?#{extension}"
with {:ok, %{status: 200, body: body}} <- with {:ok, %{status: 200, body: body}} <-
@httpoison.post(query, file_data, adapter: [pool: :default]) do @httpoison.post(query, file_data, [], adapter: [pool: :default]) do
remote_file_name = String.split(body) |> List.first() remote_file_name = String.split(body) |> List.first()
public_url = "#{files}/#{remote_file_name}.#{extension}" public_url = "#{files}/#{remote_file_name}.#{extension}"
{:ok, {:url, public_url}} {:ok, {:url, public_url}}

View file

@ -27,7 +27,7 @@ defmodule Pleroma.Uploaders.S3 do
])}} ])}}
end end
def put_file(upload = %Pleroma.Upload{}) do def put_file(%Pleroma.Upload{} = upload) do
config = Pleroma.Config.get([__MODULE__]) config = Pleroma.Config.get([__MODULE__])
bucket = Keyword.get(config, :bucket) bucket = Keyword.get(config, :bucket)

View file

@ -5,13 +5,23 @@
defmodule Pleroma.User do defmodule Pleroma.User do
use Ecto.Schema use Ecto.Schema
import Ecto.{Changeset, Query} import Ecto.Changeset
alias Pleroma.{Repo, User, Object, Web, Activity, Notification} import Ecto.Query
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Object
alias Pleroma.Web
alias Pleroma.Activity
alias Pleroma.Notification
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
alias Pleroma.Formatter alias Pleroma.Formatter
alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
alias Pleroma.Web.{OStatus, Websub, OAuth} alias Pleroma.Web.OStatus
alias Pleroma.Web.ActivityPub.{Utils, ActivityPub} alias Pleroma.Web.Websub
alias Pleroma.Web.OAuth
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.ActivityPub
require Logger require Logger
@ -96,12 +106,6 @@ defmodule Pleroma.User do
"#{ap_id(user)}/followers" "#{ap_id(user)}/followers"
end end
def follow_changeset(struct, params \\ %{}) do
struct
|> cast(params, [:following])
|> validate_required([:following])
end
def user_info(%User{} = user) do def user_info(%User{} = user) do
oneself = if user.local, do: 1, else: 0 oneself = if user.local, do: 1, else: 0
@ -233,6 +237,7 @@ defmodule Pleroma.User do
changeset changeset
|> put_change(:password_hash, hashed) |> put_change(:password_hash, hashed)
|> put_change(:ap_id, ap_id) |> put_change(:ap_id, ap_id)
|> unique_constraint(:ap_id)
|> put_change(:following, [followers]) |> put_change(:following, [followers])
|> put_change(:follower_address, followers) |> put_change(:follower_address, followers)
else else
@ -256,8 +261,9 @@ defmodule Pleroma.User do
@doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)" @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
def register(%Ecto.Changeset{} = changeset) do def register(%Ecto.Changeset{} = changeset) do
with {:ok, user} <- Repo.insert(changeset), with {:ok, user} <- Repo.insert(changeset),
{:ok, _} <- try_send_confirmation_email(user), {:ok, user} <- autofollow_users(user),
{:ok, user} <- autofollow_users(user) do {:ok, _} <- Pleroma.User.WelcomeMessage.post_welcome_message_to_user(user),
{:ok, _} <- try_send_confirmation_email(user) do
{:ok, user} {:ok, user}
end end
end end
@ -267,7 +273,7 @@ defmodule Pleroma.User do
Pleroma.Config.get([:instance, :account_activation_required]) do Pleroma.Config.get([:instance, :account_activation_required]) do
user user
|> Pleroma.UserEmail.account_confirmation_email() |> Pleroma.UserEmail.account_confirmation_email()
|> Pleroma.Mailer.deliver() |> Pleroma.Mailer.deliver_async()
else else
{:ok, :noop} {:ok, :noop}
end end
@ -307,10 +313,13 @@ defmodule Pleroma.User do
end end
end end
@doc "A mass follow for local users. Ignores blocks and has no side effects" @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
@spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()} @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
def follow_all(follower, followeds) do def follow_all(follower, followeds) do
followed_addresses = Enum.map(followeds, fn %{follower_address: fa} -> fa end) followed_addresses =
followeds
|> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
|> Enum.map(fn %{follower_address: fa} -> fa end)
q = q =
from(u in User, from(u in User,
@ -604,13 +613,40 @@ defmodule Pleroma.User do
), ),
where: where:
fragment( fragment(
"? @> ?", "coalesce((?)->'object'->>'id', (?)->>'object') = ?",
a.data, a.data,
^%{"object" => user.ap_id} a.data,
^user.ap_id
) )
) )
end end
def update_follow_request_count(%User{} = user) do
subquery =
user
|> User.get_follow_requests_query()
|> select([a], %{count: count(a.id)})
User
|> where(id: ^user.id)
|> join(:inner, [u], s in subquery(subquery))
|> update([u, s],
set: [
info:
fragment(
"jsonb_set(?, '{follow_request_count}', ?::varchar::jsonb, true)",
u.info,
s.count
)
]
)
|> Repo.update_all([], returning: true)
|> case do
{1, [user]} -> {:ok, user}
_ -> {:error, user}
end
end
def get_follow_requests(%User{} = user) do def get_follow_requests(%User{} = user) do
q = get_follow_requests_query(user) q = get_follow_requests_query(user)
reqs = Repo.all(q) reqs = Repo.all(q)
@ -724,7 +760,7 @@ defmodule Pleroma.User do
# Strip the beginning @ off if there is a query # Strip the beginning @ off if there is a query
query = String.trim_leading(query, "@") query = String.trim_leading(query, "@")
if resolve, do: User.get_or_fetch_by_nickname(query) if resolve, do: get_or_fetch(query)
fts_results = do_search(fts_search_subquery(query), for_user) fts_results = do_search(fts_search_subquery(query), for_user)
@ -737,6 +773,12 @@ defmodule Pleroma.User do
Enum.uniq_by(fts_results ++ trigram_results, & &1.id) Enum.uniq_by(fts_results ++ trigram_results, & &1.id)
end end
def all_except_one(user) do
query = from(u in User, where: u.id != ^user.id)
Repo.all(query)
end
defp do_search(subquery, for_user, options \\ []) do defp do_search(subquery, for_user, options \\ []) do
q = q =
from( from(
@ -853,6 +895,30 @@ defmodule Pleroma.User do
) )
end end
def mute(muter, %User{ap_id: ap_id}) do
info_cng =
muter.info
|> User.Info.add_to_mutes(ap_id)
cng =
change(muter)
|> put_embed(:info, info_cng)
update_and_set_cache(cng)
end
def unmute(muter, %{ap_id: ap_id}) do
info_cng =
muter.info
|> User.Info.remove_from_mutes(ap_id)
cng =
change(muter)
|> put_embed(:info, info_cng)
update_and_set_cache(cng)
end
def block(blocker, %User{ap_id: ap_id} = blocked) do def block(blocker, %User{ap_id: ap_id} = blocked) do
# sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213) # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
blocker = blocker =
@ -895,6 +961,9 @@ defmodule Pleroma.User do
update_and_set_cache(cng) update_and_set_cache(cng)
end end
def mutes?(nil, _), do: false
def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
def blocks?(user, %{ap_id: ap_id}) do def blocks?(user, %{ap_id: ap_id}) do
blocks = user.info.blocks blocks = user.info.blocks
domain_blocks = user.info.domain_blocks domain_blocks = user.info.domain_blocks
@ -906,6 +975,9 @@ defmodule Pleroma.User do
end) end)
end end
def muted_users(user),
do: Repo.all(from(u in User, where: u.ap_id in ^user.info.mutes))
def blocked_users(user), def blocked_users(user),
do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks)) do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks))
@ -1123,9 +1195,6 @@ defmodule Pleroma.User do
def parse_bio(bio, _user) when bio == "", do: bio def parse_bio(bio, _user) when bio == "", do: bio
def parse_bio(bio, user) do def parse_bio(bio, user) do
mentions = Formatter.parse_mentions(bio)
tags = Formatter.parse_tags(bio)
emoji = emoji =
(user.info.source_data["tag"] || []) (user.info.source_data["tag"] || [])
|> Enum.filter(fn %{"type" => t} -> t == "Emoji" end) |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
@ -1134,7 +1203,8 @@ defmodule Pleroma.User do
end) end)
bio bio
|> CommonUtils.format_input(mentions, tags, "text/plain", user_links: [format: :full]) |> CommonUtils.format_input("text/plain", mentions_format: :full)
|> elem(0)
|> Formatter.emojify(emoji) |> Formatter.emojify(emoji)
end end
@ -1166,7 +1236,7 @@ defmodule Pleroma.User do
{:ok, updated_user} = {:ok, updated_user} =
user user
|> change(%{tags: new_tags}) |> change(%{tags: new_tags})
|> Repo.update() |> update_and_set_cache()
updated_user updated_user
end end
@ -1220,4 +1290,13 @@ defmodule Pleroma.User do
inserted_at: NaiveDateTime.utc_now() inserted_at: NaiveDateTime.utc_now()
} }
end end
def all_superusers do
from(
u in User,
where: u.local == true,
where: fragment("?->'is_admin' @> 'true' OR ?->'is_moderator' @> 'true'", u.info, u.info)
)
|> Repo.all()
end
end end

View file

@ -12,12 +12,14 @@ defmodule Pleroma.User.Info do
field(:source_data, :map, default: %{}) field(:source_data, :map, default: %{})
field(:note_count, :integer, default: 0) field(:note_count, :integer, default: 0)
field(:follower_count, :integer, default: 0) field(:follower_count, :integer, default: 0)
field(:follow_request_count, :integer, default: 0)
field(:locked, :boolean, default: false) field(:locked, :boolean, default: false)
field(:confirmation_pending, :boolean, default: false) field(:confirmation_pending, :boolean, default: false)
field(:confirmation_token, :string, default: nil) field(:confirmation_token, :string, default: nil)
field(:default_scope, :string, default: "public") field(:default_scope, :string, default: "public")
field(:blocks, {:array, :string}, default: []) field(:blocks, {:array, :string}, default: [])
field(:domain_blocks, {:array, :string}, default: []) field(:domain_blocks, {:array, :string}, default: [])
field(:mutes, {:array, :string}, default: [])
field(:deactivated, :boolean, default: false) field(:deactivated, :boolean, default: false)
field(:no_rich_text, :boolean, default: false) field(:no_rich_text, :boolean, default: false)
field(:ap_enabled, :boolean, default: false) field(:ap_enabled, :boolean, default: false)
@ -34,6 +36,7 @@ defmodule Pleroma.User.Info do
field(:hide_followers, :boolean, default: false) field(:hide_followers, :boolean, default: false)
field(:hide_follows, :boolean, default: false) field(:hide_follows, :boolean, default: false)
field(:pinned_activities, {:array, :string}, default: []) field(:pinned_activities, {:array, :string}, default: [])
field(:flavour, :string, default: nil)
# Found in the wild # Found in the wild
# ap_id -> Where is this used? # ap_id -> Where is this used?
@ -72,6 +75,14 @@ defmodule Pleroma.User.Info do
|> validate_required([:follower_count]) |> validate_required([:follower_count])
end end
def set_mutes(info, mutes) do
params = %{mutes: mutes}
info
|> cast(params, [:mutes])
|> validate_required([:mutes])
end
def set_blocks(info, blocks) do def set_blocks(info, blocks) do
params = %{blocks: blocks} params = %{blocks: blocks}
@ -80,6 +91,14 @@ defmodule Pleroma.User.Info do
|> validate_required([:blocks]) |> validate_required([:blocks])
end end
def add_to_mutes(info, muted) do
set_mutes(info, Enum.uniq([muted | info.mutes]))
end
def remove_from_mutes(info, muted) do
set_mutes(info, List.delete(info.mutes, muted))
end
def add_to_block(info, blocked) do def add_to_block(info, blocked) do
set_blocks(info, Enum.uniq([blocked | info.blocks])) set_blocks(info, Enum.uniq([blocked | info.blocks]))
end end
@ -186,6 +205,14 @@ defmodule Pleroma.User.Info do
|> validate_required([:settings]) |> validate_required([:settings])
end end
def mastodon_flavour_update(info, flavour) do
params = %{flavour: flavour}
info
|> cast(params, [:flavour])
|> validate_required([:flavour])
end
def set_source_data(info, source_data) do def set_source_data(info, source_data) do
params = %{source_data: source_data} params = %{source_data: source_data}

View file

@ -0,0 +1,30 @@
defmodule Pleroma.User.WelcomeMessage do
alias Pleroma.User
alias Pleroma.Web.CommonAPI
def post_welcome_message_to_user(user) do
with %User{} = sender_user <- welcome_user(),
message when is_binary(message) <- welcome_message() do
CommonAPI.post(sender_user, %{
"visibility" => "direct",
"status" => "@#{user.nickname}\n#{message}"
})
else
_ -> {:ok, nil}
end
end
defp welcome_user() do
with nickname when is_binary(nickname) <-
Pleroma.Config.get([:instance, :welcome_user_nickname]),
%User{local: true} = user <- User.get_cached_by_nickname(nickname) do
user
else
_ -> nil
end
end
defp welcome_message() do
Pleroma.Config.get([:instance, :welcome_message])
end
end

View file

@ -3,13 +3,23 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ActivityPub do defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.{Activity, Repo, Object, Upload, User, Notification, Instances} alias Pleroma.Activity
alias Pleroma.Web.ActivityPub.{Transmogrifier, MRF} alias Pleroma.Repo
alias Pleroma.Object
alias Pleroma.Upload
alias Pleroma.User
alias Pleroma.Notification
alias Pleroma.Instances
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.MRF
alias Pleroma.Web.WebFinger alias Pleroma.Web.WebFinger
alias Pleroma.Web.Federator alias Pleroma.Web.Federator
alias Pleroma.Web.OStatus alias Pleroma.Web.OStatus
import Ecto.Query import Ecto.Query
import Pleroma.Web.ActivityPub.Utils import Pleroma.Web.ActivityPub.Utils
import Pleroma.Web.ActivityPub.Visibility
require Logger require Logger
@httpoison Application.get_env(:pleroma, :httpoison) @httpoison Application.get_env(:pleroma, :httpoison)
@ -19,19 +29,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp get_recipients(%{"type" => "Announce"} = data) do defp get_recipients(%{"type" => "Announce"} = data) do
to = data["to"] || [] to = data["to"] || []
cc = data["cc"] || [] cc = data["cc"] || []
recipients = to ++ cc
actor = User.get_cached_by_ap_id(data["actor"]) actor = User.get_cached_by_ap_id(data["actor"])
recipients recipients =
|> Enum.filter(fn recipient -> (to ++ cc)
case User.get_cached_by_ap_id(recipient) do |> Enum.filter(fn recipient ->
nil -> case User.get_cached_by_ap_id(recipient) do
true nil ->
true
user -> user ->
User.following?(user, actor) User.following?(user, actor)
end end
end) end)
{recipients, to, cc} {recipients, to, cc}
end end
@ -119,7 +129,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
activity.data["object"] activity.data["object"]
|> Map.get("tag", []) |> Map.get("tag", [])
|> Enum.filter(fn tag -> is_bitstring(tag) end) |> Enum.filter(fn tag -> is_bitstring(tag) end)
|> Enum.map(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end) |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end)
if activity.data["object"]["attachment"] != [] do if activity.data["object"]["attachment"] != [] do
Pleroma.Web.Streamer.stream("public:media", activity) Pleroma.Web.Streamer.stream("public:media", activity)
@ -163,9 +173,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
# only accept false as false value # only accept false as false value
local = !(params[:local] == false) local = !(params[:local] == false)
with data <- %{"to" => to, "type" => "Accept", "actor" => actor, "object" => object}, with data <- %{"to" => to, "type" => "Accept", "actor" => actor.ap_id, "object" => object},
{:ok, activity} <- insert(data, local), {:ok, activity} <- insert(data, local),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity),
_ <- User.update_follow_request_count(actor) do
{:ok, activity} {:ok, activity}
end end
end end
@ -174,9 +185,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
# only accept false as false value # only accept false as false value
local = !(params[:local] == false) local = !(params[:local] == false)
with data <- %{"to" => to, "type" => "Reject", "actor" => actor, "object" => object}, with data <- %{"to" => to, "type" => "Reject", "actor" => actor.ap_id, "object" => object},
{:ok, activity} <- insert(data, local), {:ok, activity} <- insert(data, local),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity),
_ <- User.update_follow_request_count(actor) do
{:ok, activity} {:ok, activity}
end end
end end
@ -274,7 +286,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def follow(follower, followed, activity_id \\ nil, local \\ true) do def follow(follower, followed, activity_id \\ nil, local \\ true) do
with data <- make_follow_data(follower, followed, activity_id), with data <- make_follow_data(follower, followed, activity_id),
{:ok, activity} <- insert(data, local), {:ok, activity} <- insert(data, local),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity),
_ <- User.update_follow_request_count(followed) do
{:ok, activity} {:ok, activity}
end end
end end
@ -284,7 +297,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"), {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),
unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id), unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
{:ok, activity} <- insert(unfollow_data, local), {:ok, activity} <- insert(unfollow_data, local),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity),
_ <- User.update_follow_request_count(followed) do
{:ok, activity} {:ok, activity}
end end
end end
@ -340,6 +354,31 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end end
end end
def flag(
%{
actor: actor,
context: context,
account: account,
statuses: statuses,
content: content
} = params
) do
additional = params[:additional] || %{}
# only accept false as false value
local = !(params[:local] == false)
%{
actor: actor,
context: context,
account: account,
statuses: statuses,
content: content
}
|> make_flag_data(additional)
|> insert(local)
end
def fetch_activities_for_context(context, opts \\ %{}) do def fetch_activities_for_context(context, opts \\ %{}) do
public = ["https://www.w3.org/ns/activitystreams#Public"] public = ["https://www.w3.org/ns/activitystreams#Public"]
@ -563,6 +602,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_reblogs(query, _), do: query defp restrict_reblogs(query, _), do: query
defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query
defp restrict_muted(query, %{"muting_user" => %User{info: info}}) do
mutes = info.mutes
from(
activity in query,
where: fragment("not (? = ANY(?))", activity.actor, ^mutes),
where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes)
)
end
defp restrict_muted(query, _), do: query
defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
blocks = info.blocks || [] blocks = info.blocks || []
domain_blocks = info.domain_blocks || [] domain_blocks = info.domain_blocks || []
@ -616,6 +669,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_type(opts) |> restrict_type(opts)
|> restrict_favorited_by(opts) |> restrict_favorited_by(opts)
|> restrict_blocked(opts) |> restrict_blocked(opts)
|> restrict_muted(opts)
|> restrict_media(opts) |> restrict_media(opts)
|> restrict_visibility(opts) |> restrict_visibility(opts)
|> restrict_replies(opts) |> restrict_replies(opts)
@ -744,21 +798,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
public = is_public?(activity) public = is_public?(activity)
reachable_inboxes_metadata =
(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()
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data) {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
json = Jason.encode!(data) json = Jason.encode!(data)
Enum.each(reachable_inboxes_metadata, fn {inbox, unreachable_since} -> (Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers)
Federator.enqueue(:publish_single_ap, %{ |> 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, inbox: inbox,
json: json, json: json,
actor: actor, actor: actor,
@ -774,11 +826,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64()) digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
date =
NaiveDateTime.utc_now()
|> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
signature = signature =
Pleroma.Web.HTTPSignatures.sign(actor, %{ Pleroma.Web.HTTPSignatures.sign(actor, %{
host: host, host: host,
"content-length": byte_size(json), "content-length": byte_size(json),
digest: digest digest: digest,
date: date
}) })
with {:ok, %{status: code}} when code in 200..299 <- with {:ok, %{status: code}} when code in 200..299 <-
@ -788,6 +845,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
json, json,
[ [
{"Content-Type", "application/activity+json"}, {"Content-Type", "application/activity+json"},
{"Date", date},
{"signature", signature}, {"signature", signature},
{"digest", digest} {"digest", digest}
] ]
@ -809,8 +867,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
if object = Object.get_cached_by_ap_id(id) do if object = Object.get_cached_by_ap_id(id) do
{:ok, object} {:ok, object}
else else
Logger.info("Fetching #{id} via AP")
with {:ok, data} <- fetch_and_contain_remote_object_from_id(id), with {:ok, data} <- fetch_and_contain_remote_object_from_id(id),
nil <- Object.normalize(data), nil <- Object.normalize(data),
params <- %{ params <- %{
@ -842,7 +898,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end end
def fetch_and_contain_remote_object_from_id(id) do def fetch_and_contain_remote_object_from_id(id) do
Logger.info("Fetching #{id} via AP") Logger.info("Fetching object #{id} via AP")
with true <- String.starts_with?(id, "http"), with true <- String.starts_with?(id, "http"),
{:ok, %{body: body, status: code}} when code in 200..299 <- {:ok, %{body: body, status: code}} when code in 200..299 <-
@ -859,52 +915,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end end
end end
def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
def is_public?(%Object{data: data}), do: is_public?(data)
def is_public?(%Activity{data: data}), do: is_public?(data)
def is_public?(%{"directMessage" => true}), do: false
def is_public?(data) do
"https://www.w3.org/ns/activitystreams#Public" in (data["to"] ++ (data["cc"] || []))
end
def is_private?(activity) do
!is_public?(activity) && Enum.any?(activity.data["to"], &String.contains?(&1, "/followers"))
end
def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true
def is_direct?(%Object{data: %{"directMessage" => true}}), do: true
def is_direct?(activity) do
!is_public?(activity) && !is_private?(activity)
end
def visible_for_user?(activity, nil) do
is_public?(activity)
end
def visible_for_user?(activity, user) do
x = [user.ap_id | user.following]
y = activity.data["to"] ++ (activity.data["cc"] || [])
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
end
# guard
def entire_thread_visible_for_user?(nil, _user), do: false
# child
def entire_thread_visible_for_user?(
%Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,
user
)
when is_binary(parent_id) do
parent = Activity.get_in_reply_to_activity(tail)
visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user)
end
# root
def entire_thread_visible_for_user?(tail, user), do: visible_for_user?(tail, user)
# filter out broken threads # filter out broken threads
def contain_broken_threads(%Activity{} = activity, %User{} = user) do def contain_broken_threads(%Activity{} = activity, %User{} = user) do
entire_thread_visible_for_user?(activity, user) entire_thread_visible_for_user?(activity, user)

View file

@ -5,12 +5,16 @@
defmodule Pleroma.Web.ActivityPub.ActivityPubController do defmodule Pleroma.Web.ActivityPub.ActivityPubController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.{Activity, User, Object} alias Pleroma.Activity
alias Pleroma.Web.ActivityPub.{ObjectView, UserView} alias Pleroma.User
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.UserView
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Federator alias Pleroma.Web.Federator
require Logger require Logger
@ -46,7 +50,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def object(conn, %{"uuid" => uuid}) do def object(conn, %{"uuid" => uuid}) do
with ap_id <- o_status_url(conn, :object, uuid), with ap_id <- o_status_url(conn, :object, uuid),
%Object{} = object <- Object.get_cached_by_ap_id(ap_id), %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
{_, true} <- {:public?, ActivityPub.is_public?(object)} do {_, true} <- {:public?, Visibility.is_public?(object)} do
conn conn
|> put_resp_header("content-type", "application/activity+json") |> put_resp_header("content-type", "application/activity+json")
|> json(ObjectView.render("object.json", %{object: object})) |> json(ObjectView.render("object.json", %{object: object}))
@ -59,7 +63,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def object_likes(conn, %{"uuid" => uuid, "page" => page}) do def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
with ap_id <- o_status_url(conn, :object, uuid), with ap_id <- o_status_url(conn, :object, uuid),
%Object{} = object <- Object.get_cached_by_ap_id(ap_id), %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
{_, true} <- {:public?, ActivityPub.is_public?(object)}, {_, true} <- {:public?, Visibility.is_public?(object)},
likes <- Utils.get_object_likes(object) do likes <- Utils.get_object_likes(object) do
{page, _} = Integer.parse(page) {page, _} = Integer.parse(page)
@ -75,7 +79,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def object_likes(conn, %{"uuid" => uuid}) do def object_likes(conn, %{"uuid" => uuid}) do
with ap_id <- o_status_url(conn, :object, uuid), with ap_id <- o_status_url(conn, :object, uuid),
%Object{} = object <- Object.get_cached_by_ap_id(ap_id), %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
{_, true} <- {:public?, ActivityPub.is_public?(object)}, {_, true} <- {:public?, Visibility.is_public?(object)},
likes <- Utils.get_object_likes(object) do likes <- Utils.get_object_likes(object) do
conn conn
|> put_resp_header("content-type", "application/activity+json") |> put_resp_header("content-type", "application/activity+json")
@ -89,7 +93,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def activity(conn, %{"uuid" => uuid}) do def activity(conn, %{"uuid" => uuid}) do
with ap_id <- o_status_url(conn, :activity, uuid), with ap_id <- o_status_url(conn, :activity, uuid),
%Activity{} = activity <- Activity.normalize(ap_id), %Activity{} = activity <- Activity.normalize(ap_id),
{_, true} <- {:public?, ActivityPub.is_public?(activity)} do {_, true} <- {:public?, Visibility.is_public?(activity)} do
conn conn
|> put_resp_header("content-type", "application/activity+json") |> put_resp_header("content-type", "application/activity+json")
|> json(ObjectView.render("object.json", %{object: activity})) |> json(ObjectView.render("object.json", %{object: activity}))
@ -152,13 +156,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
with %User{} = user <- User.get_cached_by_nickname(nickname), with %User{} = user <- User.get_cached_by_nickname(nickname),
true <- Utils.recipient_in_message(user.ap_id, params), true <- Utils.recipient_in_message(user.ap_id, params),
params <- Utils.maybe_splice_recipient(user.ap_id, params) do params <- Utils.maybe_splice_recipient(user.ap_id, params) do
Federator.enqueue(:incoming_ap_doc, params) Federator.incoming_ap_doc(params)
json(conn, "ok") json(conn, "ok")
end end
end end
def inbox(%{assigns: %{valid_signature: true}} = conn, params) do def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
Federator.enqueue(:incoming_ap_doc, params) Federator.incoming_ap_doc(params)
json(conn, "ok") json(conn, "ok")
end end

View file

@ -6,40 +6,80 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
alias Pleroma.User alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF @behaviour Pleroma.Web.ActivityPub.MRF
defp delist_message(message) do defp delist_message(message, threshold) when threshold > 0 do
follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address
message follower_collection? = Enum.member?(message["to"] ++ message["cc"], follower_collection)
|> Map.put("to", [follower_collection])
|> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"]) message =
case get_recipient_count(message) do
{:public, recipients}
when follower_collection? and recipients > threshold ->
message
|> Map.put("to", [follower_collection])
|> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"])
{:public, recipients} when recipients > threshold ->
message
|> Map.put("to", [])
|> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"])
_ ->
message
end
{:ok, message}
end
defp delist_message(message, _threshold), do: {:ok, message}
defp reject_message(message, threshold) when threshold > 0 do
with {_, recipients} <- get_recipient_count(message) do
if recipients > threshold do
{:reject, nil}
else
{:ok, message}
end
end
end
defp reject_message(message, _threshold), do: {:ok, message}
defp get_recipient_count(message) do
recipients = (message["to"] || []) ++ (message["cc"] || [])
follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address
if Enum.member?(recipients, "https://www.w3.org/ns/activitystreams#Public") do
recipients =
recipients
|> List.delete("https://www.w3.org/ns/activitystreams#Public")
|> List.delete(follower_collection)
{:public, length(recipients)}
else
recipients =
recipients
|> List.delete(follower_collection)
{:not_public, length(recipients)}
end
end end
@impl true @impl true
def filter(%{"type" => "Create"} = message) do def filter(%{"type" => "Create"} = message) do
delist_threshold = Pleroma.Config.get([:mrf_hellthread, :delist_threshold])
reject_threshold = reject_threshold =
Pleroma.Config.get( Pleroma.Config.get(
[:mrf_hellthread, :reject_threshold], [:mrf_hellthread, :reject_threshold],
Pleroma.Config.get([:mrf_hellthread, :threshold]) Pleroma.Config.get([:mrf_hellthread, :threshold])
) )
recipients = (message["to"] || []) ++ (message["cc"] || []) delist_threshold = Pleroma.Config.get([:mrf_hellthread, :delist_threshold])
cond do with {:ok, message} <- reject_message(message, reject_threshold),
length(recipients) > reject_threshold and reject_threshold > 0 -> {:ok, message} <- delist_message(message, delist_threshold) do
{:reject, nil} {:ok, message}
else
length(recipients) > delist_threshold and delist_threshold > 0 -> _e -> {:reject, nil}
if Enum.member?(message["to"], "https://www.w3.org/ns/activitystreams#Public") or
Enum.member?(message["cc"], "https://www.w3.org/ns/activitystreams#Public") do
{:ok, delist_message(message)}
else
{:ok, message}
end
true ->
{:ok, message}
end end
end end

View file

@ -0,0 +1,81 @@
# 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.MRF.KeywordPolicy do
@behaviour Pleroma.Web.ActivityPub.MRF
defp string_matches?(string, pattern) when is_binary(pattern) do
String.contains?(string, pattern)
end
defp string_matches?(string, pattern) do
String.match?(string, pattern)
end
defp check_reject(%{"object" => %{"content" => content, "summary" => summary}} = message) do
if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern ->
string_matches?(content, pattern) or string_matches?(summary, pattern)
end) do
{:reject, nil}
else
{:ok, message}
end
end
defp check_ftl_removal(
%{"to" => to, "object" => %{"content" => content, "summary" => summary}} = message
) do
if "https://www.w3.org/ns/activitystreams#Public" in to and
Enum.any?(Pleroma.Config.get([:mrf_keyword, :federated_timeline_removal]), fn pattern ->
string_matches?(content, pattern) or string_matches?(summary, pattern)
end) do
to = List.delete(to, "https://www.w3.org/ns/activitystreams#Public")
cc = ["https://www.w3.org/ns/activitystreams#Public" | message["cc"] || []]
message =
message
|> Map.put("to", to)
|> Map.put("cc", cc)
{:ok, message}
else
{:ok, message}
end
end
defp check_replace(%{"object" => %{"content" => content, "summary" => summary}} = message) do
{content, summary} =
Enum.reduce(Pleroma.Config.get([:mrf_keyword, :replace]), {content, summary}, fn {pattern,
replacement},
{content_acc,
summary_acc} ->
{String.replace(content_acc, pattern, replacement),
String.replace(summary_acc, pattern, replacement)}
end)
{:ok,
message
|> put_in(["object", "content"], content)
|> put_in(["object", "summary"], summary)}
end
@impl true
def filter(%{"object" => %{"content" => nil}} = message) do
{:ok, message}
end
@impl true
def filter(%{"type" => "Create", "object" => %{"content" => _content}} = message) do
with {:ok, message} <- check_reject(message),
{:ok, message} <- check_ftl_removal(message),
{:ok, message} <- check_replace(message) do
{:ok, message}
else
_e ->
{:reject, nil}
end
end
@impl true
def filter(message), do: {:ok, message}
end

View file

@ -3,7 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Relay do defmodule Pleroma.Web.ActivityPub.Relay do
alias Pleroma.{User, Object, Activity} alias Pleroma.User
alias Pleroma.Object
alias Pleroma.Activity
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
require Logger require Logger

View file

@ -6,12 +6,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
@moduledoc """ @moduledoc """
A module to handle coding from internal to wire ActivityPub and back. A module to handle coding from internal to wire ActivityPub and back.
""" """
alias Pleroma.Activity
alias Pleroma.User alias Pleroma.User
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Activity
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
import Ecto.Query import Ecto.Query
@ -406,7 +407,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
if not User.locked?(followed) do if not User.locked?(followed) do
ActivityPub.accept(%{ ActivityPub.accept(%{
to: [follower.ap_id], to: [follower.ap_id],
actor: followed.ap_id, actor: followed,
object: data, object: data,
local: true local: true
}) })
@ -432,7 +433,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
ActivityPub.accept(%{ ActivityPub.accept(%{
to: follow_activity.data["to"], to: follow_activity.data["to"],
type: "Accept", type: "Accept",
actor: followed.ap_id, actor: followed,
object: follow_activity.data["id"], object: follow_activity.data["id"],
local: false local: false
}) do }) do
@ -458,7 +459,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
ActivityPub.reject(%{ ActivityPub.reject(%{
to: follow_activity.data["to"], to: follow_activity.data["to"],
type: "Reject", type: "Reject",
actor: followed.ap_id, actor: followed,
object: follow_activity.data["id"], object: follow_activity.data["id"],
local: false local: false
}) do }) do
@ -489,7 +490,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
with actor <- get_actor(data), with actor <- get_actor(data),
%User{} = actor <- User.get_or_fetch_by_ap_id(actor), %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
{:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id), {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
public <- ActivityPub.is_public?(data), public <- Visibility.is_public?(data),
{:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do {:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do
{:ok, activity} {:ok, activity}
else else
@ -649,7 +650,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
if object = Object.normalize(id), do: {:ok, object}, else: nil if object = Object.normalize(id), do: {:ok, object}, else: nil
end end
def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) do def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) when is_binary(inReplyTo) do
with false <- String.starts_with?(inReplyTo, "http"), with false <- String.starts_with?(inReplyTo, "http"),
{:ok, %{data: replied_to_object}} <- get_obj_helper(inReplyTo) do {:ok, %{data: replied_to_object}} <- get_obj_helper(inReplyTo) do
Map.put(object, "inReplyTo", replied_to_object["external_url"] || inReplyTo) Map.put(object, "inReplyTo", replied_to_object["external_url"] || inReplyTo)
@ -765,12 +766,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def add_hashtags(object) do def add_hashtags(object) do
tags = tags =
(object["tag"] || []) (object["tag"] || [])
|> Enum.map(fn tag -> |> Enum.map(fn
%{ # Expand internal representation tags into AS2 tags.
"href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}", tag when is_binary(tag) ->
"name" => "##{tag}", %{
"type" => "Hashtag" "href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}",
} "name" => "##{tag}",
"type" => "Hashtag"
}
# Do not process tags which are already AS2 tag objects.
tag when is_map(tag) ->
tag
end) end)
object object

View file

@ -3,11 +3,19 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Utils do defmodule Pleroma.Web.ActivityPub.Utils do
alias Pleroma.{Repo, Web, Object, Activity, User, Notification} alias Pleroma.Repo
alias Pleroma.Web
alias Pleroma.Object
alias Pleroma.Activity
alias Pleroma.User
alias Pleroma.Notification
alias Pleroma.Web.Router.Helpers alias Pleroma.Web.Router.Helpers
alias Pleroma.Web.Endpoint alias Pleroma.Web.Endpoint
alias Ecto.{Changeset, UUID} alias Ecto.Changeset
alias Ecto.UUID
import Ecto.Query import Ecto.Query
require Logger require Logger
@supported_object_types ["Article", "Note", "Video", "Page"] @supported_object_types ["Article", "Note", "Video", "Page"]
@ -156,7 +164,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
_ -> 5 _ -> 5
end end
Pleroma.Web.Federator.enqueue(:publish, activity, priority) Pleroma.Web.Federator.publish(activity, priority)
:ok :ok
end end
@ -590,4 +598,20 @@ defmodule Pleroma.Web.ActivityPub.Utils do
} }
|> Map.merge(additional) |> Map.merge(additional)
end end
#### Flag-related helpers
def make_flag_data(params, additional) do
status_ap_ids = Enum.map(params.statuses || [], & &1.data["id"])
object = [params.account.ap_id] ++ status_ap_ids
%{
"type" => "Flag",
"actor" => params.actor.ap_id,
"content" => params.content,
"object" => object,
"context" => params.context
}
|> Map.merge(additional)
end
end end

View file

@ -4,7 +4,8 @@
defmodule Pleroma.Web.ActivityPub.ObjectView do defmodule Pleroma.Web.ActivityPub.ObjectView do
use Pleroma.Web, :view use Pleroma.Web, :view
alias Pleroma.{Object, Activity} alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
def render("object.json", %{object: %Object{} = object}) do def render("object.json", %{object: %Object{} = object}) do

View file

@ -4,15 +4,34 @@
defmodule Pleroma.Web.ActivityPub.UserView do defmodule Pleroma.Web.ActivityPub.UserView do
use Pleroma.Web, :view use Pleroma.Web, :view
alias Pleroma.Web.Salmon
alias Pleroma.Web.WebFinger alias Pleroma.Web.WebFinger
alias Pleroma.Web.Salmon
alias Pleroma.User alias Pleroma.User
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Router.Helpers
alias Pleroma.Web.Endpoint
import Ecto.Query import Ecto.Query
def render("endpoints.json", %{user: %User{nickname: nil, local: true} = _user}) do
%{"sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)}
end
def render("endpoints.json", %{user: %User{local: true} = _user}) do
%{
"oauthAuthorizationEndpoint" => Helpers.o_auth_url(Endpoint, :authorize),
"oauthRegistrationEndpoint" => Helpers.mastodon_api_url(Endpoint, :create_app),
"oauthTokenEndpoint" => Helpers.o_auth_url(Endpoint, :token_exchange),
"sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)
}
end
def render("endpoints.json", _), do: %{}
# the instance itself is not a Person, but instead an Application # the instance itself is not a Person, but instead an Application
def render("user.json", %{user: %{nickname: nil} = user}) do def render("user.json", %{user: %{nickname: nil} = user}) do
{:ok, user} = WebFinger.ensure_keys_present(user) {:ok, user} = WebFinger.ensure_keys_present(user)
@ -20,6 +39,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key]) public_key = :public_key.pem_encode([public_key])
endpoints = render("endpoints.json", %{user: user})
%{ %{
"id" => user.ap_id, "id" => user.ap_id,
"type" => "Application", "type" => "Application",
@ -35,9 +56,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"owner" => user.ap_id, "owner" => user.ap_id,
"publicKeyPem" => public_key "publicKeyPem" => public_key
}, },
"endpoints" => %{ "endpoints" => endpoints
"sharedInbox" => "#{Pleroma.Web.Endpoint.url()}/inbox"
}
} }
|> Map.merge(Utils.make_json_ld_header()) |> Map.merge(Utils.make_json_ld_header())
end end
@ -48,6 +67,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key]) public_key = :public_key.pem_encode([public_key])
endpoints = render("endpoints.json", %{user: user})
%{ %{
"id" => user.ap_id, "id" => user.ap_id,
"type" => "Person", "type" => "Person",
@ -65,9 +86,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"owner" => user.ap_id, "owner" => user.ap_id,
"publicKeyPem" => public_key "publicKeyPem" => public_key
}, },
"endpoints" => %{ "endpoints" => endpoints,
"sharedInbox" => "#{Pleroma.Web.Endpoint.url()}/inbox"
},
"icon" => %{ "icon" => %{
"type" => "Image", "type" => "Image",
"url" => User.avatar_url(user) "url" => User.avatar_url(user)
@ -86,7 +105,14 @@ defmodule Pleroma.Web.ActivityPub.UserView do
query = from(user in query, select: [:ap_id]) query = from(user in query, select: [:ap_id])
following = Repo.all(query) following = Repo.all(query)
collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows) total =
if !user.info.hide_follows do
length(following)
else
0
end
collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows, total)
|> Map.merge(Utils.make_json_ld_header()) |> Map.merge(Utils.make_json_ld_header())
end end
@ -95,10 +121,17 @@ defmodule Pleroma.Web.ActivityPub.UserView do
query = from(user in query, select: [:ap_id]) query = from(user in query, select: [:ap_id])
following = Repo.all(query) following = Repo.all(query)
total =
if !user.info.hide_follows do
length(following)
else
0
end
%{ %{
"id" => "#{user.ap_id}/following", "id" => "#{user.ap_id}/following",
"type" => "OrderedCollection", "type" => "OrderedCollection",
"totalItems" => length(following), "totalItems" => total,
"first" => collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows) "first" => collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
} }
|> Map.merge(Utils.make_json_ld_header()) |> Map.merge(Utils.make_json_ld_header())
@ -109,7 +142,14 @@ defmodule Pleroma.Web.ActivityPub.UserView do
query = from(user in query, select: [:ap_id]) query = from(user in query, select: [:ap_id])
followers = Repo.all(query) followers = Repo.all(query)
collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers) total =
if !user.info.hide_followers do
length(followers)
else
0
end
collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers, total)
|> Map.merge(Utils.make_json_ld_header()) |> Map.merge(Utils.make_json_ld_header())
end end
@ -118,19 +158,24 @@ defmodule Pleroma.Web.ActivityPub.UserView do
query = from(user in query, select: [:ap_id]) query = from(user in query, select: [:ap_id])
followers = Repo.all(query) followers = Repo.all(query)
total =
if !user.info.hide_followers do
length(followers)
else
0
end
%{ %{
"id" => "#{user.ap_id}/followers", "id" => "#{user.ap_id}/followers",
"type" => "OrderedCollection", "type" => "OrderedCollection",
"totalItems" => length(followers), "totalItems" => total,
"first" => collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers) "first" =>
collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers, total)
} }
|> Map.merge(Utils.make_json_ld_header()) |> Map.merge(Utils.make_json_ld_header())
end end
def render("outbox.json", %{user: user, max_id: max_qid}) do def render("outbox.json", %{user: user, max_id: max_qid}) do
# XXX: technically note_count is wrong for this, but it's better than nothing
info = User.user_info(user)
params = %{ params = %{
"limit" => "10" "limit" => "10"
} }
@ -143,14 +188,24 @@ defmodule Pleroma.Web.ActivityPub.UserView do
end end
activities = ActivityPub.fetch_user_activities(user, nil, params) activities = ActivityPub.fetch_user_activities(user, nil, params)
min_id = Enum.at(Enum.reverse(activities), 0).id
max_id = Enum.at(activities, 0).id
collection = {max_id, min_id, collection} =
Enum.map(activities, fn act -> if length(activities) > 0 do
{:ok, data} = Transmogrifier.prepare_outgoing(act.data) {
data Enum.at(Enum.reverse(activities), 0).id,
end) Enum.at(activities, 0).id,
Enum.map(activities, fn act ->
{:ok, data} = Transmogrifier.prepare_outgoing(act.data)
data
end)
}
else
{
0,
0,
[]
}
end
iri = "#{user.ap_id}/outbox" iri = "#{user.ap_id}/outbox"
@ -158,7 +213,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"id" => "#{iri}?max_id=#{max_id}", "id" => "#{iri}?max_id=#{max_id}",
"type" => "OrderedCollectionPage", "type" => "OrderedCollectionPage",
"partOf" => iri, "partOf" => iri,
"totalItems" => info.note_count,
"orderedItems" => collection, "orderedItems" => collection,
"next" => "#{iri}?max_id=#{min_id}" "next" => "#{iri}?max_id=#{min_id}"
} }
@ -167,7 +221,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
%{ %{
"id" => iri, "id" => iri,
"type" => "OrderedCollection", "type" => "OrderedCollection",
"totalItems" => info.note_count,
"first" => page "first" => page
} }
|> Map.merge(Utils.make_json_ld_header()) |> Map.merge(Utils.make_json_ld_header())
@ -205,7 +258,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"id" => "#{iri}?max_id=#{max_id}", "id" => "#{iri}?max_id=#{max_id}",
"type" => "OrderedCollectionPage", "type" => "OrderedCollectionPage",
"partOf" => iri, "partOf" => iri,
"totalItems" => -1,
"orderedItems" => collection, "orderedItems" => collection,
"next" => "#{iri}?max_id=#{min_id}" "next" => "#{iri}?max_id=#{min_id}"
} }
@ -214,7 +266,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
%{ %{
"id" => iri, "id" => iri,
"type" => "OrderedCollection", "type" => "OrderedCollection",
"totalItems" => -1,
"first" => page "first" => page
} }
|> Map.merge(Utils.make_json_ld_header()) |> Map.merge(Utils.make_json_ld_header())

View file

@ -0,0 +1,56 @@
defmodule Pleroma.Web.ActivityPub.Visibility do
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.User
def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
def is_public?(%Object{data: data}), do: is_public?(data)
def is_public?(%Activity{data: data}), do: is_public?(data)
def is_public?(%{"directMessage" => true}), do: false
def is_public?(data) do
"https://www.w3.org/ns/activitystreams#Public" in (data["to"] ++ (data["cc"] || []))
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))
else
false
end
end
def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true
def is_direct?(%Object{data: %{"directMessage" => true}}), do: true
def is_direct?(activity) do
!is_public?(activity) && !is_private?(activity)
end
def visible_for_user?(activity, nil) do
is_public?(activity)
end
def visible_for_user?(activity, user) do
x = [user.ap_id | user.following]
y = [activity.actor] ++ activity.data["to"] ++ (activity.data["cc"] || [])
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
end
# guard
def entire_thread_visible_for_user?(nil, _user), do: false
# child
def entire_thread_visible_for_user?(
%Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,
user
)
when is_binary(parent_id) do
parent = Activity.get_in_reply_to_activity(tail)
visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user)
end
# root
def entire_thread_visible_for_user?(tail, user), do: visible_for_user?(tail, user)
end

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.TwitterAPI.UserView
import Pleroma.Web.ControllerHelper, only: [json_response: 3] import Pleroma.Web.ControllerHelper, only: [json_response: 3]
@ -41,6 +42,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|> json(user.nickname) |> json(user.nickname)
end end
def user_toggle_activation(conn, %{"nickname" => nickname}) do
user = User.get_by_nickname(nickname)
{:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
conn
|> json(UserView.render("show_for_admin.json", %{user: updated_user}))
end
def tag_users(conn, %{"nicknames" => nicknames, "tags" => tags}) do def tag_users(conn, %{"nicknames" => nicknames, "tags" => tags}) do
with {:ok, _} <- User.tag(nicknames, tags), with {:ok, _} <- User.tag(nicknames, tags),
do: json_response(conn, :no_content, "") do: json_response(conn, :no_content, "")
@ -51,6 +61,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
do: json_response(conn, :no_content, "") do: json_response(conn, :no_content, "")
end end
def list_users(%{assigns: %{user: admin}} = conn, _data) do
users = User.all_except_one(admin)
conn
|> json(UserView.render("index_for_admin.json", %{users: users}))
end
def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname}) def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname})
when permission_group in ["moderator", "admin"] do when permission_group in ["moderator", "admin"] do
user = User.get_by_nickname(nickname) user = User.get_by_nickname(nickname)
@ -124,6 +141,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|> json(%{error: "No such permission_group"}) |> json(%{error: "No such permission_group"})
end end
def set_activation_status(conn, %{"nickname" => nickname, "status" => status}) do
with {:ok, status} <- Ecto.Type.cast(:boolean, status),
%User{} = user <- User.get_by_nickname(nickname),
{:ok, _} <- User.deactivate(user, !status),
do: json_response(conn, :no_content, "")
end
def relay_follow(conn, %{"relay_url" => target}) do def relay_follow(conn, %{"relay_url" => target}) do
with {:ok, _message} <- Relay.follow(target) do with {:ok, _message} <- Relay.follow(target) do
json(conn, target) json(conn, target)

View file

@ -0,0 +1,25 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Auth.Authenticator do
alias Pleroma.User
def implementation do
Pleroma.Config.get(
Pleroma.Web.Auth.Authenticator,
Pleroma.Web.Auth.PleromaAuthenticator
)
end
@callback get_user(Plug.Conn.t()) :: {:ok, User.t()} | {:error, any()}
def get_user(plug), do: implementation().get_user(plug)
@callback handle_error(Plug.Conn.t(), any()) :: any()
def handle_error(plug, error), do: implementation().handle_error(plug, error)
@callback auth_template() :: String.t() | nil
def auth_template do
implementation().auth_template() || Pleroma.Config.get(:auth_template, "show.html")
end
end

View file

@ -0,0 +1,28 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Auth.PleromaAuthenticator do
alias Pleroma.User
alias Comeonin.Pbkdf2
@behaviour Pleroma.Web.Auth.Authenticator
def get_user(%Plug.Conn{} = conn) do
%{"authorization" => %{"name" => name, "password" => password}} = conn.params
with {_, %User{} = user} <- {:user, User.get_by_nickname_or_email(name)},
{_, true} <- {:checkpw, Pbkdf2.checkpw(password, user.password_hash)} do
{:ok, user}
else
error ->
{:error, error}
end
end
def handle_error(%Plug.Conn{} = _conn, error) do
error
end
def auth_template, do: nil
end

View file

@ -3,7 +3,11 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.CommonAPI do defmodule Pleroma.Web.CommonAPI do
alias Pleroma.{User, Repo, Activity, Object} alias Pleroma.User
alias Pleroma.Repo
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.ThreadMute
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Formatter alias Pleroma.Formatter
@ -78,40 +82,20 @@ defmodule Pleroma.Web.CommonAPI do
def get_visibility(_), do: "public" def get_visibility(_), do: "public"
defp get_content_type(content_type) do
if Enum.member?(Pleroma.Config.get([:instance, :allowed_post_formats]), content_type) do
content_type
else
"text/plain"
end
end
def post(user, %{"status" => status} = data) do def post(user, %{"status" => status} = data) do
visibility = get_visibility(data) visibility = get_visibility(data)
limit = Pleroma.Config.get([:instance, :limit]) limit = Pleroma.Config.get([:instance, :limit])
with status <- String.trim(status), with status <- String.trim(status),
attachments <- attachments_from_ids(data["media_ids"]), attachments <- attachments_from_ids(data),
mentions <- Formatter.parse_mentions(status),
inReplyTo <- get_replied_to_activity(data["in_reply_to_status_id"]), inReplyTo <- get_replied_to_activity(data["in_reply_to_status_id"]),
{to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility), {content_html, mentions, tags} <-
tags <- Formatter.parse_tags(status, data),
content_html <-
make_content_html( make_content_html(
status, status,
mentions,
attachments, attachments,
tags, data
get_content_type(data["content_type"]),
Enum.member?(
[true, "true"],
Map.get(
data,
"no_attachment_links",
Pleroma.Config.get([:instance, :no_attachment_links], false)
)
)
), ),
{to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility),
context <- make_context(inReplyTo), context <- make_context(inReplyTo),
cw <- data["spoiler_text"], cw <- data["spoiler_text"],
full_payload <- String.trim(status <> (data["spoiler_text"] || "")), full_payload <- String.trim(status <> (data["spoiler_text"] || "")),
@ -216,4 +200,54 @@ defmodule Pleroma.Web.CommonAPI do
{:error, "Could not unpin"} {:error, "Could not unpin"}
end end
end end
def add_mute(user, activity) do
with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do
{:ok, activity}
else
{:error, _} -> {:error, "conversation is already muted"}
end
end
def remove_mute(user, activity) do
ThreadMute.remove_mute(user.id, activity.data["context"])
{:ok, activity}
end
def thread_muted?(%{id: nil} = _user, _activity), do: false
def thread_muted?(user, activity) do
with [] <- ThreadMute.check_muted(user.id, activity.data["context"]) do
false
else
_ -> true
end
end
def report(user, data) do
with {:account_id, %{"account_id" => account_id}} <- {:account_id, data},
{:account, %User{} = account} <- {:account, User.get_by_id(account_id)},
{:ok, {content_html, _, _}} <- make_report_content_html(data["comment"]),
{:ok, statuses} <- get_report_statuses(account, data),
{:ok, activity} <-
ActivityPub.flag(%{
context: Utils.generate_context_id(),
actor: user,
account: account,
statuses: statuses,
content: content_html
}) do
Enum.each(User.all_superusers(), fn superuser ->
superuser
|> Pleroma.AdminEmail.report(user, account, statuses, content_html)
|> Pleroma.Mailer.deliver_async()
end)
{:ok, activity}
else
{:error, err} -> {:error, err}
{:account_id, %{}} -> {:error, "Valid `account_id` required"}
{:account, nil} -> {:error, "Account not found"}
end
end
end end

View file

@ -5,12 +5,15 @@
defmodule Pleroma.Web.CommonAPI.Utils do defmodule Pleroma.Web.CommonAPI.Utils do
alias Calendar.Strftime alias Calendar.Strftime
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
alias Pleroma.{Activity, Formatter, Object, Repo} alias Pleroma.Activity
alias Pleroma.Formatter
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web alias Pleroma.Config
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Endpoint alias Pleroma.Web.Endpoint
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
alias Pleroma.Web.ActivityPub.Utils
# This is a hack for twidere. # This is a hack for twidere.
def get_by_id_or_ap_id(id) do def get_by_id_or_ap_id(id) do
@ -32,12 +35,28 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def get_replied_to_activity(_), do: nil def get_replied_to_activity(_), do: nil
def attachments_from_ids(ids) do def attachments_from_ids(data) do
if Map.has_key?(data, "descriptions") do
attachments_from_ids_descs(data["media_ids"], data["descriptions"])
else
attachments_from_ids_no_descs(data["media_ids"])
end
end
def attachments_from_ids_no_descs(ids) do
Enum.map(ids || [], fn media_id -> Enum.map(ids || [], fn media_id ->
Repo.get(Object, media_id).data Repo.get(Object, media_id).data
end) end)
end end
def attachments_from_ids_descs(ids, descs_str) do
{_, descs} = Jason.decode(descs_str)
Enum.map(ids || [], fn media_id ->
Map.put(Repo.get(Object, media_id).data, "name", descs[media_id])
end)
end
def to_for_user_and_mentions(user, mentions, inReplyTo, "public") do def to_for_user_and_mentions(user, mentions, inReplyTo, "public") do
mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end) mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end)
@ -81,24 +100,45 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def make_content_html( def make_content_html(
status, status,
mentions,
attachments, attachments,
tags, data
content_type,
no_attachment_links \\ false
) do ) do
no_attachment_links =
data
|> Map.get("no_attachment_links", Config.get([:instance, :no_attachment_links]))
|> Kernel.in([true, "true"])
content_type = get_content_type(data["content_type"])
status status
|> format_input(mentions, tags, content_type) |> format_input(content_type)
|> maybe_add_attachments(attachments, no_attachment_links) |> maybe_add_attachments(attachments, no_attachment_links)
|> maybe_add_nsfw_tag(data)
end end
defp get_content_type(content_type) do
if Enum.member?(Config.get([:instance, :allowed_post_formats]), content_type) do
content_type
else
"text/plain"
end
end
defp maybe_add_nsfw_tag({text, mentions, tags}, %{"sensitive" => sensitive})
when sensitive in [true, "True", "true", "1"] do
{text, mentions, [{"#nsfw", "nsfw"} | tags]}
end
defp maybe_add_nsfw_tag(data, _), do: data
def make_context(%Activity{data: %{"context" => context}}), do: context def make_context(%Activity{data: %{"context" => context}}), do: context
def make_context(_), do: Utils.generate_context_id() def make_context(_), do: Utils.generate_context_id()
def maybe_add_attachments(text, _attachments, _no_links = true), do: text def maybe_add_attachments(parsed, _attachments, true = _no_links), do: parsed
def maybe_add_attachments(text, attachments, _no_links) do def maybe_add_attachments({text, mentions, tags}, attachments, _no_links) do
add_attachments(text, attachments) text = add_attachments(text, attachments)
{text, mentions, tags}
end end
def add_attachments(text, attachments) do def add_attachments(text, attachments) do
@ -116,56 +156,39 @@ defmodule Pleroma.Web.CommonAPI.Utils do
Enum.join([text | attachment_text], "<br>") Enum.join([text | attachment_text], "<br>")
end end
def format_input(text, mentions, tags, format, options \\ []) def format_input(text, format, options \\ [])
@doc """ @doc """
Formatting text to plain text. Formatting text to plain text.
""" """
def format_input(text, mentions, tags, "text/plain", options) do def format_input(text, "text/plain", options) do
text text
|> Formatter.html_escape("text/plain") |> Formatter.html_escape("text/plain")
|> String.replace(~r/\r?\n/, "<br>") |> Formatter.linkify(options)
|> (&{[], &1}).() |> (fn {text, mentions, tags} ->
|> Formatter.add_links() {String.replace(text, ~r/\r?\n/, "<br>"), mentions, tags}
|> Formatter.add_user_links(mentions, options[:user_links] || []) end).()
|> Formatter.add_hashtag_links(tags)
|> Formatter.finalize()
end end
@doc """ @doc """
Formatting text to html. Formatting text to html.
""" """
def format_input(text, mentions, _tags, "text/html", options) do def format_input(text, "text/html", options) do
text text
|> Formatter.html_escape("text/html") |> Formatter.html_escape("text/html")
|> (&{[], &1}).() |> Formatter.linkify(options)
|> Formatter.add_user_links(mentions, options[:user_links] || [])
|> Formatter.finalize()
end end
@doc """ @doc """
Formatting text to markdown. Formatting text to markdown.
""" """
def format_input(text, mentions, tags, "text/markdown", options) do def format_input(text, "text/markdown", options) do
options = Keyword.put(options, :mentions_escape, true)
text text
|> Formatter.mentions_escape(mentions) |> Formatter.linkify(options)
|> Earmark.as_html!() |> (fn {text, mentions, tags} -> {Earmark.as_html!(text), mentions, tags} end).()
|> Formatter.html_escape("text/html") |> Formatter.html_escape("text/html")
|> (&{[], &1}).()
|> Formatter.add_user_links(mentions, options[:user_links] || [])
|> Formatter.add_hashtag_links(tags)
|> Formatter.finalize()
end
def add_tag_links(text, tags) do
tags =
tags
|> Enum.sort_by(fn {tag, _} -> -String.length(tag) end)
Enum.reduce(tags, text, fn {full, tag}, text ->
url = "<a href='#{Web.base_url()}/tag/#{tag}' rel='tag'>##{tag}</a>"
String.replace(text, full, url)
end)
end end
def make_note_data( def make_note_data(
@ -303,4 +326,22 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end end
def maybe_extract_mentions(_), do: [] def maybe_extract_mentions(_), do: []
def make_report_content_html(nil), do: {:ok, {nil, [], []}}
def make_report_content_html(comment) do
max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)
if String.length(comment) <= max_size do
{:ok, format_input(comment, "text/plain")}
else
{:error, "Comment must be up to #{max_size} characters"}
end
end
def get_report_statuses(%User{ap_id: actor}, %{"status_ids" => status_ids}) do
{:ok, Activity.all_by_actor_and_id(actor, status_ids)}
end
def get_report_statuses(_, _), do: {:ok, nil}
end end

View file

@ -5,6 +5,11 @@
defmodule Pleroma.Web.ControllerHelper do defmodule Pleroma.Web.ControllerHelper do
use Pleroma.Web, :controller use Pleroma.Web, :controller
def oauth_scopes(params, default) do
# Note: `scopes` is used by Mastodon — supporting it but sticking to OAuth's standard `scope` wherever we control it
Pleroma.Web.OAuth.parse_scopes(params["scope"] || params["scopes"], default)
end
def json_response(conn, status, json) do def json_response(conn, status, json) do
conn conn
|> put_status(status) |> put_status(status)

View file

@ -3,54 +3,83 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Federator do defmodule Pleroma.Web.Federator do
use GenServer
alias Pleroma.User
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Web.{WebFinger, Websub, Salmon} alias Pleroma.User
alias Pleroma.Web.Federator.RetryQueue alias Pleroma.Web.WebFinger
alias Pleroma.Web.Websub
alias Pleroma.Web.Salmon
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Federator.RetryQueue
alias Pleroma.Web.OStatus alias Pleroma.Web.OStatus
alias Pleroma.Jobs
require Logger require Logger
@websub Application.get_env(:pleroma, :websub) @websub Application.get_env(:pleroma, :websub)
@ostatus Application.get_env(:pleroma, :ostatus) @ostatus Application.get_env(:pleroma, :ostatus)
def init(args) do def init() do
{:ok, args} # 1 minute
Process.sleep(1000 * 60 * 1)
refresh_subscriptions()
end end
def start_link do # Client API
spawn(fn ->
# 1 minute
Process.sleep(1000 * 60 * 1)
enqueue(:refresh_subscriptions, nil)
end)
GenServer.start_link( def incoming_doc(doc) do
__MODULE__, Jobs.enqueue(:federator_incoming, __MODULE__, [:incoming_doc, doc])
%{
in: {:sets.new(), []},
out: {:sets.new(), []}
},
name: __MODULE__
)
end end
def handle(:refresh_subscriptions, _) do def incoming_ap_doc(params) do
Jobs.enqueue(:federator_incoming, __MODULE__, [:incoming_ap_doc, params])
end
def publish(activity, priority \\ 1) do
Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish, activity], priority)
end
def publish_single_ap(params) do
Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish_single_ap, params])
end
def publish_single_websub(websub) do
Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish_single_websub, websub])
end
def verify_websub(websub) do
Jobs.enqueue(:federator_outgoing, __MODULE__, [:verify_websub, websub])
end
def request_subscription(sub) do
Jobs.enqueue(:federator_outgoing, __MODULE__, [:request_subscription, sub])
end
def refresh_subscriptions() do
Jobs.enqueue(:federator_outgoing, __MODULE__, [:refresh_subscriptions])
end
def publish_single_salmon(params) do
Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish_single_salmon, params])
end
# Job Worker Callbacks
def perform(:refresh_subscriptions) do
Logger.debug("Federator running refresh subscriptions") Logger.debug("Federator running refresh subscriptions")
Websub.refresh_subscriptions() Websub.refresh_subscriptions()
spawn(fn -> spawn(fn ->
# 6 hours # 6 hours
Process.sleep(1000 * 60 * 60 * 6) Process.sleep(1000 * 60 * 60 * 6)
enqueue(:refresh_subscriptions, nil) refresh_subscriptions()
end) end)
end end
def handle(:request_subscription, websub) do def perform(:request_subscription, websub) do
Logger.debug("Refreshing #{websub.topic}") Logger.debug("Refreshing #{websub.topic}")
with {:ok, websub} <- Websub.request_subscription(websub) do with {:ok, websub} <- Websub.request_subscription(websub) do
@ -60,13 +89,13 @@ defmodule Pleroma.Web.Federator do
end end
end end
def handle(:publish, activity) do def perform(:publish, activity) do
Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end) Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end)
with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do
{:ok, actor} = WebFinger.ensure_keys_present(actor) {:ok, actor} = WebFinger.ensure_keys_present(actor)
if ActivityPub.is_public?(activity) do if Visibility.is_public?(activity) do
if OStatus.is_representable?(activity) do if OStatus.is_representable?(activity) do
Logger.info(fn -> "Sending #{activity.data["id"]} out via WebSub" end) Logger.info(fn -> "Sending #{activity.data["id"]} out via WebSub" end)
Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity) Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
@ -86,7 +115,7 @@ defmodule Pleroma.Web.Federator do
end end
end end
def handle(:verify_websub, websub) do def perform(:verify_websub, websub) do
Logger.debug(fn -> Logger.debug(fn ->
"Running WebSub verification for #{websub.id} (#{websub.topic}, #{websub.callback})" "Running WebSub verification for #{websub.id} (#{websub.topic}, #{websub.callback})"
end) end)
@ -94,12 +123,12 @@ defmodule Pleroma.Web.Federator do
@websub.verify(websub) @websub.verify(websub)
end end
def handle(:incoming_doc, doc) do def perform(:incoming_doc, doc) do
Logger.info("Got document, trying to parse") Logger.info("Got document, trying to parse")
@ostatus.handle_incoming(doc) @ostatus.handle_incoming(doc)
end end
def handle(:incoming_ap_doc, params) do def perform(:incoming_ap_doc, params) do
Logger.info("Handling incoming AP activity") Logger.info("Handling incoming AP activity")
params = Utils.normalize_params(params) params = Utils.normalize_params(params)
@ -124,11 +153,11 @@ defmodule Pleroma.Web.Federator do
end end
end end
def handle(:publish_single_salmon, params) do def perform(:publish_single_salmon, params) do
Salmon.send_to_user(params) Salmon.send_to_user(params)
end end
def handle(:publish_single_ap, params) do def perform(:publish_single_ap, params) do
case ActivityPub.publish_one(params) do case ActivityPub.publish_one(params) do
{:ok, _} -> {:ok, _} ->
:ok :ok
@ -138,7 +167,7 @@ defmodule Pleroma.Web.Federator do
end end
end end
def handle( def perform(
:publish_single_websub, :publish_single_websub,
%{xml: _xml, topic: _topic, callback: _callback, secret: _secret} = params %{xml: _xml, topic: _topic, callback: _callback, secret: _secret} = params
) do ) do
@ -151,75 +180,11 @@ defmodule Pleroma.Web.Federator do
end end
end end
def handle(type, _) do def perform(type, _) do
Logger.debug(fn -> "Unknown task: #{type}" end) Logger.debug(fn -> "Unknown task: #{type}" end)
{:error, "Don't know what to do with this"} {:error, "Don't know what to do with this"}
end end
if Mix.env() == :test do
def enqueue(type, payload, _priority \\ 1) do
if Pleroma.Config.get([:instance, :federating]) do
handle(type, payload)
end
end
else
def enqueue(type, payload, priority \\ 1) do
if Pleroma.Config.get([:instance, :federating]) do
GenServer.cast(__MODULE__, {:enqueue, type, payload, priority})
end
end
end
def maybe_start_job(running_jobs, queue) do
if :sets.size(running_jobs) < Pleroma.Config.get([__MODULE__, :max_jobs]) && queue != [] do
{{type, payload}, queue} = queue_pop(queue)
{:ok, pid} = Task.start(fn -> handle(type, payload) end)
mref = Process.monitor(pid)
{:sets.add_element(mref, running_jobs), queue}
else
{running_jobs, queue}
end
end
def handle_cast({:enqueue, type, payload, _priority}, state)
when type in [:incoming_doc, :incoming_ap_doc] do
%{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state
i_queue = enqueue_sorted(i_queue, {type, payload}, 1)
{i_running_jobs, i_queue} = maybe_start_job(i_running_jobs, i_queue)
{:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}}
end
def handle_cast({:enqueue, type, payload, _priority}, state) do
%{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state
o_queue = enqueue_sorted(o_queue, {type, payload}, 1)
{o_running_jobs, o_queue} = maybe_start_job(o_running_jobs, o_queue)
{:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}}
end
def handle_cast(m, state) do
IO.inspect("Unknown: #{inspect(m)}, #{inspect(state)}")
{:noreply, state}
end
def handle_info({:DOWN, ref, :process, _pid, _reason}, state) do
%{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state
i_running_jobs = :sets.del_element(ref, i_running_jobs)
o_running_jobs = :sets.del_element(ref, o_running_jobs)
{i_running_jobs, i_queue} = maybe_start_job(i_running_jobs, i_queue)
{o_running_jobs, o_queue} = maybe_start_job(o_running_jobs, o_queue)
{:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}}
end
def enqueue_sorted(queue, element, priority) do
[%{item: element, priority: priority} | queue]
|> Enum.sort_by(fn %{priority: priority} -> priority end)
end
def queue_pop([%{item: element} | queue]) do
{element, queue}
end
def ap_enabled_actor(id) do def ap_enabled_actor(id) do
user = User.get_by_ap_id(id) user = User.get_by_ap_id(id)

View file

@ -5,8 +5,9 @@
# https://tools.ietf.org/html/draft-cavage-http-signatures-08 # https://tools.ietf.org/html/draft-cavage-http-signatures-08
defmodule Pleroma.Web.HTTPSignatures do defmodule Pleroma.Web.HTTPSignatures do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
require Logger require Logger
def split_signature(sig) do def split_signature(sig) do

View file

@ -4,34 +4,55 @@
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.{Repo, Object, Activity, User, Notification, Stats} alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Filter
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.Stats
alias Pleroma.User
alias Pleroma.Web alias Pleroma.Web
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.Push
alias Push.Subscription
alias Pleroma.Web.MastodonAPI.{ alias Pleroma.Web.MastodonAPI.AccountView
StatusView, alias Pleroma.Web.MastodonAPI.FilterView
AccountView, alias Pleroma.Web.MastodonAPI.ListView
MastodonView, alias Pleroma.Web.MastodonAPI.MastodonView
ListView, alias Pleroma.Web.MastodonAPI.PushSubscriptionView
FilterView, alias Pleroma.Web.MastodonAPI.StatusView
PushSubscriptionView alias Pleroma.Web.MastodonAPI.ReportView
}
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.OAuth.{Authorization, Token, App} alias Pleroma.Web.OAuth.App
alias Pleroma.Web.MediaProxy alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
import Ecto.Query import Ecto.Query
require Logger require Logger
@httpoison Application.get_env(:pleroma, :httpoison) @httpoison Application.get_env(:pleroma, :httpoison)
@local_mastodon_name "Mastodon-Local"
action_fallback(:errors) action_fallback(:errors)
def create_app(conn, params) do def create_app(conn, params) do
with cs <- App.register_changeset(%App{}, params) |> IO.inspect(), scopes = oauth_scopes(params, ["read"])
{:ok, app} <- Repo.insert(cs) |> IO.inspect() do
app_attrs =
params
|> Map.drop(["scope", "scopes"])
|> Map.put("scopes", scopes)
with cs <- App.register_changeset(%App{}, app_attrs),
false <- cs.changes[:client_name] == @local_mastodon_name,
{:ok, app} <- Repo.insert(cs) do
res = %{ res = %{
id: app.id |> to_string, id: app.id |> to_string,
name: app.client_name, name: app.client_name,
@ -129,7 +150,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
@mastodon_api_level "2.5.0" @mastodon_api_level "2.5.0"
def masto_instance(conn, _params) do def masto_instance(conn, _params) do
instance = Pleroma.Config.get(:instance) instance = Config.get(:instance)
response = %{ response = %{
uri: Web.base_url(), uri: Web.base_url(),
@ -222,10 +243,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
params params
|> Map.put("type", ["Create", "Announce"]) |> Map.put("type", ["Create", "Announce"])
|> Map.put("blocking_user", user) |> Map.put("blocking_user", user)
|> Map.put("muting_user", user)
|> Map.put("user", user) |> Map.put("user", user)
activities = activities =
ActivityPub.fetch_activities([user.ap_id | user.following], params) [user.ap_id | user.following]
|> ActivityPub.fetch_activities(params)
|> ActivityPub.contain_timeline(user) |> ActivityPub.contain_timeline(user)
|> Enum.reverse() |> Enum.reverse()
@ -238,14 +261,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def public_timeline(%{assigns: %{user: user}} = conn, params) do def public_timeline(%{assigns: %{user: user}} = conn, params) do
local_only = params["local"] in [true, "True", "true", "1"] local_only = params["local"] in [true, "True", "true", "1"]
params = activities =
params params
|> Map.put("type", ["Create", "Announce"]) |> Map.put("type", ["Create", "Announce"])
|> Map.put("local_only", local_only) |> Map.put("local_only", local_only)
|> Map.put("blocking_user", user) |> Map.put("blocking_user", user)
|> Map.put("muting_user", user)
activities = |> ActivityPub.fetch_public_activities()
ActivityPub.fetch_public_activities(params)
|> Enum.reverse() |> Enum.reverse()
conn conn
@ -286,7 +308,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Repo.get(Activity, id), with %Activity{} = activity <- Repo.get(Activity, id),
true <- ActivityPub.visible_for_user?(activity, user) do true <- Visibility.visible_for_user?(activity, user) do
conn conn
|> put_view(StatusView) |> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user}) |> try_render("status.json", %{activity: activity, for: user})
@ -314,6 +336,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
as: :activity as: :activity
) )
|> Enum.reverse(), |> Enum.reverse(),
# credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart
descendants: descendants:
StatusView.render( StatusView.render(
"index.json", "index.json",
@ -322,6 +345,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
as: :activity as: :activity
) )
|> Enum.reverse() |> Enum.reverse()
# credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart
} }
json(conn, result) json(conn, result)
@ -426,7 +450,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Repo.get(Activity, id), with %Activity{} = activity <- Repo.get(Activity, id),
%User{} = user <- User.get_by_nickname(user.nickname), %User{} = user <- User.get_by_nickname(user.nickname),
true <- ActivityPub.visible_for_user?(activity, user), true <- Visibility.visible_for_user?(activity, user),
{:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do {:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do
conn conn
|> put_view(StatusView) |> put_view(StatusView)
@ -437,7 +461,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Repo.get(Activity, id), with %Activity{} = activity <- Repo.get(Activity, id),
%User{} = user <- User.get_by_nickname(user.nickname), %User{} = user <- User.get_by_nickname(user.nickname),
true <- ActivityPub.visible_for_user?(activity, user), true <- Visibility.visible_for_user?(activity, user),
{:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do {:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do
conn conn
|> put_view(StatusView) |> put_view(StatusView)
@ -445,13 +469,37 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end end
end end
def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
activity = Activity.get_by_id(id)
with {:ok, activity} <- CommonAPI.add_mute(user, activity) do
conn
|> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
else
{:error, reason} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(:bad_request, Jason.encode!(%{"error" => reason}))
end
end
def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
activity = Activity.get_by_id(id)
with {:ok, activity} <- CommonAPI.remove_mute(user, activity) do
conn
|> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
end
end
def notifications(%{assigns: %{user: user}} = conn, params) do def notifications(%{assigns: %{user: user}} = conn, params) do
notifications = Notification.for_user(user, params) notifications = Notification.for_user(user, params)
result = result =
Enum.map(notifications, fn x -> notifications
render_notification(user, x) |> Enum.map(fn x -> render_notification(user, x) end)
end)
|> Enum.filter(& &1) |> Enum.filter(& &1)
conn conn
@ -580,17 +628,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
[] []
|> Enum.map(&String.downcase(&1)) |> Enum.map(&String.downcase(&1))
query_params = activities =
params params
|> Map.put("type", "Create") |> Map.put("type", "Create")
|> Map.put("local_only", local_only) |> Map.put("local_only", local_only)
|> Map.put("blocking_user", user) |> Map.put("blocking_user", user)
|> Map.put("muting_user", user)
|> Map.put("tag", tags) |> Map.put("tag", tags)
|> Map.put("tag_all", tag_all) |> Map.put("tag_all", tag_all)
|> Map.put("tag_reject", tag_reject) |> Map.put("tag_reject", tag_reject)
|> ActivityPub.fetch_public_activities()
activities =
ActivityPub.fetch_public_activities(query_params)
|> Enum.reverse() |> Enum.reverse()
conn conn
@ -647,7 +694,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
{:ok, _activity} <- {:ok, _activity} <-
ActivityPub.accept(%{ ActivityPub.accept(%{
to: [follower.ap_id], to: [follower.ap_id],
actor: followed.ap_id, actor: followed,
object: follow_activity.data["id"], object: follow_activity.data["id"],
type: "Accept" type: "Accept"
}) do }) do
@ -669,7 +716,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
{:ok, _activity} <- {:ok, _activity} <-
ActivityPub.reject(%{ ActivityPub.reject(%{
to: [follower.ap_id], to: [follower.ap_id],
actor: followed.ap_id, actor: followed,
object: follow_activity.data["id"], object: follow_activity.data["id"],
type: "Reject" type: "Reject"
}) do }) do
@ -690,7 +737,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
{:ok, _activity} <- ActivityPub.follow(follower, followed), {:ok, _activity} <- ActivityPub.follow(follower, followed),
{:ok, follower, followed} <- {:ok, follower, followed} <-
User.wait_and_refresh( User.wait_and_refresh(
Pleroma.Config.get([:activitypub, :follow_handshake_timeout]), Config.get([:activitypub, :follow_handshake_timeout]),
follower, follower,
followed followed
) do ) do
@ -730,6 +777,41 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end end
end end
def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
with %User{} = muted <- Repo.get(User, id),
{:ok, muter} <- User.mute(muter, muted) do
conn
|> put_view(AccountView)
|> render("relationship.json", %{user: muter, target: muted})
else
{:error, message} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{"error" => message}))
end
end
def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
with %User{} = muted <- Repo.get(User, id),
{:ok, muter} <- User.unmute(muter, muted) do
conn
|> put_view(AccountView)
|> render("relationship.json", %{user: muter, target: muted})
else
{:error, message} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{"error" => message}))
end
end
def mutes(%{assigns: %{user: user}} = conn, _) do
with muted_accounts <- User.muted_users(user) do
res = AccountView.render("accounts.json", users: muted_accounts, for: user, as: :user)
json(conn, res)
end
end
def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
with %User{} = blocked <- Repo.get(User, id), with %User{} = blocked <- Repo.get(User, id),
{:ok, blocker} <- User.block(blocker, blocked), {:ok, blocker} <- User.block(blocker, blocked),
@ -786,7 +868,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
if Regex.match?(~r/https?:/, query) do if Regex.match?(~r/https?:/, query) do
with {:ok, object} <- ActivityPub.fetch_object_from_id(query), with {:ok, object} <- ActivityPub.fetch_object_from_id(query),
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
true <- ActivityPub.visible_for_user?(activity, user) do true <- Visibility.visible_for_user?(activity, user) do
[activity] [activity]
else else
_e -> [] _e -> []
@ -819,7 +901,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
tags_path = Web.base_url() <> "/tag/" tags_path = Web.base_url() <> "/tag/"
tags = tags =
String.split(query) query
|> String.split()
|> Enum.uniq() |> Enum.uniq()
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end) |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|> Enum.map(fn tag -> String.slice(tag, 1..-1) end) |> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
@ -841,7 +924,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
statuses = status_search(user, query) statuses = status_search(user, query)
tags = tags =
String.split(query) query
|> String.split()
|> Enum.uniq() |> Enum.uniq()
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end) |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|> Enum.map(fn tag -> String.slice(tag, 1..-1) end) |> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
@ -865,14 +949,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end end
def favourites(%{assigns: %{user: user}} = conn, params) do def favourites(%{assigns: %{user: user}} = conn, params) do
params = activities =
params params
|> Map.put("type", "Create") |> Map.put("type", "Create")
|> Map.put("favorited_by", user.ap_id) |> Map.put("favorited_by", user.ap_id)
|> Map.put("blocking_user", user) |> Map.put("blocking_user", user)
|> ActivityPub.fetch_public_activities()
activities =
ActivityPub.fetch_public_activities(params)
|> Enum.reverse() |> Enum.reverse()
conn conn
@ -985,15 +1067,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
params params
|> Map.put("type", "Create") |> Map.put("type", "Create")
|> Map.put("blocking_user", user) |> Map.put("blocking_user", user)
|> Map.put("muting_user", user)
# we must filter the following list for the user to avoid leaking statuses the user # we must filter the following list for the user to avoid leaking statuses the user
# does not actually have permission to see (for more info, peruse security issue #270). # does not actually have permission to see (for more info, peruse security issue #270).
following_to = activities =
following following
|> Enum.filter(fn x -> x in user.following end) |> Enum.filter(fn x -> x in user.following end)
|> ActivityPub.fetch_activities_bounded(following, params)
activities =
ActivityPub.fetch_activities_bounded(following_to, following, params)
|> Enum.reverse() |> Enum.reverse()
conn conn
@ -1015,11 +1096,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
if user && token do if user && token do
mastodon_emoji = mastodonized_emoji() mastodon_emoji = mastodonized_emoji()
limit = Pleroma.Config.get([:instance, :limit]) limit = Config.get([:instance, :limit])
accounts = accounts =
Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user})) Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user}))
flavour = get_user_flavour(user)
initial_state = initial_state =
%{ %{
meta: %{ meta: %{
@ -1039,8 +1122,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
max_toot_chars: limit max_toot_chars: limit
}, },
rights: %{ rights: %{
delete_others_notice: !!user.info.is_moderator, delete_others_notice: present?(user.info.is_moderator),
admin: !!user.info.is_admin admin: present?(user.info.is_admin)
}, },
compose: %{ compose: %{
me: "#{user.id}", me: "#{user.id}",
@ -1104,7 +1187,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
conn conn
|> put_layout(false) |> put_layout(false)
|> put_view(MastodonView) |> put_view(MastodonView)
|> render("index.html", %{initial_state: initial_state}) |> render("index.html", %{initial_state: initial_state, flavour: flavour})
else else
conn conn
|> redirect(to: "/web/login") |> redirect(to: "/web/login")
@ -1126,6 +1209,43 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end end
end end
@supported_flavours ["glitch", "vanilla"]
def set_flavour(%{assigns: %{user: user}} = conn, %{"flavour" => flavour} = _params)
when flavour in @supported_flavours do
flavour_cng = User.Info.mastodon_flavour_update(user.info, flavour)
with changeset <- Ecto.Changeset.change(user),
changeset <- Ecto.Changeset.put_embed(changeset, :info, flavour_cng),
{:ok, user} <- User.update_and_set_cache(changeset),
flavour <- user.info.flavour do
json(conn, flavour)
else
e ->
conn
|> put_resp_content_type("application/json")
|> send_resp(500, Jason.encode!(%{"error" => inspect(e)}))
end
end
def set_flavour(conn, _params) do
conn
|> put_status(400)
|> json(%{error: "Unsupported flavour"})
end
def get_flavour(%{assigns: %{user: user}} = conn, _params) do
json(conn, get_user_flavour(user))
end
defp get_user_flavour(%User{info: %{flavour: flavour}}) when flavour in @supported_flavours do
flavour
end
defp get_user_flavour(_) do
"glitch"
end
def login(conn, %{"code" => code}) do def login(conn, %{"code" => code}) do
with {:ok, app} <- get_or_make_app(), with {:ok, app} <- get_or_make_app(),
%Authorization{} = auth <- Repo.get_by(Authorization, token: code, app_id: app.id), %Authorization{} = auth <- Repo.get_by(Authorization, token: code, app_id: app.id),
@ -1145,7 +1265,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
response_type: "code", response_type: "code",
client_id: app.client_id, client_id: app.client_id,
redirect_uri: ".", redirect_uri: ".",
scope: app.scopes scope: Enum.join(app.scopes, " ")
) )
conn conn
@ -1154,16 +1274,27 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end end
defp get_or_make_app() do defp get_or_make_app() do
with %App{} = app <- Repo.get_by(App, client_name: "Mastodon-Local") do find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."}
scopes = ["read", "write", "follow", "push"]
with %App{} = app <- Repo.get_by(App, find_attrs) do
{:ok, app} =
if app.scopes == scopes do
{:ok, app}
else
app
|> Ecto.Changeset.change(%{scopes: scopes})
|> Repo.update()
end
{:ok, app} {:ok, app}
else else
_e -> _e ->
cs = cs =
App.register_changeset(%App{}, %{ App.register_changeset(
client_name: "Mastodon-Local", %App{},
redirect_uris: ".", Map.put(find_attrs, :scopes, scopes)
scopes: "read,write,follow" )
})
Repo.insert(cs) Repo.insert(cs)
end end
@ -1235,7 +1366,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end end
def get_filters(%{assigns: %{user: user}} = conn, _) do def get_filters(%{assigns: %{user: user}} = conn, _) do
filters = Pleroma.Filter.get_filters(user) filters = Filter.get_filters(user)
res = FilterView.render("filters.json", filters: filters) res = FilterView.render("filters.json", filters: filters)
json(conn, res) json(conn, res)
end end
@ -1244,7 +1375,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
%{assigns: %{user: user}} = conn, %{assigns: %{user: user}} = conn,
%{"phrase" => phrase, "context" => context} = params %{"phrase" => phrase, "context" => context} = params
) do ) do
query = %Pleroma.Filter{ query = %Filter{
user_id: user.id, user_id: user.id,
phrase: phrase, phrase: phrase,
context: context, context: context,
@ -1253,13 +1384,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
# expires_at # expires_at
} }
{:ok, response} = Pleroma.Filter.create(query) {:ok, response} = Filter.create(query)
res = FilterView.render("filter.json", filter: response) res = FilterView.render("filter.json", filter: response)
json(conn, res) json(conn, res)
end end
def get_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do def get_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
filter = Pleroma.Filter.get(filter_id, user) filter = Filter.get(filter_id, user)
res = FilterView.render("filter.json", filter: filter) res = FilterView.render("filter.json", filter: filter)
json(conn, res) json(conn, res)
end end
@ -1268,7 +1399,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
%{assigns: %{user: user}} = conn, %{assigns: %{user: user}} = conn,
%{"phrase" => phrase, "context" => context, "id" => filter_id} = params %{"phrase" => phrase, "context" => context, "id" => filter_id} = params
) do ) do
query = %Pleroma.Filter{ query = %Filter{
user_id: user.id, user_id: user.id,
filter_id: filter_id, filter_id: filter_id,
phrase: phrase, phrase: phrase,
@ -1278,32 +1409,32 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
# expires_at # expires_at
} }
{:ok, response} = Pleroma.Filter.update(query) {:ok, response} = Filter.update(query)
res = FilterView.render("filter.json", filter: response) res = FilterView.render("filter.json", filter: response)
json(conn, res) json(conn, res)
end end
def delete_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do def delete_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
query = %Pleroma.Filter{ query = %Filter{
user_id: user.id, user_id: user.id,
filter_id: filter_id filter_id: filter_id
} }
{:ok, _} = Pleroma.Filter.delete(query) {:ok, _} = Filter.delete(query)
json(conn, %{}) json(conn, %{})
end end
def create_push_subscription(%{assigns: %{user: user, token: token}} = conn, params) do def create_push_subscription(%{assigns: %{user: user, token: token}} = conn, params) do
true = Pleroma.Web.Push.enabled() true = Push.enabled()
Pleroma.Web.Push.Subscription.delete_if_exists(user, token) Subscription.delete_if_exists(user, token)
{:ok, subscription} = Pleroma.Web.Push.Subscription.create(user, token, params) {:ok, subscription} = Subscription.create(user, token, params)
view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
json(conn, view) json(conn, view)
end end
def get_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do def get_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do
true = Pleroma.Web.Push.enabled() true = Push.enabled()
subscription = Pleroma.Web.Push.Subscription.get(user, token) subscription = Subscription.get(user, token)
view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
json(conn, view) json(conn, view)
end end
@ -1312,15 +1443,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
%{assigns: %{user: user, token: token}} = conn, %{assigns: %{user: user, token: token}} = conn,
params params
) do ) do
true = Pleroma.Web.Push.enabled() true = Push.enabled()
{:ok, subscription} = Pleroma.Web.Push.Subscription.update(user, token, params) {:ok, subscription} = Subscription.update(user, token, params)
view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
json(conn, view) json(conn, view)
end end
def delete_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do def delete_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do
true = Pleroma.Web.Push.enabled() true = Push.enabled()
{:ok, _response} = Pleroma.Web.Push.Subscription.delete(user, token) {:ok, _response} = Subscription.delete(user, token)
json(conn, %{}) json(conn, %{})
end end
@ -1331,17 +1462,21 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end end
def suggestions(%{assigns: %{user: user}} = conn, _) do def suggestions(%{assigns: %{user: user}} = conn, _) do
suggestions = Pleroma.Config.get(:suggestions) suggestions = Config.get(:suggestions)
if Keyword.get(suggestions, :enabled, false) do if Keyword.get(suggestions, :enabled, false) do
api = Keyword.get(suggestions, :third_party_engine, "") api = Keyword.get(suggestions, :third_party_engine, "")
timeout = Keyword.get(suggestions, :timeout, 5000) timeout = Keyword.get(suggestions, :timeout, 5000)
limit = Keyword.get(suggestions, :limit, 23) limit = Keyword.get(suggestions, :limit, 23)
host = Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) host = Config.get([Pleroma.Web.Endpoint, :url, :host])
user = user.nickname user = user.nickname
url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user)
url =
api
|> String.replace("{{host}}", host)
|> String.replace("{{user}}", user)
with {:ok, %{status: 200, body: body}} <- with {:ok, %{status: 200, body: body}} <-
@httpoison.get( @httpoison.get(
@ -1354,8 +1489,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
] ]
), ),
{:ok, data} <- Jason.decode(body) do {:ok, data} <- Jason.decode(body) do
data2 = data =
Enum.slice(data, 0, limit) data
|> Enum.slice(0, limit)
|> Enum.map(fn x -> |> Enum.map(fn x ->
Map.put( Map.put(
x, x,
@ -1374,7 +1510,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end) end)
conn conn
|> json(data2) |> json(data)
else else
e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}") e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
end end
@ -1383,9 +1519,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end end
end end
def status_card(conn, %{"id" => status_id}) do def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
with %Activity{} = activity <- Repo.get(Activity, status_id), with %Activity{} = activity <- Repo.get(Activity, status_id),
true <- ActivityPub.is_public?(activity) do true <- Visibility.visible_for_user?(activity, user) do
data = data =
StatusView.render( StatusView.render(
"card.json", "card.json",
@ -1399,6 +1535,20 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end end
end end
def reports(%{assigns: %{user: user}} = conn, params) do
case CommonAPI.report(user, params) do
{:ok, activity} ->
conn
|> put_view(ReportView)
|> try_render("report.json", %{activity: activity})
{:error, err} ->
conn
|> put_status(:bad_request)
|> json(%{error: err})
end
end
def try_render(conn, target, params) def try_render(conn, target, params)
when is_binary(target) do when is_binary(target) do
res = render(conn, target, params) res = render(conn, target, params)
@ -1417,4 +1567,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> put_status(501) |> put_status(501)
|> json(%{error: "Can't display this activity"}) |> json(%{error: "Can't display this activity"})
end end
defp present?(nil), do: false
defp present?(false), do: false
defp present?(_), do: true
end end

View file

@ -4,11 +4,12 @@
defmodule Pleroma.Web.MastodonAPI.AccountView do defmodule Pleroma.Web.MastodonAPI.AccountView do
use Pleroma.Web, :view use Pleroma.Web, :view
alias Pleroma.User
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MediaProxy
alias Pleroma.HTML alias Pleroma.HTML
alias Pleroma.User
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MediaProxy
def render("accounts.json", %{users: users} = opts) do def render("accounts.json", %{users: users} = opts) do
users users
@ -31,7 +32,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
} }
end end
def render("relationship.json", %{user: user, target: target}) do def render("relationship.json", %{user: nil, target: _target}) do
%{}
end
def render("relationship.json", %{user: %User{} = user, target: %User{} = target}) do
follow_activity = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, target) follow_activity = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, target)
requested = requested =
@ -46,7 +51,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
following: User.following?(user, target), following: User.following?(user, target),
followed_by: User.following?(target, user), followed_by: User.following?(target, user),
blocking: User.blocks?(user, target), blocking: User.blocks?(user, target),
muting: false, muting: User.mutes?(user, target),
muting_notifications: false, muting_notifications: false,
requested: requested, requested: requested,
domain_blocking: false, domain_blocking: false,
@ -84,6 +89,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for])) bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for]))
relationship = render("relationship.json", %{user: opts[:for], target: user})
%{ %{
id: to_string(user.id), id: to_string(user.id),
username: username_from_nickname(user.nickname), username: username_from_nickname(user.nickname),
@ -114,7 +121,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
confirmation_pending: user_info.confirmation_pending, confirmation_pending: user_info.confirmation_pending,
tags: user.tags, tags: user.tags,
is_moderator: user.info.is_moderator, is_moderator: user.info.is_moderator,
is_admin: user.info.is_admin is_admin: user.info.is_admin,
relationship: relationship
} }
} }
end end

View file

@ -4,8 +4,8 @@
defmodule Pleroma.Web.MastodonAPI.FilterView do defmodule Pleroma.Web.MastodonAPI.FilterView do
use Pleroma.Web, :view use Pleroma.Web, :view
alias Pleroma.Web.MastodonAPI.FilterView
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.FilterView
def render("filters.json", %{filters: filters} = opts) do def render("filters.json", %{filters: filters} = opts) do
render_many(filters, FilterView, "filter.json", opts) render_many(filters, FilterView, "filter.json", opts)

View file

@ -0,0 +1,14 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.ReportView do
use Pleroma.Web, :view
def render("report.json", %{activity: activity}) do
%{
id: to_string(activity.id),
action_taken: false
}
end
end

View file

@ -9,10 +9,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.HTML alias Pleroma.HTML
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.MediaProxy
# TODO: Add cached version. # TODO: Add cached version.
defp get_replied_to_activities(activities) do defp get_replied_to_activities(activities) do
@ -143,10 +144,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)) card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity))
url =
if user.local do
Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
else
object["external_url"] || object["id"]
end
%{ %{
id: to_string(activity.id), id: to_string(activity.id),
uri: object["id"], uri: object["id"],
url: object["external_url"] || object["id"], url: url,
account: AccountView.render("account.json", %{user: user}), account: AccountView.render("account.json", %{user: user}),
in_reply_to_id: reply_to && to_string(reply_to.id), in_reply_to_id: reply_to && to_string(reply_to.id),
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id), in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
@ -160,12 +168,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
reblogged: present?(repeated), reblogged: present?(repeated),
favourited: present?(favorited), favourited: present?(favorited),
bookmarked: present?(bookmarked), bookmarked: present?(bookmarked),
muted: false, muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user),
pinned: pinned?(activity, user), pinned: pinned?(activity, user),
sensitive: sensitive, sensitive: sensitive,
spoiler_text: object["summary"] || "", spoiler_text: object["summary"] || "",
visibility: get_visibility(object), visibility: get_visibility(object),
media_attachments: attachments |> Enum.take(4), media_attachments: attachments,
mentions: mentions, mentions: mentions,
tags: build_tags(tags), tags: build_tags(tags),
application: %{ application: %{

View file

@ -6,9 +6,10 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
require Logger require Logger
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
alias Pleroma.{User, Repo} alias Pleroma.Repo
alias Pleroma.User
@behaviour :cowboy_websocket_handler @behaviour :cowboy_websocket
@streams [ @streams [
"public", "public",
@ -25,37 +26,37 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
# Handled by periodic keepalive in Pleroma.Web.Streamer. # Handled by periodic keepalive in Pleroma.Web.Streamer.
@timeout :infinity @timeout :infinity
def init(_type, _req, _opts) do def init(%{qs: qs} = req, state) do
{:upgrade, :protocol, :cowboy_websocket} with params <- :cow_qs.parse_qs(qs),
end
def websocket_init(_type, req, _opts) do
with {qs, req} <- :cowboy_req.qs(req),
params <- :cow_qs.parse_qs(qs),
access_token <- List.keyfind(params, "access_token", 0), access_token <- List.keyfind(params, "access_token", 0),
{_, stream} <- List.keyfind(params, "stream", 0), {_, stream} <- List.keyfind(params, "stream", 0),
{:ok, user} <- allow_request(stream, access_token), {:ok, user} <- allow_request(stream, access_token),
topic when is_binary(topic) <- expand_topic(stream, params) do topic when is_binary(topic) <- expand_topic(stream, params) do
send(self(), :subscribe) {:cowboy_websocket, req, %{user: user, topic: topic}, %{idle_timeout: @timeout}}
{:ok, req, %{user: user, topic: topic}, @timeout}
else else
{:error, code} -> {:error, code} ->
Logger.debug("#{__MODULE__} denied connection: #{inspect(code)} - #{inspect(req)}") Logger.debug("#{__MODULE__} denied connection: #{inspect(code)} - #{inspect(req)}")
{:ok, req} = :cowboy_req.reply(code, req) {:ok, req} = :cowboy_req.reply(code, req)
{:shutdown, req} {:ok, req, state}
error -> error ->
Logger.debug("#{__MODULE__} denied connection: #{inspect(error)} - #{inspect(req)}") Logger.debug("#{__MODULE__} denied connection: #{inspect(error)} - #{inspect(req)}")
{:shutdown, req} {:ok, req} = :cowboy_req.reply(400, req)
{:ok, req, state}
end end
end end
# We never receive messages. def websocket_init(state) do
def websocket_handle(_frame, req, state) do send(self(), :subscribe)
{:ok, req, state} {:ok, state}
end end
def websocket_info(:subscribe, req, state) do # We never receive messages.
def websocket_handle(_frame, state) do
{:ok, state}
end
def websocket_info(:subscribe, state) do
Logger.debug( Logger.debug(
"#{__MODULE__} accepted websocket connection for user #{ "#{__MODULE__} accepted websocket connection for user #{
(state.user || %{id: "anonymous"}).id (state.user || %{id: "anonymous"}).id
@ -63,14 +64,14 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
) )
Pleroma.Web.Streamer.add_socket(state.topic, streamer_socket(state)) Pleroma.Web.Streamer.add_socket(state.topic, streamer_socket(state))
{:ok, req, state} {:ok, state}
end end
def websocket_info({:text, message}, req, state) do def websocket_info({:text, message}, state) do
{:reply, {:text, message}, req, state} {:reply, {:text, message}, state}
end end
def websocket_terminate(reason, _req, state) do def terminate(reason, _req, state) do
Logger.debug( Logger.debug(
"#{__MODULE__} terminating websocket connection for user #{ "#{__MODULE__} terminating websocket connection for user #{
(state.user || %{id: "anonymous"}).id (state.user || %{id: "anonymous"}).id

View file

@ -4,11 +4,12 @@
defmodule Pleroma.Web.MediaProxy.MediaProxyController do defmodule Pleroma.Web.MediaProxy.MediaProxyController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.{Web.MediaProxy, ReverseProxy} alias Pleroma.ReverseProxy
alias Pleroma.Web.MediaProxy
@default_proxy_opts [max_body_length: 25 * 1_048_576, http: [follow_redirect: true]] @default_proxy_opts [max_body_length: 25 * 1_048_576, http: [follow_redirect: true]]
def remote(conn, params = %{"sig" => sig64, "url" => url64}) do def remote(conn, %{"sig" => sig64, "url" => url64} = params) do
with config <- Pleroma.Config.get([:media_proxy], []), with config <- Pleroma.Config.get([:media_proxy], []),
true <- Keyword.get(config, :enabled, false), true <- Keyword.get(config, :enabled, false),
{:ok, url} <- MediaProxy.decode_url(sig64, url64), {:ok, url} <- MediaProxy.decode_url(sig64, url64),

View file

@ -9,7 +9,7 @@ defmodule Pleroma.Web.MediaProxy do
def url(""), do: nil def url(""), do: nil
def url(url = "/" <> _), do: url def url("/" <> _ = url), do: url
def url(url) do def url(url) do
config = Application.get_env(:pleroma, :media_proxy, []) config = Application.get_env(:pleroma, :media_proxy, [])
@ -19,11 +19,16 @@ defmodule Pleroma.Web.MediaProxy do
else else
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base] secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
# Must preserve `%2F` for compatibility with S3 (https://git.pleroma.social/pleroma/pleroma/issues/580)
replacement = get_replacement(url, ":2F:")
# The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice. # The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice.
base64 = base64 =
url url
|> String.replace("%2F", replacement)
|> URI.decode() |> URI.decode()
|> URI.encode() |> URI.encode()
|> String.replace(replacement, "%2F")
|> Base.url_encode64(@base64_opts) |> Base.url_encode64(@base64_opts)
sig = :crypto.hmac(:sha, secret, base64) sig = :crypto.hmac(:sha, secret, base64)
@ -60,4 +65,12 @@ defmodule Pleroma.Web.MediaProxy do
|> Enum.filter(fn value -> value end) |> Enum.filter(fn value -> value end)
|> Path.join() |> Path.join()
end end
defp get_replacement(url, replacement) do
if String.contains?(url, replacement) do
get_replacement(url, replacement <> replacement)
else
replacement
end
end
end end

View file

@ -3,10 +3,10 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.Providers.OpenGraph do defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
alias Pleroma.Web.Metadata.Providers.Provider alias Pleroma.User
alias Pleroma.Web.Metadata alias Pleroma.Web.Metadata
alias Pleroma.{HTML, Formatter, User} alias Pleroma.Web.Metadata.Providers.Provider
alias Pleroma.Web.MediaProxy alias Pleroma.Web.Metadata.Utils
@behaviour Provider @behaviour Provider
@ -17,7 +17,7 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
user: user user: user
}) do }) do
attachments = build_attachments(object) attachments = build_attachments(object)
scrubbed_content = scrub_html_and_truncate(object) scrubbed_content = Utils.scrub_html_and_truncate(object)
# Zero width space # Zero width space
content = content =
if scrubbed_content != "" and scrubbed_content != "\u200B" do if scrubbed_content != "" and scrubbed_content != "\u200B" do
@ -42,13 +42,14 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
{:meta, {:meta,
[ [
property: "og:description", property: "og:description",
content: "#{user_name_string(user)}" <> content content: "#{Utils.user_name_string(user)}" <> content
], []}, ], []},
{:meta, [property: "og:type", content: "website"], []} {:meta, [property: "og:type", content: "website"], []}
] ++ ] ++
if attachments == [] or Metadata.activity_nsfw?(object) do if attachments == [] or Metadata.activity_nsfw?(object) do
[ [
{:meta, [property: "og:image", content: attachment_url(User.avatar_url(user))], []}, {:meta, [property: "og:image", content: Utils.attachment_url(User.avatar_url(user))],
[]},
{:meta, [property: "og:image:width", content: 150], []}, {:meta, [property: "og:image:width", content: 150], []},
{:meta, [property: "og:image:height", content: 150], []} {:meta, [property: "og:image:height", content: 150], []}
] ]
@ -59,17 +60,17 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
@impl Provider @impl Provider
def build_tags(%{user: user}) do def build_tags(%{user: user}) do
with truncated_bio = scrub_html_and_truncate(user.bio || "") do with truncated_bio = Utils.scrub_html_and_truncate(user.bio || "") do
[ [
{:meta, {:meta,
[ [
property: "og:title", property: "og:title",
content: user_name_string(user) content: Utils.user_name_string(user)
], []}, ], []},
{:meta, [property: "og:url", content: User.profile_url(user)], []}, {:meta, [property: "og:url", content: User.profile_url(user)], []},
{:meta, [property: "og:description", content: truncated_bio], []}, {:meta, [property: "og:description", content: truncated_bio], []},
{:meta, [property: "og:type", content: "website"], []}, {:meta, [property: "og:type", content: "website"], []},
{:meta, [property: "og:image", content: attachment_url(User.avatar_url(user))], []}, {:meta, [property: "og:image", content: Utils.attachment_url(User.avatar_url(user))], []},
{:meta, [property: "og:image:width", content: 150], []}, {:meta, [property: "og:image:width", content: 150], []},
{:meta, [property: "og:image:height", content: 150], []} {:meta, [property: "og:image:height", content: 150], []}
] ]
@ -91,14 +92,15 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
case media_type do case media_type do
"audio" -> "audio" ->
[ [
{:meta, [property: "og:" <> media_type, content: attachment_url(url["href"])], []} {:meta,
[property: "og:" <> media_type, content: Utils.attachment_url(url["href"])], []}
| acc | acc
] ]
"image" -> "image" ->
[ [
{:meta, [property: "og:" <> media_type, content: attachment_url(url["href"])], {:meta,
[]}, [property: "og:" <> media_type, content: Utils.attachment_url(url["href"])], []},
{:meta, [property: "og:image:width", content: 150], []}, {:meta, [property: "og:image:width", content: 150], []},
{:meta, [property: "og:image:height", content: 150], []} {:meta, [property: "og:image:height", content: 150], []}
| acc | acc
@ -106,7 +108,8 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
"video" -> "video" ->
[ [
{:meta, [property: "og:" <> media_type, content: attachment_url(url["href"])], []} {:meta,
[property: "og:" <> media_type, content: Utils.attachment_url(url["href"])], []}
| acc | acc
] ]
@ -118,37 +121,4 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
acc ++ rendered_tags acc ++ rendered_tags
end) end)
end end
defp scrub_html_and_truncate(%{data: %{"content" => content}} = object) do
content
# html content comes from DB already encoded, decode first and scrub after
|> HtmlEntities.decode()
|> String.replace(~r/<br\s?\/?>/, " ")
|> HTML.get_cached_stripped_html_for_object(object, __MODULE__)
|> Formatter.demojify()
|> Formatter.truncate()
end
defp scrub_html_and_truncate(content) when is_binary(content) do
content
# html content comes from DB already encoded, decode first and scrub after
|> HtmlEntities.decode()
|> String.replace(~r/<br\s?\/?>/, " ")
|> HTML.strip_tags()
|> Formatter.demojify()
|> Formatter.truncate()
end
defp attachment_url(url) do
MediaProxy.url(url)
end
defp user_name_string(user) do
"#{user.name} " <>
if user.local do
"(@#{user.nickname}@#{Pleroma.Web.Endpoint.host()})"
else
"(@#{user.nickname})"
end
end
end end

View file

@ -0,0 +1,21 @@
defmodule Pleroma.Web.Metadata.PlayerView do
use Pleroma.Web, :view
import Phoenix.HTML.Tag, only: [content_tag: 3, tag: 2]
def render("player.html", %{"mediaType" => type, "href" => href}) do
{tag_type, tag_attrs} =
case type do
"audio" <> _ -> {:audio, []}
"video" <> _ -> {:video, [loop: true]}
end
content_tag(
tag_type,
[
tag(:source, src: href, type: type),
"Your browser does not support #{type} playback."
],
[controls: true] ++ tag_attrs
)
end
end

View file

@ -3,44 +3,120 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.Providers.TwitterCard do defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
alias Pleroma.Web.Metadata.Providers.Provider alias Pleroma.User
alias Pleroma.Web.Metadata alias Pleroma.Web.Metadata
alias Pleroma.Web.Metadata.Providers.Provider
alias Pleroma.Web.Metadata.Utils
@behaviour Provider @behaviour Provider
@impl Provider @impl Provider
def build_tags(%{object: object}) do def build_tags(%{
if Metadata.activity_nsfw?(object) or object.data["attachment"] == [] do activity_id: id,
build_tags(nil) object: object,
else user: user
case find_first_acceptable_media_type(object) do }) do
"image" -> attachments = build_attachments(id, object)
[{:meta, [property: "twitter:card", content: "summary_large_image"], []}] scrubbed_content = Utils.scrub_html_and_truncate(object)
# Zero width space
"audio" -> content =
[{:meta, [property: "twitter:card", content: "player"], []}] if scrubbed_content != "" and scrubbed_content != "\u200B" do
"" <> scrubbed_content <> ""
"video" -> else
[{:meta, [property: "twitter:card", content: "player"], []}] ""
end
_ ->
build_tags(nil) [
{:meta,
[
property: "twitter:title",
content: Utils.user_name_string(user)
], []},
{:meta,
[
property: "twitter:description",
content: content
], []}
] ++
if attachments == [] or Metadata.activity_nsfw?(object) do
[
{:meta,
[property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))], []},
{:meta, [property: "twitter:card", content: "summary_large_image"], []}
]
else
attachments
end end
end
end end
@impl Provider @impl Provider
def build_tags(_) do def build_tags(%{user: user}) do
[{:meta, [property: "twitter:card", content: "summary"], []}] with truncated_bio = Utils.scrub_html_and_truncate(user.bio || "") do
[
{:meta,
[
property: "twitter:title",
content: Utils.user_name_string(user)
], []},
{:meta, [property: "twitter:description", content: truncated_bio], []},
{:meta, [property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))],
[]},
{:meta, [property: "twitter:card", content: "summary"], []}
]
end
end end
def find_first_acceptable_media_type(%{data: %{"attachment" => attachment}}) do defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
Enum.find_value(attachment, fn attachment -> Enum.reduce(attachments, [], fn attachment, acc ->
Enum.find_value(attachment["url"], fn url -> rendered_tags =
Enum.find(["image", "audio", "video"], fn media_type -> Enum.reduce(attachment["url"], [], fn url, acc ->
String.starts_with?(url["mediaType"], media_type) media_type =
Enum.find(["image", "audio", "video"], fn media_type ->
String.starts_with?(url["mediaType"], media_type)
end)
# TODO: Add additional properties to objects when we have the data available.
case media_type do
"audio" ->
[
{:meta, [property: "twitter:card", content: "player"], []},
{:meta, [property: "twitter:player:width", content: "480"], []},
{:meta, [property: "twitter:player:height", content: "80"], []},
{:meta, [property: "twitter:player", content: player_url(id)], []}
| acc
]
"image" ->
[
{:meta, [property: "twitter:card", content: "summary_large_image"], []},
{:meta,
[
property: "twitter:player",
content: Utils.attachment_url(url["href"])
], []}
| acc
]
# TODO: Need the true width and height values here or Twitter renders an iFrame with a bad aspect ratio
"video" ->
[
{:meta, [property: "twitter:card", content: "player"], []},
{:meta, [property: "twitter:player", content: player_url(id)], []},
{:meta, [property: "twitter:player:width", content: "480"], []},
{:meta, [property: "twitter:player:height", content: "480"], []}
| acc
]
_ ->
acc
end
end) end)
end)
acc ++ rendered_tags
end) end)
end end
defp player_url(id) do
Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice_player, id)
end
end end

View file

@ -0,0 +1,42 @@
# Pleroma: A lightweight social networking server
# Copyright \xc2\xa9 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.Utils do
alias Pleroma.HTML
alias Pleroma.Formatter
alias Pleroma.Web.MediaProxy
def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do
content
# html content comes from DB already encoded, decode first and scrub after
|> HtmlEntities.decode()
|> String.replace(~r/<br\s?\/?>/, " ")
|> HTML.get_cached_stripped_html_for_object(object, __MODULE__)
|> Formatter.demojify()
|> Formatter.truncate()
end
def scrub_html_and_truncate(content) when is_binary(content) do
content
# html content comes from DB already encoded, decode first and scrub after
|> HtmlEntities.decode()
|> String.replace(~r/<br\s?\/?>/, " ")
|> HTML.strip_tags()
|> Formatter.demojify()
|> Formatter.truncate()
end
def attachment_url(url) do
MediaProxy.url(url)
end
def user_name_string(user) do
"#{user.name} " <>
if user.local do
"(@#{user.nickname}@#{Pleroma.Web.Endpoint.host()})"
else
"(@#{user.nickname})"
end
end
end

View file

@ -5,10 +5,11 @@
defmodule Pleroma.Web.Nodeinfo.NodeinfoController do defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Stats
alias Pleroma.Web
alias Pleroma.{User, Repo}
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Repo
alias Pleroma.Stats
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.MRF
plug(Pleroma.Web.FederatingPlug) plug(Pleroma.Web.FederatingPlug)
@ -32,7 +33,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
# returns a nodeinfo 2.0 map, since 2.1 just adds a repository field # returns a nodeinfo 2.0 map, since 2.1 just adds a repository field
# under software. # under software.
def raw_nodeinfo() do def raw_nodeinfo do
instance = Application.get_env(:pleroma, :instance) instance = Application.get_env(:pleroma, :instance)
media_proxy = Application.get_env(:pleroma, :media_proxy) media_proxy = Application.get_env(:pleroma, :media_proxy)
suggestions = Application.get_env(:pleroma, :suggestions) suggestions = Application.get_env(:pleroma, :suggestions)
@ -44,6 +45,33 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
Application.get_env(:pleroma, :mrf_simple) Application.get_env(:pleroma, :mrf_simple)
|> Enum.into(%{}) |> Enum.into(%{})
# This horror is needed to convert regex sigils to strings
mrf_keyword =
Application.get_env(:pleroma, :mrf_keyword, [])
|> Enum.map(fn {key, value} ->
{key,
Enum.map(value, fn
{pattern, replacement} ->
%{
"pattern" =>
if not is_binary(pattern) do
inspect(pattern)
else
pattern
end,
"replacement" => replacement
}
pattern ->
if not is_binary(pattern) do
inspect(pattern)
else
pattern
end
end)}
end)
|> Enum.into(%{})
mrf_policies = mrf_policies =
MRF.get_policies() MRF.get_policies()
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end) |> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
@ -66,13 +94,12 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
Config.get([:mrf_user_allowlist], []) Config.get([:mrf_user_allowlist], [])
|> Enum.into(%{}, fn {k, v} -> {k, length(v)} end) |> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
mrf_transparency = Keyword.get(instance, :mrf_transparency)
federation_response = federation_response =
if mrf_transparency do if Keyword.get(instance, :mrf_transparency) do
%{ %{
mrf_policies: mrf_policies, mrf_policies: mrf_policies,
mrf_simple: mrf_simple, mrf_simple: mrf_simple,
mrf_keyword: mrf_keyword,
mrf_user_allowlist: mrf_user_allowlist, mrf_user_allowlist: mrf_user_allowlist,
quarantined_instances: quarantined quarantined_instances: quarantined
} }

20
lib/pleroma/web/oauth.ex Normal file
View file

@ -0,0 +1,20 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth do
def parse_scopes(scopes, _default) when is_list(scopes) do
Enum.filter(scopes, &(&1 not in [nil, ""]))
end
def parse_scopes(scopes, default) when is_binary(scopes) do
scopes
|> String.trim()
|> String.split(~r/[\s,]+/)
|> parse_scopes(default)
end
def parse_scopes(_, default) do
default
end
end

View file

@ -4,12 +4,12 @@
defmodule Pleroma.Web.OAuth.App do defmodule Pleroma.Web.OAuth.App do
use Ecto.Schema use Ecto.Schema
import Ecto.{Changeset} import Ecto.Changeset
schema "apps" do schema "apps" do
field(:client_name, :string) field(:client_name, :string)
field(:redirect_uris, :string) field(:redirect_uris, :string)
field(:scopes, :string) field(:scopes, {:array, :string}, default: [])
field(:website, :string) field(:website, :string)
field(:client_id, :string) field(:client_id, :string)
field(:client_secret, :string) field(:client_secret, :string)
@ -25,8 +25,14 @@ defmodule Pleroma.Web.OAuth.App do
if changeset.valid? do if changeset.valid? do
changeset changeset
|> put_change(:client_id, :crypto.strong_rand_bytes(32) |> Base.url_encode64()) |> put_change(
|> put_change(:client_secret, :crypto.strong_rand_bytes(32) |> Base.url_encode64()) :client_id,
:crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
)
|> put_change(
:client_secret,
:crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
)
else else
changeset changeset
end end

View file

@ -5,13 +5,17 @@
defmodule Pleroma.Web.OAuth.Authorization do defmodule Pleroma.Web.OAuth.Authorization do
use Ecto.Schema use Ecto.Schema
alias Pleroma.{User, Repo} alias Pleroma.User
alias Pleroma.Web.OAuth.{Authorization, App} alias Pleroma.Repo
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.App
import Ecto.{Changeset, Query} import Ecto.Changeset
import Ecto.Query
schema "oauth_authorizations" do schema "oauth_authorizations" do
field(:token, :string) field(:token, :string)
field(:scopes, {:array, :string}, default: [])
field(:valid_until, :naive_datetime) field(:valid_until, :naive_datetime)
field(:used, :boolean, default: false) field(:used, :boolean, default: false)
belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId) belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId)
@ -20,14 +24,16 @@ defmodule Pleroma.Web.OAuth.Authorization do
timestamps() timestamps()
end end
def create_authorization(%App{} = app, %User{} = user) do def create_authorization(%App{} = app, %User{} = user, scopes \\ nil) do
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64() scopes = scopes || app.scopes
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
authorization = %Authorization{ authorization = %Authorization{
token: token, token: token,
used: false, used: false,
user_id: user.id, user_id: user.id,
app_id: app.id, app_id: app.id,
scopes: scopes,
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10) valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10)
} }

View file

@ -5,42 +5,56 @@
defmodule Pleroma.Web.OAuth.OAuthController do defmodule Pleroma.Web.OAuth.OAuthController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Web.OAuth.{Authorization, Token, App} alias Pleroma.Web.Auth.Authenticator
alias Pleroma.{Repo, User} alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OAuth.App
alias Pleroma.Repo
alias Pleroma.User
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
plug(:fetch_session) plug(:fetch_session)
plug(:fetch_flash) plug(:fetch_flash)
action_fallback(Pleroma.Web.OAuth.FallbackController) action_fallback(Pleroma.Web.OAuth.FallbackController)
def authorize(conn, params) do def authorize(conn, params) do
render(conn, "show.html", %{ app = Repo.get_by(App, client_id: params["client_id"])
available_scopes = (app && app.scopes) || []
scopes = oauth_scopes(params, nil) || available_scopes
render(conn, Authenticator.auth_template(), %{
response_type: params["response_type"], response_type: params["response_type"],
client_id: params["client_id"], client_id: params["client_id"],
scope: params["scope"], available_scopes: available_scopes,
scopes: scopes,
redirect_uri: params["redirect_uri"], redirect_uri: params["redirect_uri"],
state: params["state"] state: params["state"],
params: params
}) })
end end
def create_authorization(conn, %{ def create_authorization(conn, %{
"authorization" => "authorization" =>
%{ %{
"name" => name,
"password" => password,
"client_id" => client_id, "client_id" => client_id,
"redirect_uri" => redirect_uri "redirect_uri" => redirect_uri
} = params } = auth_params
}) do }) do
with %User{} = user <- User.get_by_nickname_or_email(name), with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},
true <- Pbkdf2.checkpw(password, user.password_hash),
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
%App{} = app <- Repo.get_by(App, client_id: client_id), %App{} = app <- Repo.get_by(App, client_id: client_id),
{:ok, auth} <- Authorization.create_authorization(app, user) do true <- redirect_uri in String.split(app.redirect_uris),
# Special case: Local MastodonFE. scopes <- oauth_scopes(auth_params, []),
{:unsupported_scopes, []} <- {:unsupported_scopes, scopes -- app.scopes},
# Note: `scope` param is intentionally not optional in this context
{:missing_scopes, false} <- {:missing_scopes, scopes == []},
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
{:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
redirect_uri = redirect_uri =
if redirect_uri == "." do if redirect_uri == "." do
# Special case: Local MastodonFE
mastodon_api_url(conn, :login) mastodon_api_url(conn, :login)
else else
redirect_uri redirect_uri
@ -58,8 +72,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
url_params = %{:code => auth.token} url_params = %{:code => auth.token}
url_params = url_params =
if params["state"] do if auth_params["state"] do
Map.put(url_params, :state, params["state"]) Map.put(url_params, :state, auth_params["state"])
else else
url_params url_params
end end
@ -69,19 +83,23 @@ defmodule Pleroma.Web.OAuth.OAuthController do
redirect(conn, external: url) redirect(conn, external: url)
end end
else else
{scopes_issue, _} when scopes_issue in [:unsupported_scopes, :missing_scopes] ->
conn
|> put_flash(:error, "Permissions not specified.")
|> put_status(:unauthorized)
|> authorize(auth_params)
{:auth_active, false} -> {:auth_active, false} ->
conn conn
|> put_flash(:error, "Account confirmation pending") |> put_flash(:error, "Account confirmation pending.")
|> put_status(:forbidden) |> put_status(:forbidden)
|> authorize(params) |> authorize(auth_params)
error -> error ->
error Authenticator.handle_error(conn, error)
end end
end end
# TODO
# - proper scope handling
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
with %App{} = app <- get_app_from_request(conn, params), with %App{} = app <- get_app_from_request(conn, params),
fixed_token = fix_padding(params["code"]), fixed_token = fix_padding(params["code"]),
@ -95,7 +113,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
refresh_token: token.refresh_token, refresh_token: token.refresh_token,
created_at: DateTime.to_unix(inserted_at), created_at: DateTime.to_unix(inserted_at),
expires_in: 60 * 10, expires_in: 60 * 10,
scope: "read write follow" scope: Enum.join(token.scopes, " ")
} }
json(conn, response) json(conn, response)
@ -106,8 +124,6 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end end
end end
# TODO
# - investigate a way to verify the user wants to grant read/write/follow once scope handling is done
def token_exchange( def token_exchange(
conn, conn,
%{"grant_type" => "password", "username" => name, "password" => password} = params %{"grant_type" => "password", "username" => name, "password" => password} = params
@ -116,14 +132,17 @@ defmodule Pleroma.Web.OAuth.OAuthController do
%User{} = user <- User.get_by_nickname_or_email(name), %User{} = user <- User.get_by_nickname_or_email(name),
true <- Pbkdf2.checkpw(password, user.password_hash), true <- Pbkdf2.checkpw(password, user.password_hash),
{:auth_active, true} <- {:auth_active, User.auth_active?(user)}, {:auth_active, true} <- {:auth_active, User.auth_active?(user)},
{:ok, auth} <- Authorization.create_authorization(app, user), scopes <- oauth_scopes(params, app.scopes),
[] <- scopes -- app.scopes,
true <- Enum.any?(scopes),
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
{:ok, token} <- Token.exchange_token(app, auth) do {:ok, token} <- Token.exchange_token(app, auth) do
response = %{ response = %{
token_type: "Bearer", token_type: "Bearer",
access_token: token.token, access_token: token.token,
refresh_token: token.refresh_token, refresh_token: token.refresh_token,
expires_in: 60 * 10, expires_in: 60 * 10,
scope: "read write follow" scope: Enum.join(token.scopes, " ")
} }
json(conn, response) json(conn, response)
@ -169,7 +188,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
token token
|> URI.decode() |> URI.decode()
|> Base.url_decode64!(padding: false) |> Base.url_decode64!(padding: false)
|> Base.url_encode64() |> Base.url_encode64(padding: false)
end end
defp get_app_from_request(conn, params) do defp get_app_from_request(conn, params) do

View file

@ -7,12 +7,16 @@ defmodule Pleroma.Web.OAuth.Token do
import Ecto.Query import Ecto.Query
alias Pleroma.{User, Repo} alias Pleroma.User
alias Pleroma.Web.OAuth.{Token, App, Authorization} alias Pleroma.Repo
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
schema "oauth_tokens" do schema "oauth_tokens" do
field(:token, :string) field(:token, :string)
field(:refresh_token, :string) field(:refresh_token, :string)
field(:scopes, {:array, :string}, default: [])
field(:valid_until, :naive_datetime) field(:valid_until, :naive_datetime)
belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId) belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId)
belongs_to(:app, App) belongs_to(:app, App)
@ -23,17 +27,19 @@ defmodule Pleroma.Web.OAuth.Token do
def exchange_token(app, auth) do def exchange_token(app, auth) do
with {:ok, auth} <- Authorization.use_token(auth), with {:ok, auth} <- Authorization.use_token(auth),
true <- auth.app_id == app.id do true <- auth.app_id == app.id do
create_token(app, Repo.get(User, auth.user_id)) create_token(app, Repo.get(User, auth.user_id), auth.scopes)
end end
end end
def create_token(%App{} = app, %User{} = user) do def create_token(%App{} = app, %User{} = user, scopes \\ nil) do
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64() scopes = scopes || app.scopes
refresh_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64() token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
refresh_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
token = %Token{ token = %Token{
token: token, token: token,
refresh_token: refresh_token, refresh_token: refresh_token,
scopes: scopes,
user_id: user.id, user_id: user.id,
app_id: app.id, app_id: app.id,
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10) valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10)
@ -44,9 +50,27 @@ defmodule Pleroma.Web.OAuth.Token do
def delete_user_tokens(%User{id: user_id}) do def delete_user_tokens(%User{id: user_id}) do
from( from(
t in Pleroma.Web.OAuth.Token, t in Token,
where: t.user_id == ^user_id where: t.user_id == ^user_id
) )
|> Repo.delete_all() |> Repo.delete_all()
end end
def delete_user_token(%User{id: user_id}, token_id) do
from(
t in Token,
where: t.user_id == ^user_id,
where: t.id == ^token_id
)
|> Repo.delete_all()
end
def get_user_tokens(%User{id: user_id}) do
from(
t in Token,
where: t.user_id == ^user_id
)
|> Repo.all()
|> Repo.preload(:app)
end
end end

View file

@ -3,8 +3,11 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OStatus.ActivityRepresenter do defmodule Pleroma.Web.OStatus.ActivityRepresenter do
alias Pleroma.{Activity, User, Object} alias Pleroma.Activity
alias Pleroma.User
alias Pleroma.Object
alias Pleroma.Web.OStatus.UserRepresenter alias Pleroma.Web.OStatus.UserRepresenter
require Logger require Logger
defp get_href(id) do defp get_href(id) do

View file

@ -3,10 +3,11 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OStatus.FeedRepresenter do defmodule Pleroma.Web.OStatus.FeedRepresenter do
alias Pleroma.Web.OStatus
alias Pleroma.Web.OStatus.{UserRepresenter, ActivityRepresenter}
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.OStatus
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
alias Pleroma.Web.OStatus.ActivityRepresenter
alias Pleroma.Web.OStatus.UserRepresenter
def to_simple_form(user, activities, _users) do def to_simple_form(user, activities, _users) do
most_recent_update = most_recent_update =

View file

@ -3,7 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OStatus.FollowHandler do defmodule Pleroma.Web.OStatus.FollowHandler do
alias Pleroma.Web.{XML, OStatus} alias Pleroma.Web.XML
alias Pleroma.Web.OStatus
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.User alias Pleroma.User

View file

@ -4,8 +4,10 @@
defmodule Pleroma.Web.OStatus.NoteHandler do defmodule Pleroma.Web.OStatus.NoteHandler do
require Logger require Logger
alias Pleroma.Web.{XML, OStatus} alias Pleroma.Web.OStatus
alias Pleroma.{Object, Activity} alias Pleroma.Web.XML
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI

View file

@ -3,7 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OStatus.UnfollowHandler do defmodule Pleroma.Web.OStatus.UnfollowHandler do
alias Pleroma.Web.{XML, OStatus} alias Pleroma.Web.XML
alias Pleroma.Web.OStatus
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.User alias Pleroma.User

View file

@ -9,11 +9,19 @@ defmodule Pleroma.Web.OStatus do
import Pleroma.Web.XML import Pleroma.Web.XML
require Logger require Logger
alias Pleroma.{Repo, User, Web, Object, Activity} alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Object
alias Pleroma.Activity
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.{WebFinger, Websub}
alias Pleroma.Web.OStatus.{FollowHandler, UnfollowHandler, NoteHandler, DeleteHandler}
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.WebFinger
alias Pleroma.Web.Websub
alias Pleroma.Web.OStatus.FollowHandler
alias Pleroma.Web.OStatus.UnfollowHandler
alias Pleroma.Web.OStatus.NoteHandler
alias Pleroma.Web.OStatus.DeleteHandler
def is_representable?(%Activity{data: data}) do def is_representable?(%Activity{data: data}) do
object = Object.normalize(data["object"]) object = Object.normalize(data["object"])

View file

@ -5,13 +5,18 @@
defmodule Pleroma.Web.OStatus.OStatusController do defmodule Pleroma.Web.OStatus.OStatusController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.{User, Activity, Object} alias Pleroma.Activity
alias Pleroma.Web.OStatus.{FeedRepresenter, ActivityRepresenter} alias Pleroma.Object
alias Pleroma.Web.{OStatus, Federator} alias Pleroma.User
alias Pleroma.Web.XML
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.OStatus.ActivityRepresenter
alias Pleroma.Web.OStatus.FeedRepresenter
alias Pleroma.Web.Federator
alias Pleroma.Web.OStatus
alias Pleroma.Web.XML
plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming]) plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])
@ -29,6 +34,9 @@ defmodule Pleroma.Web.OStatus.OStatusController do
"activity+json" -> "activity+json" ->
ActivityPubController.call(conn, :user) ActivityPubController.call(conn, :user)
"json" ->
ActivityPubController.call(conn, :user)
_ -> _ ->
with %User{} = user <- User.get_cached_by_nickname(nickname) do with %User{} = user <- User.get_cached_by_nickname(nickname) do
redirect(conn, external: OStatus.feed_path(user)) redirect(conn, external: OStatus.feed_path(user))
@ -83,19 +91,19 @@ defmodule Pleroma.Web.OStatus.OStatusController do
{:ok, body, _conn} = read_body(conn) {:ok, body, _conn} = read_body(conn)
{:ok, doc} = decode_or_retry(body) {:ok, doc} = decode_or_retry(body)
Federator.enqueue(:incoming_doc, doc) Federator.incoming_doc(doc)
conn conn
|> send_resp(200, "") |> send_resp(200, "")
end end
def object(conn, %{"uuid" => uuid}) do def object(conn, %{"uuid" => uuid}) do
if get_format(conn) == "activity+json" do if get_format(conn) in ["activity+json", "json"] do
ActivityPubController.call(conn, :object) ActivityPubController.call(conn, :object)
else else
with id <- o_status_url(conn, :object, uuid), with id <- o_status_url(conn, :object, uuid),
{_, %Activity{} = activity} <- {:activity, Activity.get_create_by_object_ap_id(id)}, {_, %Activity{} = activity} <- {:activity, Activity.get_create_by_object_ap_id(id)},
{_, true} <- {:public?, ActivityPub.is_public?(activity)}, {_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case get_format(conn) do case get_format(conn) do
"html" -> redirect(conn, to: "/notice/#{activity.id}") "html" -> redirect(conn, to: "/notice/#{activity.id}")
@ -115,12 +123,12 @@ defmodule Pleroma.Web.OStatus.OStatusController do
end end
def activity(conn, %{"uuid" => uuid}) do def activity(conn, %{"uuid" => uuid}) do
if get_format(conn) == "activity+json" do if get_format(conn) in ["activity+json", "json"] do
ActivityPubController.call(conn, :activity) ActivityPubController.call(conn, :activity)
else else
with id <- o_status_url(conn, :activity, uuid), with id <- o_status_url(conn, :activity, uuid),
{_, %Activity{} = activity} <- {:activity, Activity.normalize(id)}, {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
{_, true} <- {:public?, ActivityPub.is_public?(activity)}, {_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case format = get_format(conn) do case format = get_format(conn) do
"html" -> redirect(conn, to: "/notice/#{activity.id}") "html" -> redirect(conn, to: "/notice/#{activity.id}")
@ -141,7 +149,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
def notice(conn, %{"id" => id}) do def notice(conn, %{"id" => id}) do
with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id(id)}, with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id(id)},
{_, true} <- {:public?, ActivityPub.is_public?(activity)}, {_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case format = get_format(conn) do case format = get_format(conn) do
"html" -> "html" ->
@ -149,6 +157,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
%Object{} = object = Object.normalize(activity.data["object"]) %Object{} = object = Object.normalize(activity.data["object"])
Fallback.RedirectController.redirector_with_meta(conn, %{ Fallback.RedirectController.redirector_with_meta(conn, %{
activity_id: activity.id,
object: object, object: object,
url: url:
Pleroma.Web.Router.Helpers.o_status_url( Pleroma.Web.Router.Helpers.o_status_url(
@ -180,6 +189,30 @@ defmodule Pleroma.Web.OStatus.OStatusController do
end end
end end
# Returns an HTML embedded <audio> or <video> player suitable for embed iframes.
def notice_player(conn, %{"id" => id}) do
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
true <- Visibility.is_public?(activity),
%Object{} = object <- Object.normalize(activity.data["object"]),
%{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object,
true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do
conn
|> put_layout(:metadata_player)
|> put_resp_header("x-frame-options", "ALLOW")
|> put_resp_header(
"content-security-policy",
"default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
)
|> put_view(Pleroma.Web.Metadata.PlayerView)
|> render("player.html", url)
else
_error ->
conn
|> put_status(404)
|> Fallback.RedirectController.redirector(nil, 404)
end
end
defp represent_activity( defp represent_activity(
conn, conn,
"activity+json", "activity+json",

View file

@ -5,7 +5,8 @@
defmodule Pleroma.Web.Push do defmodule Pleroma.Web.Push do
use GenServer use GenServer
alias Pleroma.{Repo, User} alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.Push.Subscription alias Pleroma.Web.Push.Subscription
require Logger require Logger

View file

@ -4,8 +4,11 @@
defmodule Pleroma.Web.Push.Subscription do defmodule Pleroma.Web.Push.Subscription do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Pleroma.{Repo, User}
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.Push.Subscription alias Pleroma.Web.Push.Subscription

View file

@ -3,7 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Helpers do defmodule Pleroma.Web.RichMedia.Helpers do
alias Pleroma.{Activity, Object, HTML} alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.HTML
alias Pleroma.Web.RichMedia.Parser alias Pleroma.Web.RichMedia.Parser
def fetch_data_for_activity(%Activity{} = activity) do def fetch_data_for_activity(%Activity{} = activity) do

View file

@ -9,6 +9,13 @@ defmodule Pleroma.Web.RichMedia.Parser do
Pleroma.Web.RichMedia.Parsers.OEmbed Pleroma.Web.RichMedia.Parsers.OEmbed
] ]
@hackney_options [
pool: :media,
timeout: 2_000,
recv_timeout: 2_000,
max_body: 2_000_000
]
def parse(nil), do: {:error, "No URL provided"} def parse(nil), do: {:error, "No URL provided"}
if Mix.env() == :test do if Mix.env() == :test do
@ -28,7 +35,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
defp parse_url(url) do defp parse_url(url) do
try do try do
{:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: [pool: :media]) {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options)
html |> maybe_parse() |> clean_parsed_data() |> check_parsed_data() html |> maybe_parse() |> clean_parsed_data() |> check_parsed_data()
rescue rescue

View file

@ -74,6 +74,29 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Plugs.EnsureUserKeyPlug) plug(Pleroma.Plugs.EnsureUserKeyPlug)
end end
pipeline :oauth_read_or_unauthenticated do
plug(Pleroma.Plugs.OAuthScopesPlug, %{
scopes: ["read"],
fallback: :proceed_unauthenticated
})
end
pipeline :oauth_read do
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["read"]})
end
pipeline :oauth_write do
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["write"]})
end
pipeline :oauth_follow do
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["follow"]})
end
pipeline :oauth_push do
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]})
end
pipeline :well_known do pipeline :well_known do
plug(:accepts, ["json", "jrd+json", "xml", "xrd+xml"]) plug(:accepts, ["json", "jrd+json", "xml", "xrd+xml"])
end end
@ -101,6 +124,7 @@ defmodule Pleroma.Web.Router do
scope "/api/pleroma", Pleroma.Web.TwitterAPI do scope "/api/pleroma", Pleroma.Web.TwitterAPI do
pipe_through(:pleroma_api) pipe_through(:pleroma_api)
get("/password_reset/:token", UtilController, :show_password_reset) get("/password_reset/:token", UtilController, :show_password_reset)
post("/password_reset", UtilController, :password_reset) post("/password_reset", UtilController, :password_reset)
get("/emoji", UtilController, :emoji) get("/emoji", UtilController, :emoji)
@ -113,8 +137,11 @@ defmodule Pleroma.Web.Router do
end end
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
pipe_through(:admin_api) pipe_through([:admin_api, :oauth_write])
get("/users", AdminAPIController, :list_users)
delete("/user", AdminAPIController, :user_delete) delete("/user", AdminAPIController, :user_delete)
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
post("/user", AdminAPIController, :user_create) post("/user", AdminAPIController, :user_create)
put("/users/tag", AdminAPIController, :tag_users) put("/users/tag", AdminAPIController, :tag_users)
delete("/users/tag", AdminAPIController, :untag_users) delete("/users/tag", AdminAPIController, :untag_users)
@ -124,6 +151,8 @@ defmodule Pleroma.Web.Router do
post("/permission_group/:nickname/:permission_group", AdminAPIController, :right_add) post("/permission_group/:nickname/:permission_group", AdminAPIController, :right_add)
delete("/permission_group/:nickname/:permission_group", AdminAPIController, :right_delete) delete("/permission_group/:nickname/:permission_group", AdminAPIController, :right_delete)
put("/activation_status/:nickname", AdminAPIController, :set_activation_status)
post("/relay", AdminAPIController, :relay_follow) post("/relay", AdminAPIController, :relay_follow)
delete("/relay", AdminAPIController, :relay_unfollow) delete("/relay", AdminAPIController, :relay_unfollow)
@ -135,17 +164,32 @@ defmodule Pleroma.Web.Router do
scope "/", Pleroma.Web.TwitterAPI do scope "/", Pleroma.Web.TwitterAPI do
pipe_through(:pleroma_html) pipe_through(:pleroma_html)
get("/ostatus_subscribe", UtilController, :remote_follow)
post("/ostatus_subscribe", UtilController, :do_remote_follow)
post("/main/ostatus", UtilController, :remote_subscribe) post("/main/ostatus", UtilController, :remote_subscribe)
get("/ostatus_subscribe", UtilController, :remote_follow)
scope [] do
pipe_through(:oauth_follow)
post("/ostatus_subscribe", UtilController, :do_remote_follow)
end
end end
scope "/api/pleroma", Pleroma.Web.TwitterAPI do scope "/api/pleroma", Pleroma.Web.TwitterAPI do
pipe_through(:authenticated_api) pipe_through(:authenticated_api)
post("/blocks_import", UtilController, :blocks_import)
post("/follow_import", UtilController, :follow_import) scope [] do
post("/change_password", UtilController, :change_password) pipe_through(:oauth_write)
post("/delete_account", UtilController, :delete_account)
post("/change_password", UtilController, :change_password)
post("/delete_account", UtilController, :delete_account)
end
scope [] do
pipe_through(:oauth_follow)
post("/blocks_import", UtilController, :blocks_import)
post("/follow_import", UtilController, :follow_import)
end
end end
scope "/oauth", Pleroma.Web.OAuth do scope "/oauth", Pleroma.Web.OAuth do
@ -158,119 +202,156 @@ defmodule Pleroma.Web.Router do
scope "/api/v1", Pleroma.Web.MastodonAPI do scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:authenticated_api) pipe_through(:authenticated_api)
patch("/accounts/update_credentials", MastodonAPIController, :update_credentials) scope [] do
get("/accounts/verify_credentials", MastodonAPIController, :verify_credentials) pipe_through(:oauth_read)
get("/accounts/relationships", MastodonAPIController, :relationships)
get("/accounts/search", MastodonAPIController, :account_search)
post("/accounts/:id/follow", MastodonAPIController, :follow)
post("/accounts/:id/unfollow", MastodonAPIController, :unfollow)
post("/accounts/:id/block", MastodonAPIController, :block)
post("/accounts/:id/unblock", MastodonAPIController, :unblock)
post("/accounts/:id/mute", MastodonAPIController, :relationship_noop)
post("/accounts/:id/unmute", MastodonAPIController, :relationship_noop)
get("/accounts/:id/lists", MastodonAPIController, :account_lists)
get("/follow_requests", MastodonAPIController, :follow_requests) get("/accounts/verify_credentials", MastodonAPIController, :verify_credentials)
post("/follow_requests/:id/authorize", MastodonAPIController, :authorize_follow_request)
post("/follow_requests/:id/reject", MastodonAPIController, :reject_follow_request)
post("/follows", MastodonAPIController, :follow) get("/accounts/relationships", MastodonAPIController, :relationships)
get("/accounts/search", MastodonAPIController, :account_search)
get("/blocks", MastodonAPIController, :blocks) get("/accounts/:id/lists", MastodonAPIController, :account_lists)
get("/mutes", MastodonAPIController, :empty_array) get("/follow_requests", MastodonAPIController, :follow_requests)
get("/blocks", MastodonAPIController, :blocks)
get("/mutes", MastodonAPIController, :mutes)
get("/timelines/home", MastodonAPIController, :home_timeline) get("/timelines/home", MastodonAPIController, :home_timeline)
get("/timelines/direct", MastodonAPIController, :dm_timeline)
get("/timelines/direct", MastodonAPIController, :dm_timeline) get("/favourites", MastodonAPIController, :favourites)
get("/bookmarks", MastodonAPIController, :bookmarks)
get("/favourites", MastodonAPIController, :favourites) post("/notifications/clear", MastodonAPIController, :clear_notifications)
get("/bookmarks", MastodonAPIController, :bookmarks) post("/notifications/dismiss", MastodonAPIController, :dismiss_notification)
get("/notifications", MastodonAPIController, :notifications)
get("/notifications/:id", MastodonAPIController, :get_notification)
post("/statuses", MastodonAPIController, :post_status) get("/lists", MastodonAPIController, :get_lists)
delete("/statuses/:id", MastodonAPIController, :delete_status) get("/lists/:id", MastodonAPIController, :get_list)
get("/lists/:id/accounts", MastodonAPIController, :list_accounts)
post("/statuses/:id/reblog", MastodonAPIController, :reblog_status) get("/domain_blocks", MastodonAPIController, :domain_blocks)
post("/statuses/:id/unreblog", MastodonAPIController, :unreblog_status)
post("/statuses/:id/favourite", MastodonAPIController, :fav_status)
post("/statuses/:id/unfavourite", MastodonAPIController, :unfav_status)
post("/statuses/:id/pin", MastodonAPIController, :pin_status)
post("/statuses/:id/unpin", MastodonAPIController, :unpin_status)
post("/statuses/:id/bookmark", MastodonAPIController, :bookmark_status)
post("/statuses/:id/unbookmark", MastodonAPIController, :unbookmark_status)
post("/notifications/clear", MastodonAPIController, :clear_notifications) get("/filters", MastodonAPIController, :get_filters)
post("/notifications/dismiss", MastodonAPIController, :dismiss_notification)
get("/notifications", MastodonAPIController, :notifications)
get("/notifications/:id", MastodonAPIController, :get_notification)
post("/media", MastodonAPIController, :upload) get("/suggestions", MastodonAPIController, :suggestions)
put("/media/:id", MastodonAPIController, :update_media)
get("/lists", MastodonAPIController, :get_lists) get("/endorsements", MastodonAPIController, :empty_array)
get("/lists/:id", MastodonAPIController, :get_list)
delete("/lists/:id", MastodonAPIController, :delete_list)
post("/lists", MastodonAPIController, :create_list)
put("/lists/:id", MastodonAPIController, :rename_list)
get("/lists/:id/accounts", MastodonAPIController, :list_accounts)
post("/lists/:id/accounts", MastodonAPIController, :add_to_list)
delete("/lists/:id/accounts", MastodonAPIController, :remove_from_list)
get("/domain_blocks", MastodonAPIController, :domain_blocks) get("/pleroma/flavour", MastodonAPIController, :get_flavour)
post("/domain_blocks", MastodonAPIController, :block_domain) end
delete("/domain_blocks", MastodonAPIController, :unblock_domain)
get("/filters", MastodonAPIController, :get_filters) scope [] do
post("/filters", MastodonAPIController, :create_filter) pipe_through(:oauth_write)
get("/filters/:id", MastodonAPIController, :get_filter)
put("/filters/:id", MastodonAPIController, :update_filter)
delete("/filters/:id", MastodonAPIController, :delete_filter)
post("/push/subscription", MastodonAPIController, :create_push_subscription) patch("/accounts/update_credentials", MastodonAPIController, :update_credentials)
get("/push/subscription", MastodonAPIController, :get_push_subscription)
put("/push/subscription", MastodonAPIController, :update_push_subscription)
delete("/push/subscription", MastodonAPIController, :delete_push_subscription)
get("/suggestions", MastodonAPIController, :suggestions) post("/statuses", MastodonAPIController, :post_status)
delete("/statuses/:id", MastodonAPIController, :delete_status)
get("/endorsements", MastodonAPIController, :empty_array) post("/statuses/:id/reblog", MastodonAPIController, :reblog_status)
post("/statuses/:id/unreblog", MastodonAPIController, :unreblog_status)
post("/statuses/:id/favourite", MastodonAPIController, :fav_status)
post("/statuses/:id/unfavourite", MastodonAPIController, :unfav_status)
post("/statuses/:id/pin", MastodonAPIController, :pin_status)
post("/statuses/:id/unpin", MastodonAPIController, :unpin_status)
post("/statuses/:id/bookmark", MastodonAPIController, :bookmark_status)
post("/statuses/:id/unbookmark", MastodonAPIController, :unbookmark_status)
post("/statuses/:id/mute", MastodonAPIController, :mute_conversation)
post("/statuses/:id/unmute", MastodonAPIController, :unmute_conversation)
post("/media", MastodonAPIController, :upload)
put("/media/:id", MastodonAPIController, :update_media)
delete("/lists/:id", MastodonAPIController, :delete_list)
post("/lists", MastodonAPIController, :create_list)
put("/lists/:id", MastodonAPIController, :rename_list)
post("/lists/:id/accounts", MastodonAPIController, :add_to_list)
delete("/lists/:id/accounts", MastodonAPIController, :remove_from_list)
post("/filters", MastodonAPIController, :create_filter)
get("/filters/:id", MastodonAPIController, :get_filter)
put("/filters/:id", MastodonAPIController, :update_filter)
delete("/filters/:id", MastodonAPIController, :delete_filter)
post("/pleroma/flavour/:flavour", MastodonAPIController, :set_flavour)
post("/reports", MastodonAPIController, :reports)
end
scope [] do
pipe_through(:oauth_follow)
post("/follows", MastodonAPIController, :follow)
post("/accounts/:id/follow", MastodonAPIController, :follow)
post("/accounts/:id/unfollow", MastodonAPIController, :unfollow)
post("/accounts/:id/block", MastodonAPIController, :block)
post("/accounts/:id/unblock", MastodonAPIController, :unblock)
post("/accounts/:id/mute", MastodonAPIController, :mute)
post("/accounts/:id/unmute", MastodonAPIController, :unmute)
post("/follow_requests/:id/authorize", MastodonAPIController, :authorize_follow_request)
post("/follow_requests/:id/reject", MastodonAPIController, :reject_follow_request)
post("/domain_blocks", MastodonAPIController, :block_domain)
delete("/domain_blocks", MastodonAPIController, :unblock_domain)
end
scope [] do
pipe_through(:oauth_push)
post("/push/subscription", MastodonAPIController, :create_push_subscription)
get("/push/subscription", MastodonAPIController, :get_push_subscription)
put("/push/subscription", MastodonAPIController, :update_push_subscription)
delete("/push/subscription", MastodonAPIController, :delete_push_subscription)
end
end end
scope "/api/web", Pleroma.Web.MastodonAPI do scope "/api/web", Pleroma.Web.MastodonAPI do
pipe_through(:authenticated_api) pipe_through([:authenticated_api, :oauth_write])
put("/settings", MastodonAPIController, :put_settings) put("/settings", MastodonAPIController, :put_settings)
end end
scope "/api/v1", Pleroma.Web.MastodonAPI do scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:api) pipe_through(:api)
get("/instance", MastodonAPIController, :masto_instance) get("/instance", MastodonAPIController, :masto_instance)
get("/instance/peers", MastodonAPIController, :peers) get("/instance/peers", MastodonAPIController, :peers)
post("/apps", MastodonAPIController, :create_app) post("/apps", MastodonAPIController, :create_app)
get("/custom_emojis", MastodonAPIController, :custom_emojis) get("/custom_emojis", MastodonAPIController, :custom_emojis)
get("/timelines/public", MastodonAPIController, :public_timeline)
get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline)
get("/timelines/list/:list_id", MastodonAPIController, :list_timeline)
get("/statuses/:id", MastodonAPIController, :get_status)
get("/statuses/:id/context", MastodonAPIController, :get_context)
get("/statuses/:id/card", MastodonAPIController, :status_card) get("/statuses/:id/card", MastodonAPIController, :status_card)
get("/statuses/:id/favourited_by", MastodonAPIController, :favourited_by) get("/statuses/:id/favourited_by", MastodonAPIController, :favourited_by)
get("/statuses/:id/reblogged_by", MastodonAPIController, :reblogged_by) get("/statuses/:id/reblogged_by", MastodonAPIController, :reblogged_by)
get("/accounts/:id/statuses", MastodonAPIController, :user_statuses)
get("/accounts/:id/followers", MastodonAPIController, :followers)
get("/accounts/:id/following", MastodonAPIController, :following)
get("/accounts/:id", MastodonAPIController, :user)
get("/trends", MastodonAPIController, :empty_array) get("/trends", MastodonAPIController, :empty_array)
get("/search", MastodonAPIController, :search) scope [] do
pipe_through(:oauth_read_or_unauthenticated)
get("/timelines/public", MastodonAPIController, :public_timeline)
get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline)
get("/timelines/list/:list_id", MastodonAPIController, :list_timeline)
get("/statuses/:id", MastodonAPIController, :get_status)
get("/statuses/:id/context", MastodonAPIController, :get_context)
get("/accounts/:id/statuses", MastodonAPIController, :user_statuses)
get("/accounts/:id/followers", MastodonAPIController, :followers)
get("/accounts/:id/following", MastodonAPIController, :following)
get("/accounts/:id", MastodonAPIController, :user)
get("/search", MastodonAPIController, :search)
end
end end
scope "/api/v2", Pleroma.Web.MastodonAPI do scope "/api/v2", Pleroma.Web.MastodonAPI do
pipe_through(:api) pipe_through([:api, :oauth_read_or_unauthenticated])
get("/search", MastodonAPIController, :search2) get("/search", MastodonAPIController, :search2)
end end
@ -287,19 +368,11 @@ defmodule Pleroma.Web.Router do
scope "/api", Pleroma.Web do scope "/api", Pleroma.Web do
pipe_through(:api) pipe_through(:api)
get("/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
get("/qvitter/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
get("/users/show", TwitterAPI.Controller, :show_user)
get("/statuses/followers", TwitterAPI.Controller, :followers)
get("/statuses/friends", TwitterAPI.Controller, :friends)
get("/statuses/blocks", TwitterAPI.Controller, :blocks)
get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status)
get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation)
post("/account/register", TwitterAPI.Controller, :register) post("/account/register", TwitterAPI.Controller, :register)
post("/account/password_reset", TwitterAPI.Controller, :password_reset) post("/account/password_reset", TwitterAPI.Controller, :password_reset)
post("/account/resend_confirmation_email", TwitterAPI.Controller, :resend_confirmation_email)
get( get(
"/account/confirm_email/:user_id/:token", "/account/confirm_email/:user_id/:token",
TwitterAPI.Controller, TwitterAPI.Controller,
@ -307,14 +380,26 @@ defmodule Pleroma.Web.Router do
as: :confirm_email as: :confirm_email
) )
post("/account/resend_confirmation_email", TwitterAPI.Controller, :resend_confirmation_email) scope [] do
pipe_through(:oauth_read_or_unauthenticated)
get("/search", TwitterAPI.Controller, :search) get("/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline) get("/qvitter/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
get("/users/show", TwitterAPI.Controller, :show_user)
get("/statuses/followers", TwitterAPI.Controller, :followers)
get("/statuses/friends", TwitterAPI.Controller, :friends)
get("/statuses/blocks", TwitterAPI.Controller, :blocks)
get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status)
get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation)
get("/search", TwitterAPI.Controller, :search)
get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline)
end
end end
scope "/api", Pleroma.Web do scope "/api", Pleroma.Web do
pipe_through(:api) pipe_through([:api, :oauth_read_or_unauthenticated])
get("/statuses/public_timeline", TwitterAPI.Controller, :public_timeline) get("/statuses/public_timeline", TwitterAPI.Controller, :public_timeline)
@ -328,73 +413,88 @@ defmodule Pleroma.Web.Router do
end end
scope "/api", Pleroma.Web, as: :twitter_api_search do scope "/api", Pleroma.Web, as: :twitter_api_search do
pipe_through(:api) pipe_through([:api, :oauth_read_or_unauthenticated])
get("/pleroma/search_user", TwitterAPI.Controller, :search_user) get("/pleroma/search_user", TwitterAPI.Controller, :search_user)
end end
scope "/api", Pleroma.Web, as: :authenticated_twitter_api do scope "/api", Pleroma.Web, as: :authenticated_twitter_api do
pipe_through(:authenticated_api) pipe_through(:authenticated_api)
get("/account/verify_credentials", TwitterAPI.Controller, :verify_credentials) get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens)
post("/account/verify_credentials", TwitterAPI.Controller, :verify_credentials) delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token)
post("/account/update_profile", TwitterAPI.Controller, :update_profile) scope [] do
post("/account/update_profile_banner", TwitterAPI.Controller, :update_banner) pipe_through(:oauth_read)
post("/qvitter/update_background_image", TwitterAPI.Controller, :update_background)
get("/statuses/home_timeline", TwitterAPI.Controller, :friends_timeline) get("/account/verify_credentials", TwitterAPI.Controller, :verify_credentials)
get("/statuses/friends_timeline", TwitterAPI.Controller, :friends_timeline) post("/account/verify_credentials", TwitterAPI.Controller, :verify_credentials)
get("/statuses/mentions", TwitterAPI.Controller, :mentions_timeline)
get("/statuses/mentions_timeline", TwitterAPI.Controller, :mentions_timeline)
get("/statuses/dm_timeline", TwitterAPI.Controller, :dm_timeline)
get("/qvitter/statuses/notifications", TwitterAPI.Controller, :notifications)
# XXX: this is really a pleroma API, but we want to keep the pleroma namespace clean get("/statuses/home_timeline", TwitterAPI.Controller, :friends_timeline)
# for now. get("/statuses/friends_timeline", TwitterAPI.Controller, :friends_timeline)
post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read) get("/statuses/mentions", TwitterAPI.Controller, :mentions_timeline)
get("/statuses/mentions_timeline", TwitterAPI.Controller, :mentions_timeline)
get("/statuses/dm_timeline", TwitterAPI.Controller, :dm_timeline)
get("/qvitter/statuses/notifications", TwitterAPI.Controller, :notifications)
post("/statuses/update", TwitterAPI.Controller, :status_update) get("/pleroma/friend_requests", TwitterAPI.Controller, :friend_requests)
post("/statuses/retweet/:id", TwitterAPI.Controller, :retweet)
post("/statuses/unretweet/:id", TwitterAPI.Controller, :unretweet)
post("/statuses/destroy/:id", TwitterAPI.Controller, :delete_post)
post("/statuses/pin/:id", TwitterAPI.Controller, :pin) get("/friends/ids", TwitterAPI.Controller, :friends_ids)
post("/statuses/unpin/:id", TwitterAPI.Controller, :unpin) get("/friendships/no_retweets/ids", TwitterAPI.Controller, :empty_array)
get("/pleroma/friend_requests", TwitterAPI.Controller, :friend_requests) get("/mutes/users/ids", TwitterAPI.Controller, :empty_array)
post("/pleroma/friendships/approve", TwitterAPI.Controller, :approve_friend_request) get("/qvitter/mutes", TwitterAPI.Controller, :raw_empty_array)
post("/pleroma/friendships/deny", TwitterAPI.Controller, :deny_friend_request)
post("/friendships/create", TwitterAPI.Controller, :follow) get("/externalprofile/show", TwitterAPI.Controller, :external_profile)
post("/friendships/destroy", TwitterAPI.Controller, :unfollow)
post("/blocks/create", TwitterAPI.Controller, :block)
post("/blocks/destroy", TwitterAPI.Controller, :unblock)
post("/statusnet/media/upload", TwitterAPI.Controller, :upload) post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read)
post("/media/upload", TwitterAPI.Controller, :upload_json) end
post("/media/metadata/create", TwitterAPI.Controller, :update_media)
post("/favorites/create/:id", TwitterAPI.Controller, :favorite) scope [] do
post("/favorites/create", TwitterAPI.Controller, :favorite) pipe_through(:oauth_write)
post("/favorites/destroy/:id", TwitterAPI.Controller, :unfavorite)
post("/qvitter/update_avatar", TwitterAPI.Controller, :update_avatar) post("/account/update_profile", TwitterAPI.Controller, :update_profile)
post("/account/update_profile_banner", TwitterAPI.Controller, :update_banner)
post("/qvitter/update_background_image", TwitterAPI.Controller, :update_background)
get("/friends/ids", TwitterAPI.Controller, :friends_ids) post("/statuses/update", TwitterAPI.Controller, :status_update)
get("/friendships/no_retweets/ids", TwitterAPI.Controller, :empty_array) post("/statuses/retweet/:id", TwitterAPI.Controller, :retweet)
post("/statuses/unretweet/:id", TwitterAPI.Controller, :unretweet)
post("/statuses/destroy/:id", TwitterAPI.Controller, :delete_post)
get("/mutes/users/ids", TwitterAPI.Controller, :empty_array) post("/statuses/pin/:id", TwitterAPI.Controller, :pin)
get("/qvitter/mutes", TwitterAPI.Controller, :raw_empty_array) post("/statuses/unpin/:id", TwitterAPI.Controller, :unpin)
get("/externalprofile/show", TwitterAPI.Controller, :external_profile) post("/statusnet/media/upload", TwitterAPI.Controller, :upload)
post("/media/upload", TwitterAPI.Controller, :upload_json)
post("/media/metadata/create", TwitterAPI.Controller, :update_media)
post("/favorites/create/:id", TwitterAPI.Controller, :favorite)
post("/favorites/create", TwitterAPI.Controller, :favorite)
post("/favorites/destroy/:id", TwitterAPI.Controller, :unfavorite)
post("/qvitter/update_avatar", TwitterAPI.Controller, :update_avatar)
end
scope [] do
pipe_through(:oauth_follow)
post("/pleroma/friendships/approve", TwitterAPI.Controller, :approve_friend_request)
post("/pleroma/friendships/deny", TwitterAPI.Controller, :deny_friend_request)
post("/friendships/create", TwitterAPI.Controller, :follow)
post("/friendships/destroy", TwitterAPI.Controller, :unfollow)
post("/blocks/create", TwitterAPI.Controller, :block)
post("/blocks/destroy", TwitterAPI.Controller, :unblock)
end
end end
pipeline :ap_relay do pipeline :ap_relay do
plug(:accepts, ["activity+json"]) plug(:accepts, ["activity+json", "json"])
end end
pipeline :ostatus do pipeline :ostatus do
plug(:accepts, ["html", "xml", "atom", "activity+json"]) plug(:accepts, ["html", "xml", "atom", "activity+json", "json"])
end end
pipeline :oembed do pipeline :oembed do
@ -407,6 +507,7 @@ defmodule Pleroma.Web.Router do
get("/objects/:uuid", OStatus.OStatusController, :object) get("/objects/:uuid", OStatus.OStatusController, :object)
get("/activities/:uuid", OStatus.OStatusController, :activity) get("/activities/:uuid", OStatus.OStatusController, :activity)
get("/notice/:id", OStatus.OStatusController, :notice) get("/notice/:id", OStatus.OStatusController, :notice)
get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player)
get("/users/:nickname/feed", OStatus.OStatusController, :feed) get("/users/:nickname/feed", OStatus.OStatusController, :feed)
get("/users/:nickname", OStatus.OStatusController, :feed_redirect) get("/users/:nickname", OStatus.OStatusController, :feed_redirect)
@ -423,7 +524,7 @@ defmodule Pleroma.Web.Router do
end end
pipeline :activitypub do pipeline :activitypub do
plug(:accepts, ["activity+json"]) plug(:accepts, ["activity+json", "json"])
plug(Pleroma.Web.Plugs.HTTPSignaturePlug) plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
end end
@ -438,7 +539,7 @@ defmodule Pleroma.Web.Router do
end end
pipeline :activitypub_client do pipeline :activitypub_client do
plug(:accepts, ["activity+json"]) plug(:accepts, ["activity+json", "json"])
plug(:fetch_session) plug(:fetch_session)
plug(Pleroma.Plugs.OAuthPlug) plug(Pleroma.Plugs.OAuthPlug)
plug(Pleroma.Plugs.BasicAuthDecoderPlug) plug(Pleroma.Plugs.BasicAuthDecoderPlug)
@ -454,9 +555,16 @@ defmodule Pleroma.Web.Router do
scope "/", Pleroma.Web.ActivityPub do scope "/", Pleroma.Web.ActivityPub do
pipe_through([:activitypub_client]) pipe_through([:activitypub_client])
get("/api/ap/whoami", ActivityPubController, :whoami) scope [] do
get("/users/:nickname/inbox", ActivityPubController, :read_inbox) pipe_through(:oauth_read)
post("/users/:nickname/outbox", ActivityPubController, :update_outbox) get("/api/ap/whoami", ActivityPubController, :whoami)
get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
end
scope [] do
pipe_through(:oauth_write)
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
end
end end
scope "/relay", Pleroma.Web.ActivityPub do scope "/relay", Pleroma.Web.ActivityPub do
@ -466,8 +574,8 @@ defmodule Pleroma.Web.Router do
scope "/", Pleroma.Web.ActivityPub do scope "/", Pleroma.Web.ActivityPub do
pipe_through(:activitypub) pipe_through(:activitypub)
post("/users/:nickname/inbox", ActivityPubController, :inbox)
post("/inbox", ActivityPubController, :inbox) post("/inbox", ActivityPubController, :inbox)
post("/users/:nickname/inbox", ActivityPubController, :inbox)
end end
scope "/.well-known", Pleroma.Web do scope "/.well-known", Pleroma.Web do
@ -486,9 +594,12 @@ defmodule Pleroma.Web.Router do
pipe_through(:mastodon_html) pipe_through(:mastodon_html)
get("/web/login", MastodonAPIController, :login) get("/web/login", MastodonAPIController, :login)
post("/web/login", MastodonAPIController, :login_post)
get("/web/*path", MastodonAPIController, :index)
delete("/auth/sign_out", MastodonAPIController, :logout) delete("/auth/sign_out", MastodonAPIController, :logout)
scope [] do
pipe_through(:oauth_read_or_unauthenticated)
get("/web/*path", MastodonAPIController, :index)
end
end end
pipeline :remote_media do pipeline :remote_media do
@ -496,6 +607,7 @@ defmodule Pleroma.Web.Router do
scope "/proxy/", Pleroma.Web.MediaProxy do scope "/proxy/", Pleroma.Web.MediaProxy do
pipe_through(:remote_media) pipe_through(:remote_media)
get("/:sig/:url", MediaProxyController, :remote) get("/:sig/:url", MediaProxyController, :remote)
get("/:sig/:url/:filename", MediaProxyController, :remote) get("/:sig/:url/:filename", MediaProxyController, :remote)
end end

View file

@ -6,10 +6,12 @@ defmodule Pleroma.Web.Salmon do
@httpoison Application.get_env(:pleroma, :httpoison) @httpoison Application.get_env(:pleroma, :httpoison)
use Bitwise use Bitwise
alias Pleroma.Instances alias Pleroma.Instances
alias Pleroma.User
alias Pleroma.Web.XML alias Pleroma.Web.XML
alias Pleroma.Web.OStatus.ActivityRepresenter alias Pleroma.Web.OStatus.ActivityRepresenter
alias Pleroma.User
require Logger require Logger
def decode(salmon) do def decode(salmon) do
@ -227,7 +229,7 @@ defmodule Pleroma.Web.Salmon do
|> Enum.each(fn remote_user -> |> Enum.each(fn remote_user ->
Logger.debug(fn -> "Sending Salmon to #{remote_user.ap_id}" end) Logger.debug(fn -> "Sending Salmon to #{remote_user.ap_id}" end)
Pleroma.Web.Federator.enqueue(:publish_single_salmon, %{ Pleroma.Web.Federator.publish_single_salmon(%{
recipient: remote_user, recipient: remote_user,
feed: feed, feed: feed,
poster: poster, poster: poster,

View file

@ -5,8 +5,12 @@
defmodule Pleroma.Web.Streamer do defmodule Pleroma.Web.Streamer do
use GenServer use GenServer
require Logger require Logger
alias Pleroma.{User, Notification, Activity, Object, Repo} alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Notification
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.Web.ActivityPub.Visibility
@keepalive_interval :timer.seconds(30) @keepalive_interval :timer.seconds(30)
@ -69,7 +73,7 @@ defmodule Pleroma.Web.Streamer do
def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do
# filter the recipient list if the activity is not public, see #270. # filter the recipient list if the activity is not public, see #270.
recipient_lists = recipient_lists =
case ActivityPub.is_public?(item) do case Visibility.is_public?(item) do
true -> true ->
Pleroma.List.get_lists_from_activity(item) Pleroma.List.get_lists_from_activity(item)
@ -78,7 +82,7 @@ defmodule Pleroma.Web.Streamer do
|> Enum.filter(fn list -> |> Enum.filter(fn list ->
owner = Repo.get(User, list.user_id) owner = Repo.get(User, list.user_id)
ActivityPub.visible_for_user?(item, owner) Visibility.visible_for_user?(item, owner)
end) end)
end end

Some files were not shown because too many files have changed in this diff Show more