forked from AkkomaGang/akkoma
Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into ecto_sql_update
This commit is contained in:
commit
67af50ec71
69 changed files with 1961 additions and 568 deletions
|
@ -52,6 +52,7 @@ unit-testing:
|
|||
- mix ecto.create
|
||||
- mix ecto.migrate
|
||||
- mix test --trace --preload-modules
|
||||
- mix coveralls
|
||||
|
||||
lint:
|
||||
stage: test
|
||||
|
|
|
@ -10,18 +10,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- A [job queue](https://git.pleroma.social/pleroma/pleroma_job_queue) for federation, emails, web push, etc.
|
||||
- [Prometheus](https://prometheus.io/) metrics
|
||||
- Support for Mastodon's remote interaction
|
||||
- Mix Tasks: `mix pleroma.database bump_all_conversations`
|
||||
- Mix Tasks: `mix pleroma.database remove_embedded_objects`
|
||||
- Mix Tasks: `mix pleroma.database update_users_following_followers_counts`
|
||||
- Mix Tasks: `mix pleroma.user toggle_confirmed`
|
||||
- Federation: Support for reports
|
||||
- Configuration: `safe_dm_mentions` option
|
||||
- Configuration: `link_name` option
|
||||
- Configuration: `fetch_initial_posts` option
|
||||
- Configuration: `notify_email` option
|
||||
- Configuration: Media proxy `whitelist` option
|
||||
- Configuration: `report_uri` option
|
||||
- Pleroma API: User subscriptions
|
||||
- Pleroma API: Healthcheck endpoint
|
||||
- Admin API: Endpoints for listing/revoking invite tokens
|
||||
- Admin API: Endpoints for making users follow/unfollow each other
|
||||
- Admin API: added filters (role, tags, email, name) for users endpoint
|
||||
- Admin API: Endpoints for managing reports
|
||||
- Admin API: Endpoints for deleting and changing the scope of individual reported statuses
|
||||
- AdminFE: initial release with basic user management accessible at /pleroma/admin/
|
||||
- Mastodon API: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/)
|
||||
- Mastodon API: `/api/v1/notifications/destroy_multiple` (glitch-soc extension)
|
||||
|
|
|
@ -406,8 +406,7 @@
|
|||
mailer: 10,
|
||||
transmogrifier: 20,
|
||||
scheduled_activities: 10,
|
||||
background: 5,
|
||||
user: 10
|
||||
background: 5
|
||||
|
||||
config :pleroma, :fetch_initial_posts,
|
||||
enabled: false,
|
||||
|
@ -466,6 +465,9 @@
|
|||
token_expires_in: 600,
|
||||
issue_new_refresh_token: true
|
||||
|
||||
config :http_signatures,
|
||||
adapter: Pleroma.Signature
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
|
|
@ -61,6 +61,8 @@
|
|||
|
||||
config :pleroma, :app_account_creation, max_requests: 5
|
||||
|
||||
config :pleroma, :http_security, report_uri: "https://endpoint.com"
|
||||
|
||||
try do
|
||||
import_config "test.secret.exs"
|
||||
rescue
|
||||
|
|
|
@ -24,7 +24,7 @@ Authentication is required and the user must be an admin.
|
|||
- Example: `https://mypleroma.org/api/pleroma/admin/users?query=john&filters=local,active&page=1&page_size=10&tags[]=some_tag&tags[]=another_tag&name=display_name&email=email@example.com`
|
||||
- Response:
|
||||
|
||||
```JSON
|
||||
```json
|
||||
{
|
||||
"page_size": integer,
|
||||
"count": integer,
|
||||
|
@ -92,7 +92,7 @@ Authentication is required and the user must be an admin.
|
|||
- `nickname`
|
||||
- Response: User’s object
|
||||
|
||||
```JSON
|
||||
```json
|
||||
{
|
||||
"deactivated": bool,
|
||||
"id": integer,
|
||||
|
@ -106,15 +106,15 @@ Authentication is required and the user must be an admin.
|
|||
|
||||
- Method: `PUT`
|
||||
- Params:
|
||||
- `nickname`
|
||||
- `tags`
|
||||
- `nicknames` (array)
|
||||
- `tags` (array)
|
||||
|
||||
### Untag a list of users
|
||||
|
||||
- Method: `DELETE`
|
||||
- Params:
|
||||
- `nickname`
|
||||
- `tags`
|
||||
- `nicknames` (array)
|
||||
- `tags` (array)
|
||||
|
||||
## `/api/pleroma/admin/users/:nickname/permission_group`
|
||||
|
||||
|
@ -124,7 +124,7 @@ Authentication is required and the user must be an admin.
|
|||
- Params: none
|
||||
- Response:
|
||||
|
||||
```JSON
|
||||
```json
|
||||
{
|
||||
"is_moderator": bool,
|
||||
"is_admin": bool
|
||||
|
@ -141,7 +141,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
- Params: none
|
||||
- Response:
|
||||
|
||||
```JSON
|
||||
```json
|
||||
{
|
||||
"is_moderator": bool,
|
||||
"is_admin": bool
|
||||
|
@ -223,7 +223,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
- Params: none
|
||||
- Response:
|
||||
|
||||
```JSON
|
||||
```json
|
||||
{
|
||||
|
||||
"invites": [
|
||||
|
@ -250,7 +250,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
- `token`
|
||||
- Response:
|
||||
|
||||
```JSON
|
||||
```json
|
||||
{
|
||||
"id": integer,
|
||||
"token": string,
|
||||
|
@ -280,3 +280,280 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
- Methods: `GET`
|
||||
- Params: none
|
||||
- Response: password reset token (base64 string)
|
||||
|
||||
## `/api/pleroma/admin/reports`
|
||||
### Get a list of reports
|
||||
- Method `GET`
|
||||
- Params:
|
||||
- `state`: optional, the state of reports. Valid values are `open`, `closed` and `resolved`
|
||||
- `limit`: optional, the number of records to retrieve
|
||||
- `since_id`: optional, returns results that are more recent than the specified id
|
||||
- `max_id`: optional, returns results that are older than the specified id
|
||||
- Response:
|
||||
- On failure: 403 Forbidden error `{"error": "error_msg"}` when requested by anonymous or non-admin
|
||||
- On success: JSON, returns a list of reports, where:
|
||||
- `account`: the user who has been reported
|
||||
- `actor`: the user who has sent the report
|
||||
- `statuses`: list of statuses that have been included to the report
|
||||
|
||||
```json
|
||||
{
|
||||
"reports": [
|
||||
{
|
||||
"account": {
|
||||
"acct": "user",
|
||||
"avatar": "https://pleroma.example.org/images/avi.png",
|
||||
"avatar_static": "https://pleroma.example.org/images/avi.png",
|
||||
"bot": false,
|
||||
"created_at": "2019-04-23T17:32:04.000Z",
|
||||
"display_name": "User",
|
||||
"emojis": [],
|
||||
"fields": [],
|
||||
"followers_count": 1,
|
||||
"following_count": 1,
|
||||
"header": "https://pleroma.example.org/images/banner.png",
|
||||
"header_static": "https://pleroma.example.org/images/banner.png",
|
||||
"id": "9i6dAJqSGSKMzLG2Lo",
|
||||
"locked": false,
|
||||
"note": "",
|
||||
"pleroma": {
|
||||
"confirmation_pending": false,
|
||||
"hide_favorites": true,
|
||||
"hide_followers": false,
|
||||
"hide_follows": false,
|
||||
"is_admin": false,
|
||||
"is_moderator": false,
|
||||
"relationship": {},
|
||||
"tags": []
|
||||
},
|
||||
"source": {
|
||||
"note": "",
|
||||
"pleroma": {},
|
||||
"sensitive": false
|
||||
},
|
||||
"statuses_count": 3,
|
||||
"url": "https://pleroma.example.org/users/user",
|
||||
"username": "user"
|
||||
},
|
||||
"actor": {
|
||||
"acct": "lain",
|
||||
"avatar": "https://pleroma.example.org/images/avi.png",
|
||||
"avatar_static": "https://pleroma.example.org/images/avi.png",
|
||||
"bot": false,
|
||||
"created_at": "2019-03-28T17:36:03.000Z",
|
||||
"display_name": "Roger Braun",
|
||||
"emojis": [],
|
||||
"fields": [],
|
||||
"followers_count": 1,
|
||||
"following_count": 1,
|
||||
"header": "https://pleroma.example.org/images/banner.png",
|
||||
"header_static": "https://pleroma.example.org/images/banner.png",
|
||||
"id": "9hEkA5JsvAdlSrocam",
|
||||
"locked": false,
|
||||
"note": "",
|
||||
"pleroma": {
|
||||
"confirmation_pending": false,
|
||||
"hide_favorites": false,
|
||||
"hide_followers": false,
|
||||
"hide_follows": false,
|
||||
"is_admin": false,
|
||||
"is_moderator": false,
|
||||
"relationship": {},
|
||||
"tags": []
|
||||
},
|
||||
"source": {
|
||||
"note": "",
|
||||
"pleroma": {},
|
||||
"sensitive": false
|
||||
},
|
||||
"statuses_count": 1,
|
||||
"url": "https://pleroma.example.org/users/lain",
|
||||
"username": "lain"
|
||||
},
|
||||
"content": "Please delete it",
|
||||
"created_at": "2019-04-29T19:48:15.000Z",
|
||||
"id": "9iJGOv1j8hxuw19bcm",
|
||||
"state": "open",
|
||||
"statuses": [
|
||||
{
|
||||
"account": { ... },
|
||||
"application": {
|
||||
"name": "Web",
|
||||
"website": null
|
||||
},
|
||||
"bookmarked": false,
|
||||
"card": null,
|
||||
"content": "<span class=\"h-card\"><a data-user=\"9hEkA5JsvAdlSrocam\" class=\"u-url mention\" href=\"https://pleroma.example.org/users/lain\">@<span>lain</span></a></span> click on my link <a href=\"https://www.google.com/\">https://www.google.com/</a>",
|
||||
"created_at": "2019-04-23T19:15:47.000Z",
|
||||
"emojis": [],
|
||||
"favourited": false,
|
||||
"favourites_count": 0,
|
||||
"id": "9i6mQ9uVrrOmOime8m",
|
||||
"in_reply_to_account_id": null,
|
||||
"in_reply_to_id": null,
|
||||
"language": null,
|
||||
"media_attachments": [],
|
||||
"mentions": [
|
||||
{
|
||||
"acct": "lain",
|
||||
"id": "9hEkA5JsvAdlSrocam",
|
||||
"url": "https://pleroma.example.org/users/lain",
|
||||
"username": "lain"
|
||||
},
|
||||
{
|
||||
"acct": "user",
|
||||
"id": "9i6dAJqSGSKMzLG2Lo",
|
||||
"url": "https://pleroma.example.org/users/user",
|
||||
"username": "user"
|
||||
}
|
||||
],
|
||||
"muted": false,
|
||||
"pinned": false,
|
||||
"pleroma": {
|
||||
"content": {
|
||||
"text/plain": "@lain click on my link https://www.google.com/"
|
||||
},
|
||||
"conversation_id": 28,
|
||||
"in_reply_to_account_acct": null,
|
||||
"local": true,
|
||||
"spoiler_text": {
|
||||
"text/plain": ""
|
||||
}
|
||||
},
|
||||
"reblog": null,
|
||||
"reblogged": false,
|
||||
"reblogs_count": 0,
|
||||
"replies_count": 0,
|
||||
"sensitive": false,
|
||||
"spoiler_text": "",
|
||||
"tags": [],
|
||||
"uri": "https://pleroma.example.org/objects/8717b90f-8e09-4b58-97b0-e3305472b396",
|
||||
"url": "https://pleroma.example.org/notice/9i6mQ9uVrrOmOime8m",
|
||||
"visibility": "direct"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## `/api/pleroma/admin/reports/:id`
|
||||
### Get an individual report
|
||||
- Method `GET`
|
||||
- Params:
|
||||
- `id`
|
||||
- Response:
|
||||
- On failure:
|
||||
- 403 Forbidden `{"error": "error_msg"}`
|
||||
- 404 Not Found `"Not found"`
|
||||
- On success: JSON, Report object (see above)
|
||||
|
||||
## `/api/pleroma/admin/reports/:id`
|
||||
### Change the state of the report
|
||||
- Method `PUT`
|
||||
- Params:
|
||||
- `id`
|
||||
- `state`: required, the new state. Valid values are `open`, `closed` and `resolved`
|
||||
- Response:
|
||||
- On failure:
|
||||
- 400 Bad Request `"Unsupported state"`
|
||||
- 403 Forbidden `{"error": "error_msg"}`
|
||||
- 404 Not Found `"Not found"`
|
||||
- On success: JSON, Report object (see above)
|
||||
|
||||
## `/api/pleroma/admin/reports/:id/respond`
|
||||
### Respond to a report
|
||||
- Method `POST`
|
||||
- Params:
|
||||
- `id`
|
||||
- `status`: required, the message
|
||||
- Response:
|
||||
- On failure:
|
||||
- 400 Bad Request `"Invalid parameters"` when `status` is missing
|
||||
- 403 Forbidden `{"error": "error_msg"}`
|
||||
- 404 Not Found `"Not found"`
|
||||
- On success: JSON, created Mastodon Status entity
|
||||
|
||||
```json
|
||||
{
|
||||
"account": { ... },
|
||||
"application": {
|
||||
"name": "Web",
|
||||
"website": null
|
||||
},
|
||||
"bookmarked": false,
|
||||
"card": null,
|
||||
"content": "Your claim is going to be closed",
|
||||
"created_at": "2019-05-11T17:13:03.000Z",
|
||||
"emojis": [],
|
||||
"favourited": false,
|
||||
"favourites_count": 0,
|
||||
"id": "9ihuiSL1405I65TmEq",
|
||||
"in_reply_to_account_id": null,
|
||||
"in_reply_to_id": null,
|
||||
"language": null,
|
||||
"media_attachments": [],
|
||||
"mentions": [
|
||||
{
|
||||
"acct": "user",
|
||||
"id": "9i6dAJqSGSKMzLG2Lo",
|
||||
"url": "https://pleroma.example.org/users/user",
|
||||
"username": "user"
|
||||
},
|
||||
{
|
||||
"acct": "admin",
|
||||
"id": "9hEkA5JsvAdlSrocam",
|
||||
"url": "https://pleroma.example.org/users/admin",
|
||||
"username": "admin"
|
||||
}
|
||||
],
|
||||
"muted": false,
|
||||
"pinned": false,
|
||||
"pleroma": {
|
||||
"content": {
|
||||
"text/plain": "Your claim is going to be closed"
|
||||
},
|
||||
"conversation_id": 35,
|
||||
"in_reply_to_account_acct": null,
|
||||
"local": true,
|
||||
"spoiler_text": {
|
||||
"text/plain": ""
|
||||
}
|
||||
},
|
||||
"reblog": null,
|
||||
"reblogged": false,
|
||||
"reblogs_count": 0,
|
||||
"replies_count": 0,
|
||||
"sensitive": false,
|
||||
"spoiler_text": "",
|
||||
"tags": [],
|
||||
"uri": "https://pleroma.example.org/objects/cab0836d-9814-46cd-a0ea-529da9db5fcb",
|
||||
"url": "https://pleroma.example.org/notice/9ihuiSL1405I65TmEq",
|
||||
"visibility": "direct"
|
||||
}
|
||||
```
|
||||
|
||||
## `/api/pleroma/admin/statuses/:id`
|
||||
### Change the scope of an individual reported status
|
||||
- Method `PUT`
|
||||
- Params:
|
||||
- `id`
|
||||
- `sensitive`: optional, valid values are `true` or `false`
|
||||
- `visibility`: optional, valid values are `public`, `private` and `unlisted`
|
||||
- Response:
|
||||
- On failure:
|
||||
- 400 Bad Request `"Unsupported visibility"`
|
||||
- 403 Forbidden `{"error": "error_msg"}`
|
||||
- 404 Not Found `"Not found"`
|
||||
- On success: JSON, Mastodon Status entity
|
||||
|
||||
## `/api/pleroma/admin/statuses/:id`
|
||||
### Delete an individual reported status
|
||||
- Method `DELETE`
|
||||
- Params:
|
||||
- `id`
|
||||
- Response:
|
||||
- On failure:
|
||||
- 403 Forbidden `{"error": "error_msg"}`
|
||||
- 404 Not Found `"Not found"`
|
||||
- On success: 200 OK `{}`
|
||||
|
|
|
@ -286,7 +286,8 @@ This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls start
|
|||
* ``sts``: Whether to additionally send a `Strict-Transport-Security` header
|
||||
* ``sts_max_age``: The maximum age for the `Strict-Transport-Security` header if sent
|
||||
* ``ct_max_age``: The maximum age for the `Expect-CT` header if sent
|
||||
* ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`.
|
||||
* ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`
|
||||
* ``report_uri``: Adds the specified url to `report-uri` and `report-to` group in CSP header.
|
||||
|
||||
## :mrf_user_allowlist
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ This guide will assume you are on Debian Stretch. This guide should also work wi
|
|||
* `erlang-tools`
|
||||
* `erlang-parsetools`
|
||||
* `erlang-eldap`, if you want to enable ldap authenticator
|
||||
* `erlang-ssh`
|
||||
* `erlang-xmerl`
|
||||
* `git`
|
||||
* `build-essential`
|
||||
|
@ -49,7 +50,7 @@ sudo dpkg -i /tmp/erlang-solutions_1.0_all.deb
|
|||
|
||||
```shell
|
||||
sudo apt update
|
||||
sudo apt install elixir erlang-dev erlang-parsetools erlang-xmerl erlang-tools
|
||||
sudo apt install elixir erlang-dev erlang-parsetools erlang-xmerl erlang-tools erlang-ssh
|
||||
```
|
||||
|
||||
### Install PleromaBE
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
- erlang-dev
|
||||
- erlang-tools
|
||||
- erlang-parsetools
|
||||
- erlang-ssh
|
||||
- erlang-xmerl (Jessieではバックポートからインストールすること!)
|
||||
- git
|
||||
- build-essential
|
||||
|
@ -44,7 +45,7 @@ wget -P /tmp/ https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb
|
|||
|
||||
* ElixirとErlangをインストールします、
|
||||
```
|
||||
apt update && apt install elixir erlang-dev erlang-parsetools erlang-xmerl erlang-tools
|
||||
apt update && apt install elixir erlang-dev erlang-parsetools erlang-xmerl erlang-tools erlang-ssh
|
||||
```
|
||||
|
||||
### Pleroma BE (バックエンド) をインストールします
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
defmodule Mix.Tasks.Pleroma.Database do
|
||||
alias Mix.Tasks.Pleroma.Common
|
||||
alias Pleroma.Conversation
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
require Logger
|
||||
use Mix.Task
|
||||
|
||||
|
@ -19,6 +22,14 @@ defmodule Mix.Tasks.Pleroma.Database do
|
|||
|
||||
Options:
|
||||
- `--vacuum` - run `VACUUM FULL` after the embedded objects are replaced with their references
|
||||
|
||||
## Create a conversation for all existing DMs. Can be safely re-run.
|
||||
|
||||
mix pleroma.database bump_all_conversations
|
||||
|
||||
## Remove duplicated items from following and update followers count for all users
|
||||
|
||||
mix pleroma.database update_users_following_followers_counts
|
||||
"""
|
||||
def run(["remove_embedded_objects" | args]) do
|
||||
{options, [], []} =
|
||||
|
@ -32,7 +43,7 @@ def run(["remove_embedded_objects" | args]) do
|
|||
Common.start_pleroma()
|
||||
Logger.info("Removing embedded objects")
|
||||
|
||||
Pleroma.Repo.query!(
|
||||
Repo.query!(
|
||||
"update activities set data = jsonb_set(data, '{object}'::text[], data->'object'->'id') where data->'object'->>'id' is not null;",
|
||||
[],
|
||||
timeout: :infinity
|
||||
|
@ -41,11 +52,24 @@ def run(["remove_embedded_objects" | args]) do
|
|||
if Keyword.get(options, :vacuum) do
|
||||
Logger.info("Runnning VACUUM FULL")
|
||||
|
||||
Pleroma.Repo.query!(
|
||||
Repo.query!(
|
||||
"vacuum full;",
|
||||
[],
|
||||
timeout: :infinity
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def run(["bump_all_conversations"]) do
|
||||
Common.start_pleroma()
|
||||
Conversation.bump_for_all_activities()
|
||||
end
|
||||
|
||||
def run(["update_users_following_followers_counts"]) do
|
||||
Common.start_pleroma()
|
||||
|
||||
users = Repo.all(User)
|
||||
Enum.each(users, &User.remove_duplicated_following/1)
|
||||
Enum.each(users, &User.update_follower_count/1)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -77,6 +77,10 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
## Delete tags from a user.
|
||||
|
||||
mix pleroma.user untag NICKNAME TAGS
|
||||
|
||||
## Toggle confirmation of the user's account.
|
||||
|
||||
mix pleroma.user toggle_confirmed NICKNAME
|
||||
"""
|
||||
def run(["new", nickname, email | rest]) do
|
||||
{options, [], []} =
|
||||
|
@ -388,6 +392,21 @@ def run(["delete_activities", nickname]) do
|
|||
end
|
||||
end
|
||||
|
||||
def run(["toggle_confirmed", nickname]) do
|
||||
Common.start_pleroma()
|
||||
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||
{:ok, user} = User.toggle_confirmation(user)
|
||||
|
||||
message = if user.info.confirmation_pending, do: "needs", else: "doesn't need"
|
||||
|
||||
Mix.shell().info("#{nickname} #{message} confirmation.")
|
||||
else
|
||||
_ ->
|
||||
Mix.shell().error("No local user #{nickname}")
|
||||
end
|
||||
end
|
||||
|
||||
defp set_moderator(user, value) do
|
||||
info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value})
|
||||
|
||||
|
|
|
@ -60,21 +60,24 @@ defmodule Pleroma.Activity do
|
|||
timestamps()
|
||||
end
|
||||
|
||||
def with_preloaded_object(query) do
|
||||
query
|
||||
|> join(
|
||||
:inner,
|
||||
[activity],
|
||||
o in Object,
|
||||
def with_joined_object(query) do
|
||||
join(query, :inner, [activity], o in Object,
|
||||
on:
|
||||
fragment(
|
||||
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
|
||||
o.data,
|
||||
activity.data,
|
||||
activity.data
|
||||
),
|
||||
as: :object
|
||||
)
|
||||
)
|
||||
|> preload([activity, object], object: object)
|
||||
end
|
||||
|
||||
def with_preloaded_object(query) do
|
||||
query
|
||||
|> has_named_binding?(:object)
|
||||
|> if(do: query, else: with_joined_object(query))
|
||||
|> preload([activity, object: object], object: object)
|
||||
end
|
||||
|
||||
def with_preloaded_bookmark(query, %User{} = user) do
|
||||
|
@ -108,7 +111,7 @@ def get_bookmark(_, _), do: nil
|
|||
|
||||
def change(struct, params \\ %{}) do
|
||||
struct
|
||||
|> cast(params, [:data])
|
||||
|> cast(params, [:data, :recipients])
|
||||
|> validate_required([:data])
|
||||
|> unique_constraint(:ap_id, name: :activities_unique_apid_index)
|
||||
end
|
||||
|
|
|
@ -95,7 +95,6 @@ def handle_command(state, "home") do
|
|||
activities =
|
||||
[user.ap_id | user.following]
|
||||
|> ActivityPub.fetch_activities(params)
|
||||
|> ActivityPub.contain_timeline(user)
|
||||
|
||||
Enum.each(activities, fn activity ->
|
||||
puts_activity(activity)
|
||||
|
|
|
@ -45,7 +45,7 @@ def get_for_ap_id(ap_id) do
|
|||
2. Create a participation for all the people involved who don't have one already
|
||||
3. Bump all relevant participations to 'unread'
|
||||
"""
|
||||
def create_or_bump_for(activity) do
|
||||
def create_or_bump_for(activity, opts \\ []) do
|
||||
with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity),
|
||||
"Create" <- activity.data["type"],
|
||||
object <- Pleroma.Object.normalize(activity),
|
||||
|
@ -58,7 +58,7 @@ def create_or_bump_for(activity) do
|
|||
participations =
|
||||
Enum.map(users, fn user ->
|
||||
{:ok, participation} =
|
||||
Participation.create_for_user_and_conversation(user, conversation)
|
||||
Participation.create_for_user_and_conversation(user, conversation, opts)
|
||||
|
||||
participation
|
||||
end)
|
||||
|
@ -72,4 +72,21 @@ def create_or_bump_for(activity) do
|
|||
e -> {:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
This is only meant to be run by a mix task. It creates conversations/participations for all direct messages in the database.
|
||||
"""
|
||||
def bump_for_all_activities do
|
||||
stream =
|
||||
Pleroma.Web.ActivityPub.ActivityPub.fetch_direct_messages_query()
|
||||
|> Repo.stream()
|
||||
|
||||
Repo.transaction(
|
||||
fn ->
|
||||
stream
|
||||
|> Enum.each(fn a -> create_or_bump_for(a, read: true) end)
|
||||
end,
|
||||
timeout: :infinity
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,15 +22,17 @@ defmodule Pleroma.Conversation.Participation do
|
|||
|
||||
def creation_cng(struct, params) do
|
||||
struct
|
||||
|> cast(params, [:user_id, :conversation_id])
|
||||
|> cast(params, [:user_id, :conversation_id, :read])
|
||||
|> validate_required([:user_id, :conversation_id])
|
||||
end
|
||||
|
||||
def create_for_user_and_conversation(user, conversation) do
|
||||
def create_for_user_and_conversation(user, conversation, opts \\ []) do
|
||||
read = !!opts[:read]
|
||||
|
||||
%__MODULE__{}
|
||||
|> creation_cng(%{user_id: user.id, conversation_id: conversation.id})
|
||||
|> creation_cng(%{user_id: user.id, conversation_id: conversation.id, read: read})
|
||||
|> Repo.insert(
|
||||
on_conflict: [set: [read: false, updated_at: NaiveDateTime.utc_now()]],
|
||||
on_conflict: [set: [read: read, updated_at: NaiveDateTime.utc_now()]],
|
||||
returning: true,
|
||||
conflict_target: [:user_id, :conversation_id]
|
||||
)
|
||||
|
|
|
@ -29,7 +29,7 @@ def report(to, reporter, account, statuses, comment) do
|
|||
end
|
||||
|
||||
statuses_html =
|
||||
if length(statuses) > 0 do
|
||||
if is_list(statuses) && length(statuses) > 0 do
|
||||
statuses_list_html =
|
||||
statuses
|
||||
|> Enum.map(fn
|
||||
|
|
|
@ -38,7 +38,8 @@ def get_filters(%User{id: user_id} = _user) do
|
|||
query =
|
||||
from(
|
||||
f in Pleroma.Filter,
|
||||
where: f.user_id == ^user_id
|
||||
where: f.user_id == ^user_id,
|
||||
order_by: [desc: :id]
|
||||
)
|
||||
|
||||
Repo.all(query)
|
||||
|
|
31
lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex
Normal file
31
lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug do
|
||||
import Plug.Conn
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.User
|
||||
|
||||
def init(options) do
|
||||
options
|
||||
end
|
||||
|
||||
def call(conn, _) do
|
||||
public? = Config.get!([:instance, :public])
|
||||
|
||||
case {public?, conn} do
|
||||
{true, _} ->
|
||||
conn
|
||||
|
||||
{false, %{assigns: %{user: %User{}}}} ->
|
||||
conn
|
||||
|
||||
{false, _} ->
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|> send_resp(403, Jason.encode!(%{error: "This resource requires authentication."}))
|
||||
|> halt
|
||||
end
|
||||
end
|
||||
end
|
|
@ -20,8 +20,9 @@ def call(conn, _options) do
|
|||
|
||||
defp headers do
|
||||
referrer_policy = Config.get([:http_security, :referrer_policy])
|
||||
report_uri = Config.get([:http_security, :report_uri])
|
||||
|
||||
[
|
||||
headers = [
|
||||
{"x-xss-protection", "1; mode=block"},
|
||||
{"x-permitted-cross-domain-policies", "none"},
|
||||
{"x-frame-options", "DENY"},
|
||||
|
@ -30,12 +31,27 @@ defp headers do
|
|||
{"x-download-options", "noopen"},
|
||||
{"content-security-policy", csp_string() <> ";"}
|
||||
]
|
||||
|
||||
if report_uri do
|
||||
report_group = %{
|
||||
"group" => "csp-endpoint",
|
||||
"max-age" => 10_886_400,
|
||||
"endpoints" => [
|
||||
%{"url" => report_uri}
|
||||
]
|
||||
}
|
||||
|
||||
headers ++ [{"reply-to", Jason.encode!(report_group)}]
|
||||
else
|
||||
headers
|
||||
end
|
||||
end
|
||||
|
||||
defp csp_string do
|
||||
scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
|
||||
static_url = Pleroma.Web.Endpoint.static_url()
|
||||
websocket_url = Pleroma.Web.Endpoint.websocket_url()
|
||||
report_uri = Config.get([:http_security, :report_uri])
|
||||
|
||||
connect_src = "connect-src 'self' #{static_url} #{websocket_url}"
|
||||
|
||||
|
@ -53,7 +69,7 @@ defp csp_string do
|
|||
"script-src 'self'"
|
||||
end
|
||||
|
||||
[
|
||||
main_part = [
|
||||
"default-src 'none'",
|
||||
"base-uri 'self'",
|
||||
"frame-ancestors 'none'",
|
||||
|
@ -63,11 +79,14 @@ defp csp_string do
|
|||
"font-src 'self'",
|
||||
"manifest-src 'self'",
|
||||
connect_src,
|
||||
script_src,
|
||||
if scheme == "https" do
|
||||
"upgrade-insecure-requests"
|
||||
end
|
||||
script_src
|
||||
]
|
||||
|
||||
report = if report_uri, do: ["report-uri #{report_uri}; report-to csp-endpoint"], else: []
|
||||
|
||||
insecure = if scheme == "https", do: ["upgrade-insecure-requests"], else: []
|
||||
|
||||
(main_part ++ report ++ insecure)
|
||||
|> Enum.join("; ")
|
||||
end
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.HTTPSignatures
|
||||
import Plug.Conn
|
||||
require Logger
|
||||
|
||||
|
|
41
lib/pleroma/signature.ex
Normal file
41
lib/pleroma/signature.ex
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Signature do
|
||||
@behaviour HTTPSignatures.Adapter
|
||||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.Salmon
|
||||
alias Pleroma.Web.WebFinger
|
||||
|
||||
def fetch_public_key(conn) do
|
||||
with actor_id <- Utils.get_ap_id(conn.params["actor"]),
|
||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||
{:ok, public_key}
|
||||
else
|
||||
e ->
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
def refetch_public_key(conn) do
|
||||
with actor_id <- Utils.get_ap_id(conn.params["actor"]),
|
||||
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
|
||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||
{:ok, public_key}
|
||||
else
|
||||
e ->
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
def sign(%User{} = user, headers) do
|
||||
with {:ok, %{info: %{keys: keys}}} <- WebFinger.ensure_keys_present(user),
|
||||
{:ok, private_key, _} <- Salmon.keys_from_pem(keys) do
|
||||
HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -55,7 +55,7 @@ defmodule Pleroma.User do
|
|||
field(:last_refreshed_at, :naive_datetime_usec)
|
||||
has_many(:notifications, Notification)
|
||||
has_many(:registrations, Registration)
|
||||
embeds_one(:info, Pleroma.User.Info)
|
||||
embeds_one(:info, User.Info)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
@ -166,7 +166,7 @@ def remote_user_creation(params) do
|
|||
|
||||
def update_changeset(struct, params \\ %{}) do
|
||||
struct
|
||||
|> cast(params, [:bio, :name, :avatar])
|
||||
|> cast(params, [:bio, :name, :avatar, :following])
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_format(:nickname, local_nickname_regex())
|
||||
|> validate_length(:bio, max: 5000)
|
||||
|
@ -233,7 +233,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|
|||
|> validate_confirmation(:password)
|
||||
|> unique_constraint(:email)
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames]))
|
||||
|> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
|
||||
|> validate_format(:nickname, local_nickname_regex())
|
||||
|> validate_format(:email, @email_regex)
|
||||
|> validate_length(:bio, max: 1000)
|
||||
|
@ -278,7 +278,7 @@ def register(%Ecto.Changeset{} = changeset) do
|
|||
with {:ok, user} <- Repo.insert(changeset),
|
||||
{:ok, user} <- autofollow_users(user),
|
||||
{:ok, user} <- set_cache(user),
|
||||
{:ok, _} <- Pleroma.User.WelcomeMessage.post_welcome_message_to_user(user),
|
||||
{:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
|
||||
{:ok, _} <- try_send_confirmation_email(user) do
|
||||
{:ok, user}
|
||||
end
|
||||
|
@ -709,6 +709,18 @@ def update_follower_count(%User{} = user) do
|
|||
end
|
||||
end
|
||||
|
||||
def remove_duplicated_following(%User{following: following} = user) do
|
||||
uniq_following = Enum.uniq(following)
|
||||
|
||||
if length(following) == length(uniq_following) do
|
||||
{:ok, user}
|
||||
else
|
||||
user
|
||||
|> update_changeset(%{following: uniq_following})
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
end
|
||||
|
||||
@spec get_users_from_set([String.t()], boolean()) :: [User.t()]
|
||||
def get_users_from_set(ap_ids, local_only \\ true) do
|
||||
criteria = %{ap_id: ap_ids, deactivated: false}
|
||||
|
@ -1378,4 +1390,17 @@ def all_superusers do
|
|||
def showing_reblogs?(%User{} = user, %User{} = target) do
|
||||
target.ap_id not in user.info.muted_reblogs
|
||||
end
|
||||
|
||||
@spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
|
||||
def toggle_confirmation(%User{} = user) do
|
||||
need_confirmation? = !user.info.confirmation_pending
|
||||
|
||||
info_changeset =
|
||||
User.Info.confirmation_changeset(user.info, need_confirmation: need_confirmation?)
|
||||
|
||||
user
|
||||
|> change()
|
||||
|> put_embed(:info, info_changeset)
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -212,7 +212,7 @@ def profile_update(info, params) do
|
|||
])
|
||||
end
|
||||
|
||||
@spec confirmation_changeset(Info.t(), keyword()) :: Ecto.Changerset.t()
|
||||
@spec confirmation_changeset(Info.t(), keyword()) :: Changeset.t()
|
||||
def confirmation_changeset(info, opts) do
|
||||
need_confirmation? = Keyword.get(opts, :need_confirmation)
|
||||
|
||||
|
|
|
@ -540,8 +540,6 @@ defp restrict_visibility(query, %{visibility: visibility})
|
|||
)
|
||||
)
|
||||
|
||||
Ecto.Adapters.SQL.to_sql(:all, Repo, query)
|
||||
|
||||
query
|
||||
else
|
||||
Logger.error("Could not restrict visibility to #{visibility}")
|
||||
|
@ -557,8 +555,6 @@ defp restrict_visibility(query, %{visibility: visibility})
|
|||
fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
|
||||
)
|
||||
|
||||
Ecto.Adapters.SQL.to_sql(:all, Repo, query)
|
||||
|
||||
query
|
||||
end
|
||||
|
||||
|
@ -569,6 +565,18 @@ defp restrict_visibility(_query, %{visibility: visibility})
|
|||
|
||||
defp restrict_visibility(query, _visibility), do: query
|
||||
|
||||
defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}) do
|
||||
query =
|
||||
from(
|
||||
a in query,
|
||||
where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
|
||||
)
|
||||
|
||||
query
|
||||
end
|
||||
|
||||
defp restrict_thread_visibility(query, _), do: query
|
||||
|
||||
def fetch_user_activities(user, reading_user, params \\ %{}) do
|
||||
params =
|
||||
params
|
||||
|
@ -695,6 +703,12 @@ defp restrict_type(query, %{"type" => type}) do
|
|||
|
||||
defp restrict_type(query, _), do: query
|
||||
|
||||
defp restrict_state(query, %{"state" => state}) do
|
||||
from(activity in query, where: fragment("?->>'state' = ?", activity.data, ^state))
|
||||
end
|
||||
|
||||
defp restrict_state(query, _), do: query
|
||||
|
||||
defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
|
||||
from(
|
||||
activity in query,
|
||||
|
@ -750,8 +764,11 @@ defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
|
|||
blocks = info.blocks || []
|
||||
domain_blocks = info.domain_blocks || []
|
||||
|
||||
query =
|
||||
if has_named_binding?(query, :object), do: query, else: Activity.with_joined_object(query)
|
||||
|
||||
from(
|
||||
activity in query,
|
||||
[activity, object: o] in query,
|
||||
where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
|
||||
where: fragment("not (? && ?)", activity.recipients, ^blocks),
|
||||
where:
|
||||
|
@ -761,7 +778,8 @@ defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
|
|||
activity.data,
|
||||
^blocks
|
||||
),
|
||||
where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks)
|
||||
where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks),
|
||||
where: fragment("not (split_part(?->>'actor', '/', 3) = ANY(?))", o.data, ^domain_blocks)
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -843,11 +861,13 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
|||
|> restrict_local(opts)
|
||||
|> restrict_actor(opts)
|
||||
|> restrict_type(opts)
|
||||
|> restrict_state(opts)
|
||||
|> restrict_favorited_by(opts)
|
||||
|> restrict_blocked(opts)
|
||||
|> restrict_muted(opts)
|
||||
|> restrict_media(opts)
|
||||
|> restrict_visibility(opts)
|
||||
|> restrict_thread_visibility(opts)
|
||||
|> restrict_replies(opts)
|
||||
|> restrict_reblogs(opts)
|
||||
|> restrict_pinned(opts)
|
||||
|
@ -966,11 +986,10 @@ def contain_activity(%Activity{} = activity, %User{} = user) do
|
|||
contain_broken_threads(activity, user)
|
||||
end
|
||||
|
||||
# do post-processing on a timeline
|
||||
def contain_timeline(timeline, user) do
|
||||
timeline
|
||||
|> Enum.filter(fn activity ->
|
||||
contain_activity(activity, user)
|
||||
end)
|
||||
def fetch_direct_messages_query do
|
||||
Activity
|
||||
|> restrict_type(%{"type" => "Create"})
|
||||
|> restrict_visibility(%{visibility: "direct"})
|
||||
|> order_by([activity], asc: activity.id)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -55,7 +55,7 @@ defp check_media_nsfw(
|
|||
object =
|
||||
if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_nsfw]), actor_host) do
|
||||
tags = (child_object["tag"] || []) ++ ["nsfw"]
|
||||
child_object = Map.put(child_object, "tags", tags)
|
||||
child_object = Map.put(child_object, "tag", tags)
|
||||
child_object = Map.put(child_object, "sensitive", true)
|
||||
Map.put(object, "object", child_object)
|
||||
else
|
||||
|
|
|
@ -31,7 +31,7 @@ defp process_tag(
|
|||
|
||||
object =
|
||||
object
|
||||
|> Map.put("tags", tags)
|
||||
|> Map.put("tag", tags)
|
||||
|> Map.put("sensitive", true)
|
||||
|
||||
message = Map.put(message, "object", object)
|
||||
|
|
|
@ -54,7 +54,7 @@ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = pa
|
|||
|> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
|
||||
|
||||
signature =
|
||||
Pleroma.Web.HTTPSignatures.sign(actor, %{
|
||||
Pleroma.Signature.sign(actor, %{
|
||||
host: host,
|
||||
"content-length": byte_size(json),
|
||||
digest: digest,
|
||||
|
|
|
@ -11,7 +11,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
alias Pleroma.Object.Containment
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
|
|
|
@ -20,6 +20,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
require Logger
|
||||
|
||||
@supported_object_types ["Article", "Note", "Video", "Page"]
|
||||
@supported_report_states ~w(open closed resolved)
|
||||
@valid_visibilities ~w(public unlisted private direct)
|
||||
|
||||
# Some implementations send the actor URI as the actor field, others send the entire actor object,
|
||||
# so figure out what the actor's URI is based on what we have.
|
||||
|
@ -670,7 +672,8 @@ def make_flag_data(params, additional) do
|
|||
"actor" => params.actor.ap_id,
|
||||
"content" => params.content,
|
||||
"object" => object,
|
||||
"context" => params.context
|
||||
"context" => params.context,
|
||||
"state" => "open"
|
||||
}
|
||||
|> Map.merge(additional)
|
||||
end
|
||||
|
@ -713,4 +716,77 @@ def fetch_ordered_collection(from, pages_left, acc \\ []) do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
#### Report-related helpers
|
||||
|
||||
def update_report_state(%Activity{} = activity, state) when state in @supported_report_states do
|
||||
with new_data <- Map.put(activity.data, "state", state),
|
||||
changeset <- Changeset.change(activity, data: new_data),
|
||||
{:ok, activity} <- Repo.update(changeset) do
|
||||
{:ok, activity}
|
||||
end
|
||||
end
|
||||
|
||||
def update_report_state(_, _), do: {:error, "Unsupported state"}
|
||||
|
||||
def update_activity_visibility(activity, visibility) when visibility in @valid_visibilities do
|
||||
[to, cc, recipients] =
|
||||
activity
|
||||
|> get_updated_targets(visibility)
|
||||
|> Enum.map(&Enum.uniq/1)
|
||||
|
||||
object_data =
|
||||
activity.object.data
|
||||
|> Map.put("to", to)
|
||||
|> Map.put("cc", cc)
|
||||
|
||||
{:ok, object} =
|
||||
activity.object
|
||||
|> Object.change(%{data: object_data})
|
||||
|> Object.update_and_set_cache()
|
||||
|
||||
activity_data =
|
||||
activity.data
|
||||
|> Map.put("to", to)
|
||||
|> Map.put("cc", cc)
|
||||
|
||||
activity
|
||||
|> Map.put(:object, object)
|
||||
|> Activity.change(%{data: activity_data, recipients: recipients})
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def update_activity_visibility(_, _), do: {:error, "Unsupported visibility"}
|
||||
|
||||
defp get_updated_targets(
|
||||
%Activity{data: %{"to" => to} = data, recipients: recipients},
|
||||
visibility
|
||||
) do
|
||||
cc = Map.get(data, "cc", [])
|
||||
follower_address = User.get_cached_by_ap_id(data["actor"]).follower_address
|
||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
case visibility do
|
||||
"public" ->
|
||||
to = [public | List.delete(to, follower_address)]
|
||||
cc = [follower_address | List.delete(cc, public)]
|
||||
recipients = [public | recipients]
|
||||
[to, cc, recipients]
|
||||
|
||||
"private" ->
|
||||
to = [follower_address | List.delete(to, public)]
|
||||
cc = List.delete(cc, public)
|
||||
recipients = List.delete(recipients, public)
|
||||
[to, cc, recipients]
|
||||
|
||||
"unlisted" ->
|
||||
to = [follower_address | List.delete(to, public)]
|
||||
cc = [public | List.delete(cc, follower_address)]
|
||||
recipients = recipients ++ [follower_address, public]
|
||||
[to, cc, recipients]
|
||||
|
||||
_ ->
|
||||
[to, cc, recipients]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
defmodule Pleroma.Web.ActivityPub.Visibility do
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
|
||||
|
@ -13,11 +14,12 @@ def is_public?(data) do
|
|||
end
|
||||
|
||||
def is_private?(activity) do
|
||||
unless is_public?(activity) do
|
||||
follower_address = User.get_cached_by_ap_id(activity.data["actor"]).follower_address
|
||||
Enum.any?(activity.data["to"], &(&1 == follower_address))
|
||||
with false <- is_public?(activity),
|
||||
%User{follower_address: follower_address} <-
|
||||
User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||
follower_address in activity.data["to"]
|
||||
else
|
||||
false
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -38,24 +40,37 @@ def visible_for_user?(activity, user) do
|
|||
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
|
||||
end
|
||||
|
||||
# guard
|
||||
def entire_thread_visible_for_user?(nil, _user), do: false
|
||||
def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do
|
||||
{:ok, %{rows: [[result]]}} =
|
||||
Ecto.Adapters.SQL.query(Repo, "SELECT thread_visibility($1, $2)", [
|
||||
user.ap_id,
|
||||
activity.data["id"]
|
||||
])
|
||||
|
||||
# XXX: Probably even more inefficient than the previous implementation intended to be a placeholder untill https://git.pleroma.social/pleroma/pleroma/merge_requests/971 is in develop
|
||||
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
|
||||
result
|
||||
end
|
||||
|
||||
def entire_thread_visible_for_user?(
|
||||
%Activity{} = tail,
|
||||
# %Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,
|
||||
user
|
||||
) do
|
||||
case Object.normalize(tail) do
|
||||
%{data: %{"inReplyTo" => parent_id}} when is_binary(parent_id) ->
|
||||
parent = Activity.get_in_reply_to_activity(tail)
|
||||
visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user)
|
||||
def get_visibility(object) do
|
||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||
to = object.data["to"] || []
|
||||
cc = object.data["cc"] || []
|
||||
|
||||
_ ->
|
||||
visible_for_user?(tail, user)
|
||||
cond do
|
||||
public in to ->
|
||||
"public"
|
||||
|
||||
public in cc ->
|
||||
"unlisted"
|
||||
|
||||
# this should use the sql for the object's activity
|
||||
Enum.any?(to, &String.contains?(&1, "/followers")) ->
|
||||
"private"
|
||||
|
||||
length(cc) > 0 ->
|
||||
"private"
|
||||
|
||||
true ->
|
||||
"direct"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,11 +4,16 @@
|
|||
|
||||
defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||
use Pleroma.Web, :controller
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserInviteToken
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.AdminAPI.AccountView
|
||||
alias Pleroma.Web.AdminAPI.ReportView
|
||||
alias Pleroma.Web.AdminAPI.Search
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||
|
||||
|
@ -287,12 +292,88 @@ def get_password_reset(conn, %{"nickname" => nickname}) do
|
|||
|> json(token.token)
|
||||
end
|
||||
|
||||
def list_reports(conn, params) do
|
||||
params =
|
||||
params
|
||||
|> Map.put("type", "Flag")
|
||||
|> Map.put("skip_preload", true)
|
||||
|
||||
reports =
|
||||
[]
|
||||
|> ActivityPub.fetch_activities(params)
|
||||
|> Enum.reverse()
|
||||
|
||||
conn
|
||||
|> put_view(ReportView)
|
||||
|> render("index.json", %{reports: reports})
|
||||
end
|
||||
|
||||
def report_show(conn, %{"id" => id}) do
|
||||
with %Activity{} = report <- Activity.get_by_id(id) do
|
||||
conn
|
||||
|> put_view(ReportView)
|
||||
|> render("show.json", %{report: report})
|
||||
else
|
||||
_ -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def report_update_state(conn, %{"id" => id, "state" => state}) do
|
||||
with {:ok, report} <- CommonAPI.update_report_state(id, state) do
|
||||
conn
|
||||
|> put_view(ReportView)
|
||||
|> render("show.json", %{report: report})
|
||||
end
|
||||
end
|
||||
|
||||
def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
|
||||
with false <- is_nil(params["status"]),
|
||||
%Activity{} <- Activity.get_by_id(id) do
|
||||
params =
|
||||
params
|
||||
|> Map.put("in_reply_to_status_id", id)
|
||||
|> Map.put("visibility", "direct")
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, params)
|
||||
|
||||
conn
|
||||
|> put_view(StatusView)
|
||||
|> render("status.json", %{activity: activity})
|
||||
else
|
||||
true ->
|
||||
{:param_cast, nil}
|
||||
|
||||
nil ->
|
||||
{:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def status_update(conn, %{"id" => id} = params) do
|
||||
with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
|
||||
conn
|
||||
|> put_view(StatusView)
|
||||
|> render("status.json", %{activity: activity})
|
||||
end
|
||||
end
|
||||
|
||||
def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
||||
json(conn, %{})
|
||||
end
|
||||
end
|
||||
|
||||
def errors(conn, {:error, :not_found}) do
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json("Not found")
|
||||
end
|
||||
|
||||
def errors(conn, {:error, reason}) do
|
||||
conn
|
||||
|> put_status(400)
|
||||
|> json(reason)
|
||||
end
|
||||
|
||||
def errors(conn, {:param_cast, _}) do
|
||||
conn
|
||||
|> put_status(400)
|
||||
|
|
41
lib/pleroma/web/admin_api/views/report_view.ex
Normal file
41
lib/pleroma/web/admin_api/views/report_view.ex
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.ReportView do
|
||||
use Pleroma.Web, :view
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI.Utils
|
||||
alias Pleroma.Web.MastodonAPI.AccountView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
||||
def render("index.json", %{reports: reports}) do
|
||||
%{
|
||||
reports: render_many(reports, __MODULE__, "show.json", as: :report)
|
||||
}
|
||||
end
|
||||
|
||||
def render("show.json", %{report: report}) do
|
||||
user = User.get_cached_by_ap_id(report.data["actor"])
|
||||
created_at = Utils.to_masto_date(report.data["published"])
|
||||
|
||||
[account_ap_id | status_ap_ids] = report.data["object"]
|
||||
account = User.get_cached_by_ap_id(account_ap_id)
|
||||
|
||||
statuses =
|
||||
Enum.map(status_ap_ids, fn ap_id ->
|
||||
Activity.get_by_ap_id_with_object(ap_id)
|
||||
end)
|
||||
|
||||
%{
|
||||
id: report.id,
|
||||
account: AccountView.render("account.json", %{user: account}),
|
||||
actor: AccountView.render("account.json", %{user: user}),
|
||||
content: report.data["content"],
|
||||
created_at: created_at,
|
||||
statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}),
|
||||
state: report.data["state"]
|
||||
}
|
||||
end
|
||||
end
|
|
@ -71,6 +71,9 @@ def delete(activity_id, user) do
|
|||
{:ok, _} <- unpin(activity_id, user),
|
||||
{:ok, delete} <- ActivityPub.delete(object) do
|
||||
{:ok, delete}
|
||||
else
|
||||
_ ->
|
||||
{:error, "Could not delete"}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -116,32 +119,34 @@ def unfavorite(id_or_ap_id, user) do
|
|||
end
|
||||
end
|
||||
|
||||
def get_visibility(%{"visibility" => visibility})
|
||||
def get_visibility(%{"visibility" => visibility}, in_reply_to)
|
||||
when visibility in ~w{public unlisted private direct},
|
||||
do: visibility
|
||||
do: {visibility, get_replied_to_visibility(in_reply_to)}
|
||||
|
||||
def get_visibility(%{"in_reply_to_status_id" => status_id}) when not is_nil(status_id) do
|
||||
case get_replied_to_activity(status_id) do
|
||||
nil ->
|
||||
"public"
|
||||
|
||||
in_reply_to ->
|
||||
# XXX: these heuristics should be moved out of MastodonAPI.
|
||||
with %Object{} = object <- Object.normalize(in_reply_to) do
|
||||
Pleroma.Web.MastodonAPI.StatusView.get_visibility(object)
|
||||
end
|
||||
end
|
||||
def get_visibility(_, in_reply_to) when not is_nil(in_reply_to) do
|
||||
visibility = get_replied_to_visibility(in_reply_to)
|
||||
{visibility, visibility}
|
||||
end
|
||||
|
||||
def get_visibility(_), do: "public"
|
||||
def get_visibility(_, in_reply_to), do: {"public", get_replied_to_visibility(in_reply_to)}
|
||||
|
||||
def get_replied_to_visibility(nil), do: nil
|
||||
|
||||
def get_replied_to_visibility(activity) do
|
||||
with %Object{} = object <- Object.normalize(activity) do
|
||||
Pleroma.Web.ActivityPub.Visibility.get_visibility(object)
|
||||
end
|
||||
end
|
||||
|
||||
def post(user, %{"status" => status} = data) do
|
||||
visibility = get_visibility(data)
|
||||
limit = Pleroma.Config.get([:instance, :limit])
|
||||
|
||||
with status <- String.trim(status),
|
||||
attachments <- attachments_from_ids(data),
|
||||
in_reply_to <- get_replied_to_activity(data["in_reply_to_status_id"]),
|
||||
{visibility, in_reply_to_visibility} <- get_visibility(data, in_reply_to),
|
||||
{_, false} <-
|
||||
{:private_to_public, in_reply_to_visibility == "direct" && visibility != "direct"},
|
||||
{content_html, mentions, tags} <-
|
||||
make_content_html(
|
||||
status,
|
||||
|
@ -185,6 +190,8 @@ def post(user, %{"status" => status} = data) do
|
|||
)
|
||||
|
||||
res
|
||||
else
|
||||
e -> {:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -193,7 +200,7 @@ def update(user) do
|
|||
user =
|
||||
with emoji <- emoji_from_profile(user),
|
||||
source_data <- (user.info.source_data || %{}) |> Map.put("tag", emoji),
|
||||
info_cng <- Pleroma.User.Info.set_source_data(user.info, source_data),
|
||||
info_cng <- User.Info.set_source_data(user.info, source_data),
|
||||
change <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
|
||||
{:ok, user} <- User.update_and_set_cache(change) do
|
||||
user
|
||||
|
@ -226,7 +233,7 @@ def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
|
|||
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
true <- Enum.member?(object_to, "https://www.w3.org/ns/activitystreams#Public"),
|
||||
%{valid?: true} = info_changeset <-
|
||||
Pleroma.User.Info.add_pinnned_activity(user.info, activity),
|
||||
User.Info.add_pinnned_activity(user.info, activity),
|
||||
changeset <-
|
||||
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
|
||||
{:ok, _user} <- User.update_and_set_cache(changeset) do
|
||||
|
@ -243,7 +250,7 @@ def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
|
|||
def unpin(id_or_ap_id, user) do
|
||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
%{valid?: true} = info_changeset <-
|
||||
Pleroma.User.Info.remove_pinnned_activity(user.info, activity),
|
||||
User.Info.remove_pinnned_activity(user.info, activity),
|
||||
changeset <-
|
||||
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
|
||||
{:ok, _user} <- User.update_and_set_cache(changeset) do
|
||||
|
@ -311,6 +318,60 @@ def report(user, data) do
|
|||
end
|
||||
end
|
||||
|
||||
def update_report_state(activity_id, state) do
|
||||
with %Activity{} = activity <- Activity.get_by_id(activity_id),
|
||||
{:ok, activity} <- Utils.update_report_state(activity, state) do
|
||||
{:ok, activity}
|
||||
else
|
||||
nil ->
|
||||
{:error, :not_found}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
|
||||
_ ->
|
||||
{:error, "Could not update state"}
|
||||
end
|
||||
end
|
||||
|
||||
def update_activity_scope(activity_id, opts \\ %{}) do
|
||||
with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
|
||||
{:ok, activity} <- toggle_sensitive(activity, opts),
|
||||
{:ok, activity} <- set_visibility(activity, opts) do
|
||||
{:ok, activity}
|
||||
else
|
||||
nil ->
|
||||
{:error, :not_found}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp toggle_sensitive(activity, %{"sensitive" => sensitive}) when sensitive in ~w(true false) do
|
||||
toggle_sensitive(activity, %{"sensitive" => String.to_existing_atom(sensitive)})
|
||||
end
|
||||
|
||||
defp toggle_sensitive(%Activity{object: object} = activity, %{"sensitive" => sensitive})
|
||||
when is_boolean(sensitive) do
|
||||
new_data = Map.put(object.data, "sensitive", sensitive)
|
||||
|
||||
{:ok, object} =
|
||||
object
|
||||
|> Object.change(%{data: new_data})
|
||||
|> Object.update_and_set_cache()
|
||||
|
||||
{:ok, Map.put(activity, :object, object)}
|
||||
end
|
||||
|
||||
defp toggle_sensitive(activity, _), do: {:ok, activity}
|
||||
|
||||
defp set_visibility(activity, %{"visibility" => visibility}) do
|
||||
Utils.update_activity_visibility(activity, visibility)
|
||||
end
|
||||
|
||||
defp set_visibility(activity, _), do: {:ok, activity}
|
||||
|
||||
def hide_reblogs(user, muted) do
|
||||
ap_id = muted.ap_id
|
||||
|
||||
|
|
|
@ -237,13 +237,11 @@ def make_note_data(
|
|||
"tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
|
||||
}
|
||||
|
||||
if in_reply_to do
|
||||
in_reply_to_object = Object.normalize(in_reply_to)
|
||||
|
||||
object
|
||||
|> Map.put("inReplyTo", in_reply_to_object.data["id"])
|
||||
with false <- is_nil(in_reply_to),
|
||||
%Object{} = in_reply_to_object <- Object.normalize(in_reply_to) do
|
||||
Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
|
||||
else
|
||||
object
|
||||
_ -> object
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ defmodule Pleroma.Web.Federator.Publisher do
|
|||
"""
|
||||
@spec enqueue_one(module(), Map.t()) :: :ok
|
||||
def enqueue_one(module, %{} = params),
|
||||
do: PleromaJobQueue.enqueue(:federation_outgoing, __MODULE__, [:publish_one, module, params])
|
||||
do: PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_one, module, params])
|
||||
|
||||
@spec perform(atom(), module(), any()) :: {:ok, any()} | {:error, any()}
|
||||
def perform(:publish_one, module, params) do
|
||||
|
@ -52,9 +52,9 @@ def perform(type, _, _) do
|
|||
@doc """
|
||||
Relays an activity to all specified peers.
|
||||
"""
|
||||
@callback publish(Pleroma.User.t(), Pleroma.Activity.t()) :: :ok | {:error, any()}
|
||||
@callback publish(User.t(), Activity.t()) :: :ok | {:error, any()}
|
||||
|
||||
@spec publish(Pleroma.User.t(), Pleroma.Activity.t()) :: :ok
|
||||
@spec publish(User.t(), Activity.t()) :: :ok
|
||||
def publish(%User{} = user, %Activity{} = activity) do
|
||||
Config.get([:instance, :federation_publisher_modules])
|
||||
|> Enum.each(fn module ->
|
||||
|
@ -70,9 +70,9 @@ def publish(%User{} = user, %Activity{} = activity) do
|
|||
@doc """
|
||||
Gathers links used by an outgoing federation module for WebFinger output.
|
||||
"""
|
||||
@callback gather_webfinger_links(Pleroma.User.t()) :: list()
|
||||
@callback gather_webfinger_links(User.t()) :: list()
|
||||
|
||||
@spec gather_webfinger_links(Pleroma.User.t()) :: list()
|
||||
@spec gather_webfinger_links(User.t()) :: list()
|
||||
def gather_webfinger_links(%User{} = user) do
|
||||
Config.get([:instance, :federation_publisher_modules])
|
||||
|> Enum.reduce([], fn module, links ->
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
# https://tools.ietf.org/html/draft-cavage-http-signatures-08
|
||||
defmodule Pleroma.Web.HTTPSignatures do
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
|
||||
require Logger
|
||||
|
||||
def split_signature(sig) do
|
||||
default = %{"headers" => "date"}
|
||||
|
||||
sig =
|
||||
sig
|
||||
|> String.trim()
|
||||
|> String.split(",")
|
||||
|> Enum.reduce(default, fn part, acc ->
|
||||
[key | rest] = String.split(part, "=")
|
||||
value = Enum.join(rest, "=")
|
||||
Map.put(acc, key, String.trim(value, "\""))
|
||||
end)
|
||||
|
||||
Map.put(sig, "headers", String.split(sig["headers"], ~r/\s/))
|
||||
end
|
||||
|
||||
def validate(headers, signature, public_key) do
|
||||
sigstring = build_signing_string(headers, signature["headers"])
|
||||
Logger.debug("Signature: #{signature["signature"]}")
|
||||
Logger.debug("Sigstring: #{sigstring}")
|
||||
{:ok, sig} = Base.decode64(signature["signature"])
|
||||
:public_key.verify(sigstring, :sha256, sig, public_key)
|
||||
end
|
||||
|
||||
def validate_conn(conn) do
|
||||
# TODO: How to get the right key and see if it is actually valid for that request.
|
||||
# For now, fetch the key for the actor.
|
||||
with actor_id <- Utils.get_ap_id(conn.params["actor"]),
|
||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||
if validate_conn(conn, public_key) do
|
||||
true
|
||||
else
|
||||
Logger.debug("Could not validate, re-fetching user and trying one more time")
|
||||
# Fetch user anew and try one more time
|
||||
with actor_id <- Utils.get_ap_id(conn.params["actor"]),
|
||||
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
|
||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||
validate_conn(conn, public_key)
|
||||
end
|
||||
end
|
||||
else
|
||||
_e ->
|
||||
Logger.debug("Could not public key!")
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def validate_conn(conn, public_key) do
|
||||
headers = Enum.into(conn.req_headers, %{})
|
||||
signature = split_signature(headers["signature"])
|
||||
validate(headers, signature, public_key)
|
||||
end
|
||||
|
||||
def build_signing_string(headers, used_headers) do
|
||||
used_headers
|
||||
|> Enum.map(fn header -> "#{header}: #{headers[header]}" end)
|
||||
|> Enum.join("\n")
|
||||
end
|
||||
|
||||
def sign(user, headers) do
|
||||
with {:ok, %{info: %{keys: keys}}} <- Pleroma.Web.WebFinger.ensure_keys_present(user),
|
||||
{:ok, private_key, _} = Pleroma.Web.Salmon.keys_from_pem(keys) do
|
||||
sigstring = build_signing_string(headers, Map.keys(headers))
|
||||
|
||||
signature =
|
||||
:public_key.sign(sigstring, :sha256, private_key)
|
||||
|> Base.encode64()
|
||||
|
||||
[
|
||||
keyId: user.ap_id <> "#main-key",
|
||||
algorithm: "rsa-sha256",
|
||||
headers: Map.keys(headers) |> Enum.join(" "),
|
||||
signature: signature
|
||||
]
|
||||
|> Enum.map(fn {k, v} -> "#{k}=\"#{v}\"" end)
|
||||
|> Enum.join(",")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -303,7 +303,6 @@ def home_timeline(%{assigns: %{user: user}} = conn, params) do
|
|||
activities =
|
||||
[user.ap_id | user.following]
|
||||
|> ActivityPub.fetch_activities(params)
|
||||
|> ActivityPub.contain_timeline(user)
|
||||
|> Enum.reverse()
|
||||
|
||||
conn
|
||||
|
@ -1223,7 +1222,7 @@ def remove_from_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_id
|
|||
accounts
|
||||
|> Enum.each(fn account_id ->
|
||||
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
||||
%User{} = followed <- Pleroma.User.get_cached_by_id(account_id) do
|
||||
%User{} = followed <- User.get_cached_by_id(account_id) do
|
||||
Pleroma.List.unfollow(list, followed)
|
||||
end
|
||||
end)
|
||||
|
|
|
@ -16,6 +16,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.MediaProxy
|
||||
|
||||
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1]
|
||||
|
||||
# TODO: Add cached version.
|
||||
defp get_replied_to_activities(activities) do
|
||||
activities
|
||||
|
@ -340,30 +342,6 @@ def get_reply_to(%{data: %{"object" => _object}} = activity, _) do
|
|||
end
|
||||
end
|
||||
|
||||
def get_visibility(object) do
|
||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||
to = object.data["to"] || []
|
||||
cc = object.data["cc"] || []
|
||||
|
||||
cond do
|
||||
public in to ->
|
||||
"public"
|
||||
|
||||
public in cc ->
|
||||
"unlisted"
|
||||
|
||||
# this should use the sql for the object's activity
|
||||
Enum.any?(to, &String.contains?(&1, "/followers")) ->
|
||||
"private"
|
||||
|
||||
length(cc) > 0 ->
|
||||
"private"
|
||||
|
||||
true ->
|
||||
"direct"
|
||||
end
|
||||
end
|
||||
|
||||
def render_content(%{data: %{"type" => "Video"}} = object) do
|
||||
with name when not is_nil(name) and name != "" <- object.data["name"] do
|
||||
"<p><a href=\"#{object.data["id"]}\">#{name}</a></p>#{object.data["content"]}"
|
||||
|
|
|
@ -20,7 +20,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
|||
field(:scopes, {:array, :string}, default: [])
|
||||
field(:valid_until, :naive_datetime_usec)
|
||||
field(:used, :boolean, default: false)
|
||||
belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId)
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
belongs_to(:app, App)
|
||||
|
||||
timestamps()
|
||||
|
|
|
@ -22,7 +22,7 @@ defmodule Pleroma.Web.OAuth.Token do
|
|||
field(:refresh_token, :string)
|
||||
field(:scopes, {:array, :string}, default: [])
|
||||
field(:valid_until, :naive_datetime_usec)
|
||||
belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId)
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
belongs_to(:app, App)
|
||||
|
||||
timestamps()
|
||||
|
|
|
@ -84,11 +84,13 @@ defmodule Pleroma.Web.Router do
|
|||
plug(Pleroma.Plugs.EnsureUserKeyPlug)
|
||||
end
|
||||
|
||||
pipeline :oauth_read_or_unauthenticated do
|
||||
pipeline :oauth_read_or_public do
|
||||
plug(Pleroma.Plugs.OAuthScopesPlug, %{
|
||||
scopes: ["read"],
|
||||
fallback: :proceed_unauthenticated
|
||||
})
|
||||
|
||||
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
|
||||
end
|
||||
|
||||
pipeline :oauth_read do
|
||||
|
@ -192,6 +194,14 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
get("/users", AdminAPIController, :list_users)
|
||||
get("/users/:nickname", AdminAPIController, :user_show)
|
||||
|
||||
get("/reports", AdminAPIController, :list_reports)
|
||||
get("/reports/:id", AdminAPIController, :report_show)
|
||||
put("/reports/:id", AdminAPIController, :report_update_state)
|
||||
post("/reports/:id/respond", AdminAPIController, :report_respond)
|
||||
|
||||
put("/statuses/:id", AdminAPIController, :status_update)
|
||||
delete("/statuses/:id", AdminAPIController, :status_delete)
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web.TwitterAPI do
|
||||
|
@ -404,7 +414,7 @@ defmodule Pleroma.Web.Router do
|
|||
get("/accounts/search", MastodonAPIController, :account_search)
|
||||
|
||||
scope [] do
|
||||
pipe_through(:oauth_read_or_unauthenticated)
|
||||
pipe_through(:oauth_read_or_public)
|
||||
|
||||
get("/timelines/public", MastodonAPIController, :public_timeline)
|
||||
get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline)
|
||||
|
@ -425,7 +435,7 @@ defmodule Pleroma.Web.Router do
|
|||
end
|
||||
|
||||
scope "/api/v2", Pleroma.Web.MastodonAPI do
|
||||
pipe_through([:api, :oauth_read_or_unauthenticated])
|
||||
pipe_through([:api, :oauth_read_or_public])
|
||||
get("/search", MastodonAPIController, :search2)
|
||||
end
|
||||
|
||||
|
@ -455,7 +465,7 @@ defmodule Pleroma.Web.Router do
|
|||
)
|
||||
|
||||
scope [] do
|
||||
pipe_through(:oauth_read_or_unauthenticated)
|
||||
pipe_through(:oauth_read_or_public)
|
||||
|
||||
get("/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
|
||||
get("/qvitter/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
|
||||
|
@ -473,7 +483,7 @@ defmodule Pleroma.Web.Router do
|
|||
end
|
||||
|
||||
scope "/api", Pleroma.Web do
|
||||
pipe_through([:api, :oauth_read_or_unauthenticated])
|
||||
pipe_through([:api, :oauth_read_or_public])
|
||||
|
||||
get("/statuses/public_timeline", TwitterAPI.Controller, :public_timeline)
|
||||
|
||||
|
@ -487,7 +497,7 @@ defmodule Pleroma.Web.Router do
|
|||
end
|
||||
|
||||
scope "/api", Pleroma.Web, as: :twitter_api_search do
|
||||
pipe_through([:api, :oauth_read_or_unauthenticated])
|
||||
pipe_through([:api, :oauth_read_or_public])
|
||||
get("/pleroma/search_user", TwitterAPI.Controller, :search_user)
|
||||
end
|
||||
|
||||
|
@ -671,7 +681,7 @@ defmodule Pleroma.Web.Router do
|
|||
delete("/auth/sign_out", MastodonAPIController, :logout)
|
||||
|
||||
scope [] do
|
||||
pipe_through(:oauth_read_or_unauthenticated)
|
||||
pipe_through(:oauth_read_or_public)
|
||||
get("/web/*path", MastodonAPIController, :index)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -101,9 +101,7 @@ def friends_timeline(%{assigns: %{user: user}} = conn, params) do
|
|||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("user", user)
|
||||
|
||||
activities =
|
||||
ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
||||
|> ActivityPub.contain_timeline(user)
|
||||
activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
||||
|
||||
conn
|
||||
|> put_view(ActivityView)
|
||||
|
|
|
@ -310,7 +310,7 @@ def render(
|
|||
"tags" => tags,
|
||||
"activity_type" => "post",
|
||||
"possibly_sensitive" => possibly_sensitive,
|
||||
"visibility" => StatusView.get_visibility(object),
|
||||
"visibility" => Pleroma.Web.ActivityPub.Visibility.get_visibility(object),
|
||||
"summary" => summary,
|
||||
"summary_html" => summary |> Formatter.emojify(object.data["emoji"]),
|
||||
"card" => card,
|
||||
|
|
|
@ -99,7 +99,7 @@ def ensure_keys_present(user) do
|
|||
|
||||
info_cng =
|
||||
info
|
||||
|> Pleroma.User.Info.set_keys(pem)
|
||||
|> User.Info.set_keys(pem)
|
||||
|
||||
cng =
|
||||
Ecto.Changeset.change(user)
|
||||
|
|
7
mix.exs
7
mix.exs
|
@ -13,6 +13,7 @@ def project do
|
|||
start_permanent: Mix.env() == :prod,
|
||||
aliases: aliases(),
|
||||
deps: deps(),
|
||||
test_coverage: [tool: ExCoveralls],
|
||||
|
||||
# Docs
|
||||
name: "Pleroma",
|
||||
|
@ -106,6 +107,9 @@ defp deps do
|
|||
{:auto_linker,
|
||||
git: "https://git.pleroma.social/pleroma/auto_linker.git",
|
||||
ref: "c00c4e75b35367fa42c95ffd9b8c455bf9995829"},
|
||||
{:http_signatures,
|
||||
git: "https://git.pleroma.social/pleroma/http_signatures.git",
|
||||
ref: "9789401987096ead65646b52b5a2ca6bf52fc531"},
|
||||
{:pleroma_job_queue, "~> 0.2.0"},
|
||||
{:telemetry, "~> 0.3"},
|
||||
{:prometheus_ex, "~> 3.0"},
|
||||
|
@ -118,7 +122,8 @@ defp deps do
|
|||
{:benchee, "~> 1.0"},
|
||||
{:esshd, "~> 0.1.0"},
|
||||
{:ex_rated, "~> 1.2"},
|
||||
{:plug_static_index_html, "~> 1.0.0"}
|
||||
{:plug_static_index_html, "~> 1.0.0"},
|
||||
{:excoveralls, "~> 0.11.1", only: :test}
|
||||
] ++ oauth_deps
|
||||
end
|
||||
|
||||
|
|
2
mix.lock
2
mix.lock
|
@ -31,12 +31,14 @@
|
|||
"ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"ex_rated": {:hex, :ex_rated, "1.3.2", "6aeb32abb46ea6076f417a9ce8cb1cf08abf35fb2d42375beaad4dd72b550bf1", [:mix], [{:ex2ms, "~> 1.5", [hex: :ex2ms, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"ex_syslogger": {:git, "https://github.com/slashmili/ex_syslogger.git", "f3963399047af17e038897c69e20d552e6899e1d", [tag: "1.4.0"]},
|
||||
"excoveralls": {:hex, :excoveralls, "0.11.1", "dd677fbdd49114fdbdbf445540ec735808250d56b011077798316505064edb2c", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"floki": {:hex, :floki, "0.20.4", "be42ac911fece24b4c72f3b5846774b6e61b83fe685c2fc9d62093277fb3bc86", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}, {:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"gen_smtp": {:hex, :gen_smtp, "0.13.0", "11f08504c4bdd831dc520b8f84a1dce5ce624474a797394e7aafd3c29f5dcd25", [:rebar3], [], "hexpm"},
|
||||
"gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
|
||||
"hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"},
|
||||
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "9789401987096ead65646b52b5a2ca6bf52fc531", [ref: "9789401987096ead65646b52b5a2ca6bf52fc531"]},
|
||||
"httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
defmodule Pleroma.Repo.Migrations.SetDefaultStateToReports do
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
execute """
|
||||
UPDATE activities AS a
|
||||
SET data = jsonb_set(data, '{state}', '"open"', true)
|
||||
WHERE data->>'type' = 'Flag'
|
||||
"""
|
||||
end
|
||||
|
||||
def down do
|
||||
execute """
|
||||
UPDATE activities AS a
|
||||
SET data = data #- '{state}'
|
||||
WHERE data->>'type' = 'Flag'
|
||||
"""
|
||||
end
|
||||
end
|
|
@ -0,0 +1,73 @@
|
|||
defmodule Pleroma.Repo.Migrations.AddThreadVisibilityFunction do
|
||||
use Ecto.Migration
|
||||
@disable_ddl_transaction true
|
||||
|
||||
def up do
|
||||
statement = """
|
||||
CREATE OR REPLACE FUNCTION thread_visibility(actor varchar, activity_id varchar) RETURNS boolean AS $$
|
||||
DECLARE
|
||||
public varchar := 'https://www.w3.org/ns/activitystreams#Public';
|
||||
child objects%ROWTYPE;
|
||||
activity activities%ROWTYPE;
|
||||
actor_user users%ROWTYPE;
|
||||
author_fa varchar;
|
||||
valid_recipients varchar[];
|
||||
BEGIN
|
||||
--- Fetch our actor.
|
||||
SELECT * INTO actor_user FROM users WHERE users.ap_id = actor;
|
||||
|
||||
--- Fetch our initial activity.
|
||||
SELECT * INTO activity FROM activities WHERE activities.data->>'id' = activity_id;
|
||||
|
||||
LOOP
|
||||
--- Ensure that we have an activity before continuing.
|
||||
--- If we don't, the thread is not satisfiable.
|
||||
IF activity IS NULL THEN
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
--- We only care about Create activities.
|
||||
IF activity.data->>'type' != 'Create' THEN
|
||||
RETURN true;
|
||||
END IF;
|
||||
|
||||
--- Normalize the child object into child.
|
||||
SELECT * INTO child FROM objects
|
||||
INNER JOIN activities ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
|
||||
WHERE COALESCE(activity.data->'object'->>'id', activity.data->>'object') = objects.data->>'id';
|
||||
|
||||
--- Fetch the author's AS2 following collection.
|
||||
SELECT COALESCE(users.follower_address, '') INTO author_fa FROM users WHERE users.ap_id = activity.actor;
|
||||
|
||||
--- Prepare valid recipients array.
|
||||
valid_recipients := ARRAY[actor, public];
|
||||
IF ARRAY[author_fa] && actor_user.following THEN
|
||||
valid_recipients := valid_recipients || author_fa;
|
||||
END IF;
|
||||
|
||||
--- Check visibility.
|
||||
IF NOT valid_recipients && activity.recipients THEN
|
||||
--- activity not visible, break out of the loop
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
--- If there's a parent, load it and do this all over again.
|
||||
IF (child.data->'inReplyTo' IS NOT NULL) AND (child.data->'inReplyTo' != 'null'::jsonb) THEN
|
||||
SELECT * INTO activity FROM activities
|
||||
INNER JOIN objects ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
|
||||
WHERE child.data->>'inReplyTo' = objects.data->>'id';
|
||||
ELSE
|
||||
RETURN true;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql IMMUTABLE;
|
||||
"""
|
||||
|
||||
execute(statement)
|
||||
end
|
||||
|
||||
def down do
|
||||
execute("drop function thread_visibility(actor varchar, activity_id varchar)")
|
||||
end
|
||||
end
|
|
@ -11,6 +11,26 @@ defmodule Pleroma.ConversationTest do
|
|||
|
||||
import Pleroma.Factory
|
||||
|
||||
test "it goes through old direct conversations" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, _activity} =
|
||||
CommonAPI.post(user, %{"visibility" => "direct", "status" => "hey @#{other_user.nickname}"})
|
||||
|
||||
Repo.delete_all(Conversation)
|
||||
Repo.delete_all(Conversation.Participation)
|
||||
|
||||
refute Repo.one(Conversation)
|
||||
|
||||
Conversation.bump_for_all_activities()
|
||||
|
||||
assert Repo.one(Conversation)
|
||||
[participation, _p2] = Repo.all(Conversation.Participation)
|
||||
|
||||
assert participation.read
|
||||
end
|
||||
|
||||
test "it creates a conversation for given ap_id" do
|
||||
assert {:ok, %Conversation{} = conversation} =
|
||||
Conversation.create_for_ap_id("https://some_ap_id")
|
||||
|
|
|
@ -125,7 +125,7 @@ test "gives a replacement for user links, using local nicknames in user links te
|
|||
archaeme =
|
||||
insert(:user, %{
|
||||
nickname: "archa_eme_",
|
||||
info: %Pleroma.User.Info{source_data: %{"url" => "https://archeme/@archa_eme_"}}
|
||||
info: %User.Info{source_data: %{"url" => "https://archeme/@archa_eme_"}}
|
||||
})
|
||||
|
||||
archaeme_remote = insert(:user, %{nickname: "archaeme@archae.me"})
|
||||
|
|
55
test/plugs/ensure_public_or_authenticated_plug_test.exs
Normal file
55
test/plugs/ensure_public_or_authenticated_plug_test.exs
Normal file
|
@ -0,0 +1,55 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlugTest do
|
||||
use Pleroma.Web.ConnCase, async: true
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
|
||||
alias Pleroma.User
|
||||
|
||||
test "it halts if not public and no user is assigned", %{conn: conn} do
|
||||
set_public_to(false)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> EnsurePublicOrAuthenticatedPlug.call(%{})
|
||||
|
||||
assert conn.status == 403
|
||||
assert conn.halted == true
|
||||
end
|
||||
|
||||
test "it continues if public", %{conn: conn} do
|
||||
set_public_to(true)
|
||||
|
||||
ret_conn =
|
||||
conn
|
||||
|> EnsurePublicOrAuthenticatedPlug.call(%{})
|
||||
|
||||
assert ret_conn == conn
|
||||
end
|
||||
|
||||
test "it continues if a user is assigned, even if not public", %{conn: conn} do
|
||||
set_public_to(false)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, %User{})
|
||||
|
||||
ret_conn =
|
||||
conn
|
||||
|> EnsurePublicOrAuthenticatedPlug.call(%{})
|
||||
|
||||
assert ret_conn == conn
|
||||
end
|
||||
|
||||
defp set_public_to(value) do
|
||||
orig = Config.get!([:instance, :public])
|
||||
Config.put([:instance, :public], value)
|
||||
|
||||
on_exit(fn ->
|
||||
Config.put([:instance, :public], orig)
|
||||
end)
|
||||
end
|
||||
end
|
|
@ -7,12 +7,21 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
|
|||
alias Pleroma.Config
|
||||
alias Plug.Conn
|
||||
|
||||
test "it sends CSP headers when enabled", %{conn: conn} do
|
||||
describe "http security enabled" do
|
||||
setup do
|
||||
enabled = Config.get([:http_securiy, :enabled])
|
||||
|
||||
Config.put([:http_security, :enabled], true)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/instance")
|
||||
on_exit(fn ->
|
||||
Config.put([:http_security, :enabled], enabled)
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
test "it sends CSP headers when enabled", %{conn: conn} do
|
||||
conn = get(conn, "/api/v1/instance")
|
||||
|
||||
refute Conn.get_resp_header(conn, "x-xss-protection") == []
|
||||
refute Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
|
||||
|
@ -23,52 +32,26 @@ test "it sends CSP headers when enabled", %{conn: conn} do
|
|||
refute Conn.get_resp_header(conn, "content-security-policy") == []
|
||||
end
|
||||
|
||||
test "it does not send CSP headers when disabled", %{conn: conn} do
|
||||
Config.put([:http_security, :enabled], false)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/instance")
|
||||
|
||||
assert Conn.get_resp_header(conn, "x-xss-protection") == []
|
||||
assert Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
|
||||
assert Conn.get_resp_header(conn, "x-frame-options") == []
|
||||
assert Conn.get_resp_header(conn, "x-content-type-options") == []
|
||||
assert Conn.get_resp_header(conn, "x-download-options") == []
|
||||
assert Conn.get_resp_header(conn, "referrer-policy") == []
|
||||
assert Conn.get_resp_header(conn, "content-security-policy") == []
|
||||
end
|
||||
|
||||
test "it sends STS headers when enabled", %{conn: conn} do
|
||||
Config.put([:http_security, :enabled], true)
|
||||
Config.put([:http_security, :sts], true)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/instance")
|
||||
conn = get(conn, "/api/v1/instance")
|
||||
|
||||
refute Conn.get_resp_header(conn, "strict-transport-security") == []
|
||||
refute Conn.get_resp_header(conn, "expect-ct") == []
|
||||
end
|
||||
|
||||
test "it does not send STS headers when disabled", %{conn: conn} do
|
||||
Config.put([:http_security, :enabled], true)
|
||||
Config.put([:http_security, :sts], false)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/instance")
|
||||
conn = get(conn, "/api/v1/instance")
|
||||
|
||||
assert Conn.get_resp_header(conn, "strict-transport-security") == []
|
||||
assert Conn.get_resp_header(conn, "expect-ct") == []
|
||||
end
|
||||
|
||||
test "referrer-policy header reflects configured value", %{conn: conn} do
|
||||
Config.put([:http_security, :enabled], true)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/instance")
|
||||
conn = get(conn, "/api/v1/instance")
|
||||
|
||||
assert Conn.get_resp_header(conn, "referrer-policy") == ["same-origin"]
|
||||
|
||||
|
@ -80,4 +63,40 @@ test "referrer-policy header reflects configured value", %{conn: conn} do
|
|||
|
||||
assert Conn.get_resp_header(conn, "referrer-policy") == ["no-referrer"]
|
||||
end
|
||||
|
||||
test "it sends `report-to` & `report-uri` CSP response headers" do
|
||||
conn =
|
||||
build_conn()
|
||||
|> get("/api/v1/instance")
|
||||
|
||||
[csp] = Conn.get_resp_header(conn, "content-security-policy")
|
||||
|
||||
assert csp =~ ~r|report-uri https://endpoint.com; report-to csp-endpoint;|
|
||||
|
||||
[reply_to] = Conn.get_resp_header(conn, "reply-to")
|
||||
|
||||
assert reply_to ==
|
||||
"{\"endpoints\":[{\"url\":\"https://endpoint.com\"}],\"group\":\"csp-endpoint\",\"max-age\":10886400}"
|
||||
end
|
||||
end
|
||||
|
||||
test "it does not send CSP headers when disabled", %{conn: conn} do
|
||||
enabled = Config.get([:http_securiy, :enabled])
|
||||
|
||||
Config.put([:http_security, :enabled], false)
|
||||
|
||||
on_exit(fn ->
|
||||
Config.put([:http_security, :enabled], enabled)
|
||||
end)
|
||||
|
||||
conn = get(conn, "/api/v1/instance")
|
||||
|
||||
assert Conn.get_resp_header(conn, "x-xss-protection") == []
|
||||
assert Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
|
||||
assert Conn.get_resp_header(conn, "x-frame-options") == []
|
||||
assert Conn.get_resp_header(conn, "x-content-type-options") == []
|
||||
assert Conn.get_resp_header(conn, "x-download-options") == []
|
||||
assert Conn.get_resp_header(conn, "referrer-policy") == []
|
||||
assert Conn.get_resp_header(conn, "content-security-policy") == []
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
|
||||
use Pleroma.Web.ConnCase
|
||||
alias Pleroma.Web.HTTPSignatures
|
||||
alias Pleroma.Web.Plugs.HTTPSignaturePlug
|
||||
|
||||
import Plug.Conn
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do
|
||||
use Pleroma.Web.ConnCase, async: true
|
||||
use Pleroma.Web.ConnCase
|
||||
|
||||
alias Pleroma.Plugs.LegacyAuthenticationPlug
|
||||
alias Pleroma.User
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
defmodule Pleroma.RepoTest do
|
||||
use Pleroma.DataCase
|
||||
import Pleroma.Factory
|
||||
alias Pleroma.User
|
||||
|
||||
describe "find_resource/1" do
|
||||
test "returns user" do
|
||||
user = insert(:user)
|
||||
query = from(t in Pleroma.User, where: t.id == ^user.id)
|
||||
query = from(t in User, where: t.id == ^user.id)
|
||||
assert Repo.find_resource(query) == {:ok, user}
|
||||
end
|
||||
|
||||
test "returns not_found" do
|
||||
query = from(t in Pleroma.User, where: t.id == ^"9gBuXNpD2NyDmmxxdw")
|
||||
query = from(t in User, where: t.id == ^"9gBuXNpD2NyDmmxxdw")
|
||||
assert Repo.find_resource(query) == {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
describe "get_assoc/2" do
|
||||
test "get assoc from preloaded data" do
|
||||
user = %Pleroma.User{name: "Agent Smith"}
|
||||
user = %User{name: "Agent Smith"}
|
||||
token = %Pleroma.Web.OAuth.Token{insert(:oauth_token) | user: user}
|
||||
assert Repo.get_assoc(token, :user) == {:ok, user}
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Factory do
|
||||
use ExMachina.Ecto, repo: Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
def participation_factory do
|
||||
conversation = insert(:conversation)
|
||||
|
@ -23,7 +24,7 @@ def conversation_factory do
|
|||
end
|
||||
|
||||
def user_factory do
|
||||
user = %Pleroma.User{
|
||||
user = %User{
|
||||
name: sequence(:name, &"Test テスト User #{&1}"),
|
||||
email: sequence(:email, &"user#{&1}@example.com"),
|
||||
nickname: sequence(:nickname, &"nick#{&1}"),
|
||||
|
@ -34,16 +35,16 @@ def user_factory do
|
|||
|
||||
%{
|
||||
user
|
||||
| ap_id: Pleroma.User.ap_id(user),
|
||||
follower_address: Pleroma.User.ap_followers(user),
|
||||
following: [Pleroma.User.ap_id(user)]
|
||||
| ap_id: User.ap_id(user),
|
||||
follower_address: User.ap_followers(user),
|
||||
following: [User.ap_id(user)]
|
||||
}
|
||||
end
|
||||
|
||||
def note_factory(attrs \\ %{}) do
|
||||
text = sequence(:text, &"This is :moominmamma: note #{&1}")
|
||||
|
||||
user = insert(:user)
|
||||
user = attrs[:user] || insert(:user)
|
||||
|
||||
data = %{
|
||||
"type" => "Note",
|
||||
|
@ -113,7 +114,8 @@ def direct_note_activity_factory do
|
|||
end
|
||||
|
||||
def note_activity_factory(attrs \\ %{}) do
|
||||
note = attrs[:note] || insert(:note)
|
||||
user = attrs[:user] || insert(:user)
|
||||
note = attrs[:note] || insert(:note, user: user)
|
||||
|
||||
data = %{
|
||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
||||
|
|
49
test/tasks/database_test.exs
Normal file
49
test/tasks/database_test.exs
Normal file
|
@ -0,0 +1,49 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.DatabaseTest do
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
use Pleroma.DataCase
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
setup_all do
|
||||
Mix.shell(Mix.Shell.Process)
|
||||
|
||||
on_exit(fn ->
|
||||
Mix.shell(Mix.Shell.IO)
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
describe "running update_users_following_followers_counts" do
|
||||
test "following and followers count are updated" do
|
||||
[user, user2] = insert_pair(:user)
|
||||
{:ok, %User{following: following, info: info} = user} = User.follow(user, user2)
|
||||
|
||||
assert length(following) == 2
|
||||
assert info.follower_count == 0
|
||||
|
||||
info_cng = Ecto.Changeset.change(info, %{follower_count: 3})
|
||||
|
||||
{:ok, user} =
|
||||
user
|
||||
|> Ecto.Changeset.change(%{following: following ++ following})
|
||||
|> Ecto.Changeset.put_embed(:info, info_cng)
|
||||
|> Repo.update()
|
||||
|
||||
assert length(user.following) == 4
|
||||
assert user.info.follower_count == 3
|
||||
|
||||
assert :ok == Mix.Tasks.Pleroma.Database.run(["update_users_following_followers_counts"])
|
||||
|
||||
user = User.get_by_id(user.id)
|
||||
|
||||
assert length(user.following) == 2
|
||||
assert user.info.follower_count == 0
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,6 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.UserTest do
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
use Pleroma.DataCase
|
||||
|
||||
|
@ -338,4 +339,31 @@ test "activities are deleted" do
|
|||
assert message == "User #{nickname} statuses deleted."
|
||||
end
|
||||
end
|
||||
|
||||
describe "running toggle_confirmed" do
|
||||
test "user is confirmed" do
|
||||
%{id: id, nickname: nickname} = insert(:user, info: %{confirmation_pending: false})
|
||||
|
||||
assert :ok = Mix.Tasks.Pleroma.User.run(["toggle_confirmed", nickname])
|
||||
assert_received {:mix_shell, :info, [message]}
|
||||
assert message == "#{nickname} needs confirmation."
|
||||
|
||||
user = Repo.get(User, id)
|
||||
assert user.info.confirmation_pending
|
||||
assert user.info.confirmation_token
|
||||
end
|
||||
|
||||
test "user is not confirmed" do
|
||||
%{id: id, nickname: nickname} =
|
||||
insert(:user, info: %{confirmation_pending: true, confirmation_token: "some token"})
|
||||
|
||||
assert :ok = Mix.Tasks.Pleroma.User.run(["toggle_confirmed", nickname])
|
||||
assert_received {:mix_shell, :info, [message]}
|
||||
assert message == "#{nickname} doesn't need confirmation."
|
||||
|
||||
user = Repo.get(User, id)
|
||||
refute user.info.confirmation_pending
|
||||
refute user.info.confirmation_token
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -277,7 +277,7 @@ test "it requires an email, name, nickname and password, bio is optional" do
|
|||
end
|
||||
|
||||
test "it restricts certain nicknames" do
|
||||
[restricted_name | _] = Pleroma.Config.get([Pleroma.User, :restricted_nicknames])
|
||||
[restricted_name | _] = Pleroma.Config.get([User, :restricted_nicknames])
|
||||
|
||||
assert is_bitstring(restricted_name)
|
||||
|
||||
|
@ -626,6 +626,37 @@ test "it sets the info->follower_count property" do
|
|||
end
|
||||
end
|
||||
|
||||
describe "remove duplicates from following list" do
|
||||
test "it removes duplicates" do
|
||||
user = insert(:user)
|
||||
follower = insert(:user)
|
||||
|
||||
{:ok, %User{following: following} = follower} = User.follow(follower, user)
|
||||
assert length(following) == 2
|
||||
|
||||
{:ok, follower} =
|
||||
follower
|
||||
|> User.update_changeset(%{following: following ++ following})
|
||||
|> Repo.update()
|
||||
|
||||
assert length(follower.following) == 4
|
||||
|
||||
{:ok, follower} = User.remove_duplicated_following(follower)
|
||||
assert length(follower.following) == 2
|
||||
end
|
||||
|
||||
test "it does nothing when following is uniq" do
|
||||
user = insert(:user)
|
||||
follower = insert(:user)
|
||||
|
||||
{:ok, follower} = User.follow(follower, user)
|
||||
assert length(follower.following) == 2
|
||||
|
||||
{:ok, follower} = User.remove_duplicated_following(follower)
|
||||
assert length(follower.following) == 2
|
||||
end
|
||||
end
|
||||
|
||||
describe "follow_import" do
|
||||
test "it imports user followings from list" do
|
||||
[user1, user2, user3] = insert_list(3, :user)
|
||||
|
@ -873,7 +904,6 @@ test "hide a user's statuses from timelines and notifications" do
|
|||
|
||||
assert [activity] ==
|
||||
ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
|
||||
|> ActivityPub.contain_timeline(user2)
|
||||
|
||||
{:ok, _user} = User.deactivate(user)
|
||||
|
||||
|
@ -882,7 +912,6 @@ test "hide a user's statuses from timelines and notifications" do
|
|||
|
||||
assert [] ==
|
||||
ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
|
||||
|> ActivityPub.contain_timeline(user2)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1194,14 +1223,32 @@ test "follower count is updated when a follower is blocked" do
|
|||
follower2 = insert(:user)
|
||||
follower3 = insert(:user)
|
||||
|
||||
{:ok, follower} = Pleroma.User.follow(follower, user)
|
||||
{:ok, _follower2} = Pleroma.User.follow(follower2, user)
|
||||
{:ok, _follower3} = Pleroma.User.follow(follower3, user)
|
||||
{:ok, follower} = User.follow(follower, user)
|
||||
{:ok, _follower2} = User.follow(follower2, user)
|
||||
{:ok, _follower3} = User.follow(follower3, user)
|
||||
|
||||
{:ok, _} = Pleroma.User.block(user, follower)
|
||||
{:ok, _} = User.block(user, follower)
|
||||
|
||||
user_show = Pleroma.Web.TwitterAPI.UserView.render("show.json", %{user: user})
|
||||
|
||||
assert Map.get(user_show, "followers_count") == 2
|
||||
end
|
||||
|
||||
describe "toggle_confirmation/1" do
|
||||
test "if user is confirmed" do
|
||||
user = insert(:user, info: %{confirmation_pending: false})
|
||||
{:ok, user} = User.toggle_confirmation(user)
|
||||
|
||||
assert user.info.confirmation_pending
|
||||
assert user.info.confirmation_token
|
||||
end
|
||||
|
||||
test "if user is unconfirmed" do
|
||||
user = insert(:user, info: %{confirmation_pending: true, confirmation_token: "some token"})
|
||||
{:ok, user} = User.toggle_confirmation(user)
|
||||
|
||||
refute user.info.confirmation_pending
|
||||
refute user.info.confirmation_token
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -462,6 +462,29 @@ test "doesn't return announce activities concerning blocked users" do
|
|||
refute Enum.member?(activities, activity_three.id)
|
||||
end
|
||||
|
||||
test "doesn't return activities from blocked domains" do
|
||||
domain = "dogwhistle.zone"
|
||||
domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
|
||||
note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
|
||||
activity = insert(:note_activity, %{note: note})
|
||||
user = insert(:user)
|
||||
{:ok, user} = User.block_domain(user, domain)
|
||||
|
||||
activities =
|
||||
ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
|
||||
|
||||
refute activity in activities
|
||||
|
||||
followed_user = insert(:user)
|
||||
ActivityPub.follow(user, followed_user)
|
||||
{:ok, repeat_activity, _} = CommonAPI.repeat(activity.id, followed_user)
|
||||
|
||||
activities =
|
||||
ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
|
||||
|
||||
refute repeat_activity in activities
|
||||
end
|
||||
|
||||
test "doesn't return muted activities" do
|
||||
activity_one = insert(:note_activity)
|
||||
activity_two = insert(:note_activity)
|
||||
|
@ -960,17 +983,21 @@ test "it filters broken threads" do
|
|||
"in_reply_to_status_id" => private_activity_2.id
|
||||
})
|
||||
|
||||
activities = ActivityPub.fetch_activities([user1.ap_id | user1.following])
|
||||
activities =
|
||||
ActivityPub.fetch_activities([user1.ap_id | user1.following])
|
||||
|> Enum.map(fn a -> a.id end)
|
||||
|
||||
private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
|
||||
|
||||
assert [public_activity, private_activity_1, private_activity_3] == activities
|
||||
assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
|
||||
|
||||
assert length(activities) == 3
|
||||
|
||||
activities = ActivityPub.contain_timeline(activities, user1)
|
||||
activities =
|
||||
ActivityPub.fetch_activities([user1.ap_id | user1.following], %{"user" => user1})
|
||||
|> Enum.map(fn a -> a.id end)
|
||||
|
||||
assert [public_activity, private_activity_1] == activities
|
||||
assert [public_activity.id, private_activity_1.id] == activities
|
||||
assert length(activities) == 2
|
||||
end
|
||||
end
|
||||
|
|
192
test/web/activity_pub/mrf/simple_policy_test.exs
Normal file
192
test/web/activity_pub/mrf/simple_policy_test.exs
Normal file
|
@ -0,0 +1,192 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
|
||||
use Pleroma.DataCase
|
||||
import Pleroma.Factory
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Web.ActivityPub.MRF.SimplePolicy
|
||||
|
||||
setup do
|
||||
orig = Config.get!(:mrf_simple)
|
||||
|
||||
Config.put(:mrf_simple,
|
||||
media_removal: [],
|
||||
media_nsfw: [],
|
||||
federated_timeline_removal: [],
|
||||
reject: [],
|
||||
accept: []
|
||||
)
|
||||
|
||||
on_exit(fn ->
|
||||
Config.put(:mrf_simple, orig)
|
||||
end)
|
||||
end
|
||||
|
||||
describe "when :media_removal" do
|
||||
test "is empty" do
|
||||
Config.put([:mrf_simple, :media_removal], [])
|
||||
media_message = build_media_message()
|
||||
local_message = build_local_message()
|
||||
|
||||
assert SimplePolicy.filter(media_message) == {:ok, media_message}
|
||||
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
||||
end
|
||||
|
||||
test "has a matching host" do
|
||||
Config.put([:mrf_simple, :media_removal], ["remote.instance"])
|
||||
media_message = build_media_message()
|
||||
local_message = build_local_message()
|
||||
|
||||
assert SimplePolicy.filter(media_message) ==
|
||||
{:ok,
|
||||
media_message
|
||||
|> Map.put("object", Map.delete(media_message["object"], "attachment"))}
|
||||
|
||||
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
||||
end
|
||||
end
|
||||
|
||||
describe "when :media_nsfw" do
|
||||
test "is empty" do
|
||||
Config.put([:mrf_simple, :media_nsfw], [])
|
||||
media_message = build_media_message()
|
||||
local_message = build_local_message()
|
||||
|
||||
assert SimplePolicy.filter(media_message) == {:ok, media_message}
|
||||
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
||||
end
|
||||
|
||||
test "has a matching host" do
|
||||
Config.put([:mrf_simple, :media_nsfw], ["remote.instance"])
|
||||
media_message = build_media_message()
|
||||
local_message = build_local_message()
|
||||
|
||||
assert SimplePolicy.filter(media_message) ==
|
||||
{:ok,
|
||||
media_message
|
||||
|> put_in(["object", "tag"], ["foo", "nsfw"])
|
||||
|> put_in(["object", "sensitive"], true)}
|
||||
|
||||
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
||||
end
|
||||
end
|
||||
|
||||
defp build_media_message do
|
||||
%{
|
||||
"actor" => "https://remote.instance/users/bob",
|
||||
"type" => "Create",
|
||||
"object" => %{
|
||||
"attachment" => [%{}],
|
||||
"tag" => ["foo"],
|
||||
"sensitive" => false
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
describe "when :federated_timeline_removal" do
|
||||
test "is empty" do
|
||||
Config.put([:mrf_simple, :federated_timeline_removal], [])
|
||||
{_, ftl_message} = build_ftl_actor_and_message()
|
||||
local_message = build_local_message()
|
||||
|
||||
assert SimplePolicy.filter(ftl_message) == {:ok, ftl_message}
|
||||
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
||||
end
|
||||
|
||||
test "has a matching host" do
|
||||
{actor, ftl_message} = build_ftl_actor_and_message()
|
||||
|
||||
ftl_message_actor_host =
|
||||
ftl_message
|
||||
|> Map.fetch!("actor")
|
||||
|> URI.parse()
|
||||
|> Map.fetch!(:host)
|
||||
|
||||
Config.put([:mrf_simple, :federated_timeline_removal], [ftl_message_actor_host])
|
||||
local_message = build_local_message()
|
||||
|
||||
assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
|
||||
assert actor.follower_address in ftl_message["to"]
|
||||
refute actor.follower_address in ftl_message["cc"]
|
||||
refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
|
||||
assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"]
|
||||
|
||||
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
||||
end
|
||||
end
|
||||
|
||||
defp build_ftl_actor_and_message do
|
||||
actor = insert(:user)
|
||||
|
||||
{actor,
|
||||
%{
|
||||
"actor" => actor.ap_id,
|
||||
"to" => ["https://www.w3.org/ns/activitystreams#Public", "http://foo.bar/baz"],
|
||||
"cc" => [actor.follower_address, "http://foo.bar/qux"]
|
||||
}}
|
||||
end
|
||||
|
||||
describe "when :reject" do
|
||||
test "is empty" do
|
||||
Config.put([:mrf_simple, :reject], [])
|
||||
|
||||
remote_message = build_remote_message()
|
||||
|
||||
assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
|
||||
end
|
||||
|
||||
test "has a matching host" do
|
||||
Config.put([:mrf_simple, :reject], ["remote.instance"])
|
||||
|
||||
remote_message = build_remote_message()
|
||||
|
||||
assert SimplePolicy.filter(remote_message) == {:reject, nil}
|
||||
end
|
||||
end
|
||||
|
||||
describe "when :accept" do
|
||||
test "is empty" do
|
||||
Config.put([:mrf_simple, :accept], [])
|
||||
|
||||
local_message = build_local_message()
|
||||
remote_message = build_remote_message()
|
||||
|
||||
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
||||
assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
|
||||
end
|
||||
|
||||
test "is not empty but it doesn't have a matching host" do
|
||||
Config.put([:mrf_simple, :accept], ["non.matching.remote"])
|
||||
|
||||
local_message = build_local_message()
|
||||
remote_message = build_remote_message()
|
||||
|
||||
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
||||
assert SimplePolicy.filter(remote_message) == {:reject, nil}
|
||||
end
|
||||
|
||||
test "has a matching host" do
|
||||
Config.put([:mrf_simple, :accept], ["remote.instance"])
|
||||
|
||||
local_message = build_local_message()
|
||||
remote_message = build_remote_message()
|
||||
|
||||
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
||||
assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
|
||||
end
|
||||
end
|
||||
|
||||
defp build_local_message do
|
||||
%{
|
||||
"actor" => "#{Pleroma.Web.base_url()}/users/alice",
|
||||
"to" => [],
|
||||
"cc" => []
|
||||
}
|
||||
end
|
||||
|
||||
defp build_remote_message do
|
||||
%{"actor" => "https://remote.instance/users/bob"}
|
||||
end
|
||||
end
|
|
@ -95,4 +95,26 @@ test "visible_for_user?", %{
|
|||
refute Visibility.visible_for_user?(private, unrelated)
|
||||
refute Visibility.visible_for_user?(direct, unrelated)
|
||||
end
|
||||
|
||||
test "doesn't die when the user doesn't exist",
|
||||
%{
|
||||
direct: direct,
|
||||
user: user
|
||||
} do
|
||||
Repo.delete(user)
|
||||
Cachex.clear(:user_cache)
|
||||
refute Visibility.is_private?(direct)
|
||||
end
|
||||
|
||||
test "get_visibility", %{
|
||||
public: public,
|
||||
private: private,
|
||||
direct: direct,
|
||||
unlisted: unlisted
|
||||
} do
|
||||
assert Visibility.get_visibility(public) == "public"
|
||||
assert Visibility.get_visibility(private) == "private"
|
||||
assert Visibility.get_visibility(direct) == "direct"
|
||||
assert Visibility.get_visibility(unlisted) == "unlisted"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
|
||||
use Pleroma.Web.ConnCase
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserInviteToken
|
||||
alias Pleroma.Web.CommonAPI
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "/api/pleroma/admin/users" do
|
||||
|
@ -949,4 +951,329 @@ test "with token" do
|
|||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/pleroma/admin/reports/:id" do
|
||||
setup %{conn: conn} do
|
||||
admin = insert(:user, info: %{is_admin: true})
|
||||
|
||||
%{conn: assign(conn, :user, admin)}
|
||||
end
|
||||
|
||||
test "returns report by its id", %{conn: conn} do
|
||||
[reporter, target_user] = insert_pair(:user)
|
||||
activity = insert(:note_activity, user: target_user)
|
||||
|
||||
{:ok, %{id: report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended",
|
||||
"status_ids" => [activity.id]
|
||||
})
|
||||
|
||||
response =
|
||||
conn
|
||||
|> get("/api/pleroma/admin/reports/#{report_id}")
|
||||
|> json_response(:ok)
|
||||
|
||||
assert response["id"] == report_id
|
||||
end
|
||||
|
||||
test "returns 404 when report id is invalid", %{conn: conn} do
|
||||
conn = get(conn, "/api/pleroma/admin/reports/test")
|
||||
|
||||
assert json_response(conn, :not_found) == "Not found"
|
||||
end
|
||||
end
|
||||
|
||||
describe "PUT /api/pleroma/admin/reports/:id" do
|
||||
setup %{conn: conn} do
|
||||
admin = insert(:user, info: %{is_admin: true})
|
||||
[reporter, target_user] = insert_pair(:user)
|
||||
activity = insert(:note_activity, user: target_user)
|
||||
|
||||
{:ok, %{id: report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended",
|
||||
"status_ids" => [activity.id]
|
||||
})
|
||||
|
||||
%{conn: assign(conn, :user, admin), id: report_id}
|
||||
end
|
||||
|
||||
test "mark report as resolved", %{conn: conn, id: id} do
|
||||
response =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/reports/#{id}", %{"state" => "resolved"})
|
||||
|> json_response(:ok)
|
||||
|
||||
assert response["state"] == "resolved"
|
||||
end
|
||||
|
||||
test "closes report", %{conn: conn, id: id} do
|
||||
response =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/reports/#{id}", %{"state" => "closed"})
|
||||
|> json_response(:ok)
|
||||
|
||||
assert response["state"] == "closed"
|
||||
end
|
||||
|
||||
test "returns 400 when state is unknown", %{conn: conn, id: id} do
|
||||
conn =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/reports/#{id}", %{"state" => "test"})
|
||||
|
||||
assert json_response(conn, :bad_request) == "Unsupported state"
|
||||
end
|
||||
|
||||
test "returns 404 when report is not exist", %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/reports/test", %{"state" => "closed"})
|
||||
|
||||
assert json_response(conn, :not_found) == "Not found"
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/pleroma/admin/reports" do
|
||||
setup %{conn: conn} do
|
||||
admin = insert(:user, info: %{is_admin: true})
|
||||
|
||||
%{conn: assign(conn, :user, admin)}
|
||||
end
|
||||
|
||||
test "returns empty response when no reports created", %{conn: conn} do
|
||||
response =
|
||||
conn
|
||||
|> get("/api/pleroma/admin/reports")
|
||||
|> json_response(:ok)
|
||||
|
||||
assert Enum.empty?(response["reports"])
|
||||
end
|
||||
|
||||
test "returns reports", %{conn: conn} do
|
||||
[reporter, target_user] = insert_pair(:user)
|
||||
activity = insert(:note_activity, user: target_user)
|
||||
|
||||
{:ok, %{id: report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended",
|
||||
"status_ids" => [activity.id]
|
||||
})
|
||||
|
||||
response =
|
||||
conn
|
||||
|> get("/api/pleroma/admin/reports")
|
||||
|> json_response(:ok)
|
||||
|
||||
[report] = response["reports"]
|
||||
|
||||
assert length(response["reports"]) == 1
|
||||
assert report["id"] == report_id
|
||||
end
|
||||
|
||||
test "returns reports with specified state", %{conn: conn} do
|
||||
[reporter, target_user] = insert_pair(:user)
|
||||
activity = insert(:note_activity, user: target_user)
|
||||
|
||||
{:ok, %{id: first_report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended",
|
||||
"status_ids" => [activity.id]
|
||||
})
|
||||
|
||||
{:ok, %{id: second_report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I don't like this user"
|
||||
})
|
||||
|
||||
CommonAPI.update_report_state(second_report_id, "closed")
|
||||
|
||||
response =
|
||||
conn
|
||||
|> get("/api/pleroma/admin/reports", %{
|
||||
"state" => "open"
|
||||
})
|
||||
|> json_response(:ok)
|
||||
|
||||
[open_report] = response["reports"]
|
||||
|
||||
assert length(response["reports"]) == 1
|
||||
assert open_report["id"] == first_report_id
|
||||
|
||||
response =
|
||||
conn
|
||||
|> get("/api/pleroma/admin/reports", %{
|
||||
"state" => "closed"
|
||||
})
|
||||
|> json_response(:ok)
|
||||
|
||||
[closed_report] = response["reports"]
|
||||
|
||||
assert length(response["reports"]) == 1
|
||||
assert closed_report["id"] == second_report_id
|
||||
|
||||
response =
|
||||
conn
|
||||
|> get("/api/pleroma/admin/reports", %{
|
||||
"state" => "resolved"
|
||||
})
|
||||
|> json_response(:ok)
|
||||
|
||||
assert Enum.empty?(response["reports"])
|
||||
end
|
||||
|
||||
test "returns 403 when requested by a non-admin" do
|
||||
user = insert(:user)
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, user)
|
||||
|> get("/api/pleroma/admin/reports")
|
||||
|
||||
assert json_response(conn, :forbidden) == %{"error" => "User is not admin."}
|
||||
end
|
||||
|
||||
test "returns 403 when requested by anonymous" do
|
||||
conn =
|
||||
build_conn()
|
||||
|> get("/api/pleroma/admin/reports")
|
||||
|
||||
assert json_response(conn, :forbidden) == %{"error" => "Invalid credentials."}
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /api/pleroma/admin/reports/:id/respond" do
|
||||
setup %{conn: conn} do
|
||||
admin = insert(:user, info: %{is_admin: true})
|
||||
|
||||
%{conn: assign(conn, :user, admin)}
|
||||
end
|
||||
|
||||
test "returns created dm", %{conn: conn} do
|
||||
[reporter, target_user] = insert_pair(:user)
|
||||
activity = insert(:note_activity, user: target_user)
|
||||
|
||||
{:ok, %{id: report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended",
|
||||
"status_ids" => [activity.id]
|
||||
})
|
||||
|
||||
response =
|
||||
conn
|
||||
|> post("/api/pleroma/admin/reports/#{report_id}/respond", %{
|
||||
"status" => "I will check it out"
|
||||
})
|
||||
|> json_response(:ok)
|
||||
|
||||
recipients = Enum.map(response["mentions"], & &1["username"])
|
||||
|
||||
assert conn.assigns[:user].nickname in recipients
|
||||
assert reporter.nickname in recipients
|
||||
assert response["content"] == "I will check it out"
|
||||
assert response["visibility"] == "direct"
|
||||
end
|
||||
|
||||
test "returns 400 when status is missing", %{conn: conn} do
|
||||
conn = post(conn, "/api/pleroma/admin/reports/test/respond")
|
||||
|
||||
assert json_response(conn, :bad_request) == "Invalid parameters"
|
||||
end
|
||||
|
||||
test "returns 404 when report id is invalid", %{conn: conn} do
|
||||
conn =
|
||||
post(conn, "/api/pleroma/admin/reports/test/respond", %{
|
||||
"status" => "foo"
|
||||
})
|
||||
|
||||
assert json_response(conn, :not_found) == "Not found"
|
||||
end
|
||||
end
|
||||
|
||||
describe "PUT /api/pleroma/admin/statuses/:id" do
|
||||
setup %{conn: conn} do
|
||||
admin = insert(:user, info: %{is_admin: true})
|
||||
activity = insert(:note_activity)
|
||||
|
||||
%{conn: assign(conn, :user, admin), id: activity.id}
|
||||
end
|
||||
|
||||
test "toggle sensitive flag", %{conn: conn, id: id} do
|
||||
response =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "true"})
|
||||
|> json_response(:ok)
|
||||
|
||||
assert response["sensitive"]
|
||||
|
||||
response =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "false"})
|
||||
|> json_response(:ok)
|
||||
|
||||
refute response["sensitive"]
|
||||
end
|
||||
|
||||
test "change visibility flag", %{conn: conn, id: id} do
|
||||
response =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "public"})
|
||||
|> json_response(:ok)
|
||||
|
||||
assert response["visibility"] == "public"
|
||||
|
||||
response =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "private"})
|
||||
|> json_response(:ok)
|
||||
|
||||
assert response["visibility"] == "private"
|
||||
|
||||
response =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "unlisted"})
|
||||
|> json_response(:ok)
|
||||
|
||||
assert response["visibility"] == "unlisted"
|
||||
end
|
||||
|
||||
test "returns 400 when visibility is unknown", %{conn: conn, id: id} do
|
||||
conn =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "test"})
|
||||
|
||||
assert json_response(conn, :bad_request) == "Unsupported visibility"
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE /api/pleroma/admin/statuses/:id" do
|
||||
setup %{conn: conn} do
|
||||
admin = insert(:user, info: %{is_admin: true})
|
||||
activity = insert(:note_activity)
|
||||
|
||||
%{conn: assign(conn, :user, admin), id: activity.id}
|
||||
end
|
||||
|
||||
test "deletes status", %{conn: conn, id: id} do
|
||||
conn
|
||||
|> delete("/api/pleroma/admin/statuses/#{id}")
|
||||
|> json_response(:ok)
|
||||
|
||||
refute Activity.get_by_id(id)
|
||||
end
|
||||
|
||||
test "returns error when status is not exist", %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|> delete("/api/pleroma/admin/statuses/test")
|
||||
|
||||
assert json_response(conn, :bad_request) == "Could not delete"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -87,6 +87,28 @@ test "it filters out obviously bad tags when accepting a post as Markdown" do
|
|||
|
||||
assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
|
||||
end
|
||||
|
||||
test "it does not allow replies to direct messages that are not direct messages themselves" do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
|
||||
|
||||
assert {:ok, _} =
|
||||
CommonAPI.post(user, %{
|
||||
"status" => "suya..",
|
||||
"visibility" => "direct",
|
||||
"in_reply_to_status_id" => activity.id
|
||||
})
|
||||
|
||||
Enum.each(["public", "private", "unlisted"], fn visibility ->
|
||||
assert {:error, {:private_to_public, _}} =
|
||||
CommonAPI.post(user, %{
|
||||
"status" => "suya..",
|
||||
"visibility" => visibility,
|
||||
"in_reply_to_status_id" => activity.id
|
||||
})
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "reactions" do
|
||||
|
@ -239,10 +261,41 @@ test "creates a report" do
|
|||
data: %{
|
||||
"type" => "Flag",
|
||||
"content" => ^comment,
|
||||
"object" => [^target_ap_id, ^activity_ap_id]
|
||||
"object" => [^target_ap_id, ^activity_ap_id],
|
||||
"state" => "open"
|
||||
}
|
||||
} = flag_activity
|
||||
end
|
||||
|
||||
test "updates report state" do
|
||||
[reporter, target_user] = insert_pair(:user)
|
||||
activity = insert(:note_activity, user: target_user)
|
||||
|
||||
{:ok, %Activity{id: report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended",
|
||||
"status_ids" => [activity.id]
|
||||
})
|
||||
|
||||
{:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
|
||||
|
||||
assert report.data["state"] == "resolved"
|
||||
end
|
||||
|
||||
test "does not update report state when state is unsupported" do
|
||||
[reporter, target_user] = insert_pair(:user)
|
||||
activity = insert(:note_activity, user: target_user)
|
||||
|
||||
{:ok, %Activity{id: report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended",
|
||||
"status_ids" => [activity.id]
|
||||
})
|
||||
|
||||
assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
|
||||
end
|
||||
end
|
||||
|
||||
describe "reblog muting" do
|
||||
|
@ -257,14 +310,14 @@ test "creates a report" do
|
|||
test "add a reblog mute", %{muter: muter, muted: muted} do
|
||||
{:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
|
||||
|
||||
assert Pleroma.User.showing_reblogs?(muter, muted) == false
|
||||
assert User.showing_reblogs?(muter, muted) == false
|
||||
end
|
||||
|
||||
test "remove a reblog mute", %{muter: muter, muted: muted} do
|
||||
{:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
|
||||
{:ok, muter} = CommonAPI.show_reblogs(muter, muted)
|
||||
|
||||
assert Pleroma.User.showing_reblogs?(muter, muted) == true
|
||||
assert User.showing_reblogs?(muter, muted) == true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,194 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
# http signatures
|
||||
# Test data from https://tools.ietf.org/html/draft-cavage-http-signatures-08#appendix-C
|
||||
defmodule Pleroma.Web.HTTPSignaturesTest do
|
||||
use Pleroma.DataCase
|
||||
alias Pleroma.Web.HTTPSignatures
|
||||
import Pleroma.Factory
|
||||
import Tesla.Mock
|
||||
|
||||
setup do
|
||||
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||
:ok
|
||||
end
|
||||
|
||||
@public_key hd(:public_key.pem_decode(File.read!("test/web/http_sigs/pub.key")))
|
||||
|> :public_key.pem_entry_decode()
|
||||
|
||||
@headers %{
|
||||
"(request-target)" => "post /foo?param=value&pet=dog",
|
||||
"host" => "example.com",
|
||||
"date" => "Thu, 05 Jan 2014 21:31:40 GMT",
|
||||
"content-type" => "application/json",
|
||||
"digest" => "SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=",
|
||||
"content-length" => "18"
|
||||
}
|
||||
|
||||
@default_signature """
|
||||
keyId="Test",algorithm="rsa-sha256",signature="jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9HpFQlG7N4YcJPteKTu4MWCLyk+gIr0wDgqtLWf9NLpMAMimdfsH7FSWGfbMFSrsVTHNTk0rK3usrfFnti1dxsM4jl0kYJCKTGI/UWkqiaxwNiKqGcdlEDrTcUhhsFsOIo8VhddmZTZ8w="
|
||||
"""
|
||||
|
||||
@basic_signature """
|
||||
keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date",signature="HUxc9BS3P/kPhSmJo+0pQ4IsCo007vkv6bUm4Qehrx+B1Eo4Mq5/6KylET72ZpMUS80XvjlOPjKzxfeTQj4DiKbAzwJAb4HX3qX6obQTa00/qPDXlMepD2JtTw33yNnm/0xV7fQuvILN/ys+378Ysi082+4xBQFwvhNvSoVsGv4="
|
||||
"""
|
||||
|
||||
@all_headers_signature """
|
||||
keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date content-type digest content-length",signature="Ef7MlxLXoBovhil3AlyjtBwAL9g4TN3tibLj7uuNB3CROat/9KaeQ4hW2NiJ+pZ6HQEOx9vYZAyi+7cmIkmJszJCut5kQLAwuX+Ms/mUFvpKlSo9StS2bMXDBNjOh4Auj774GFj4gwjS+3NhFeoqyr/MuN6HsEnkvn6zdgfE2i0="
|
||||
"""
|
||||
|
||||
test "split up a signature" do
|
||||
expected = %{
|
||||
"keyId" => "Test",
|
||||
"algorithm" => "rsa-sha256",
|
||||
"signature" =>
|
||||
"jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9HpFQlG7N4YcJPteKTu4MWCLyk+gIr0wDgqtLWf9NLpMAMimdfsH7FSWGfbMFSrsVTHNTk0rK3usrfFnti1dxsM4jl0kYJCKTGI/UWkqiaxwNiKqGcdlEDrTcUhhsFsOIo8VhddmZTZ8w=",
|
||||
"headers" => ["date"]
|
||||
}
|
||||
|
||||
assert HTTPSignatures.split_signature(@default_signature) == expected
|
||||
end
|
||||
|
||||
test "validates the default case" do
|
||||
signature = HTTPSignatures.split_signature(@default_signature)
|
||||
assert HTTPSignatures.validate(@headers, signature, @public_key)
|
||||
end
|
||||
|
||||
test "validates the basic case" do
|
||||
signature = HTTPSignatures.split_signature(@basic_signature)
|
||||
assert HTTPSignatures.validate(@headers, signature, @public_key)
|
||||
end
|
||||
|
||||
test "validates the all-headers case" do
|
||||
signature = HTTPSignatures.split_signature(@all_headers_signature)
|
||||
assert HTTPSignatures.validate(@headers, signature, @public_key)
|
||||
end
|
||||
|
||||
test "it contructs a signing string" do
|
||||
expected = "date: Thu, 05 Jan 2014 21:31:40 GMT\ncontent-length: 18"
|
||||
assert expected == HTTPSignatures.build_signing_string(@headers, ["date", "content-length"])
|
||||
end
|
||||
|
||||
test "it validates a conn" do
|
||||
public_key_pem =
|
||||
"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnGb42rPZIapY4Hfhxrgn\nxKVJczBkfDviCrrYaYjfGxawSw93dWTUlenCVTymJo8meBlFgIQ70ar4rUbzl6GX\nMYvRdku072d1WpglNHXkjKPkXQgngFDrh2sGKtNB/cEtJcAPRO8OiCgPFqRtMiNM\nc8VdPfPdZuHEIZsJ/aUM38EnqHi9YnVDQik2xxDe3wPghOhqjxUM6eLC9jrjI+7i\naIaEygUdyst9qVg8e2FGQlwAeS2Eh8ygCxn+bBlT5OyV59jSzbYfbhtF2qnWHtZy\nkL7KOOwhIfGs7O9SoR2ZVpTEQ4HthNzainIe/6iCR5HGrao/T8dygweXFYRv+k5A\nPQIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||
|
||||
[public_key] = :public_key.pem_decode(public_key_pem)
|
||||
|
||||
public_key =
|
||||
public_key
|
||||
|> :public_key.pem_entry_decode()
|
||||
|
||||
conn = %{
|
||||
req_headers: [
|
||||
{"host", "localtesting.pleroma.lol"},
|
||||
{"connection", "close"},
|
||||
{"content-length", "2316"},
|
||||
{"user-agent", "http.rb/2.2.2 (Mastodon/2.1.0.rc3; +http://mastodon.example.org/)"},
|
||||
{"date", "Sun, 10 Dec 2017 14:23:49 GMT"},
|
||||
{"digest", "SHA-256=x/bHADMW8qRrq2NdPb5P9fl0lYpKXXpe5h5maCIL0nM="},
|
||||
{"content-type", "application/activity+json"},
|
||||
{"(request-target)", "post /users/demiurge/inbox"},
|
||||
{"signature",
|
||||
"keyId=\"http://mastodon.example.org/users/admin#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"i0FQvr51sj9BoWAKydySUAO1RDxZmNY6g7M62IA7VesbRSdFZZj9/fZapLp6YSuvxUF0h80ZcBEq9GzUDY3Chi9lx6yjpUAS2eKb+Am/hY3aswhnAfYd6FmIdEHzsMrpdKIRqO+rpQ2tR05LwiGEHJPGS0p528NvyVxrxMT5H5yZS5RnxY5X2HmTKEgKYYcvujdv7JWvsfH88xeRS7Jlq5aDZkmXvqoR4wFyfgnwJMPLel8P/BUbn8BcXglH/cunR0LUP7sflTxEz+Rv5qg+9yB8zgBsB4C0233WpcJxjeD6Dkq0EcoJObBR56F8dcb7NQtUDu7x6xxzcgSd7dHm5w==\""}
|
||||
]
|
||||
}
|
||||
|
||||
assert HTTPSignatures.validate_conn(conn, public_key)
|
||||
end
|
||||
|
||||
test "it validates a conn and fetches the key" do
|
||||
conn = %{
|
||||
params: %{"actor" => "http://mastodon.example.org/users/admin"},
|
||||
req_headers: [
|
||||
{"host", "localtesting.pleroma.lol"},
|
||||
{"x-forwarded-for", "127.0.0.1"},
|
||||
{"connection", "close"},
|
||||
{"content-length", "2307"},
|
||||
{"user-agent", "http.rb/2.2.2 (Mastodon/2.1.0.rc3; +http://mastodon.example.org/)"},
|
||||
{"date", "Sun, 11 Feb 2018 17:12:01 GMT"},
|
||||
{"digest", "SHA-256=UXsAnMtR9c7mi1FOf6HRMtPgGI1yi2e9nqB/j4rZ99I="},
|
||||
{"content-type", "application/activity+json"},
|
||||
{"signature",
|
||||
"keyId=\"http://mastodon.example.org/users/admin#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"qXKqpQXUpC3d9bZi2ioEeAqP8nRMD021CzH1h6/w+LRk4Hj31ARJHDwQM+QwHltwaLDUepshMfz2WHSXAoLmzWtvv7xRwY+mRqe+NGk1GhxVZ/LSrO/Vp7rYfDpfdVtkn36LU7/Bzwxvvaa4ZWYltbFsRBL0oUrqsfmJFswNCQIG01BB52BAhGSCORHKtQyzo1IZHdxl8y80pzp/+FOK2SmHkqWkP9QbaU1qTZzckL01+7M5btMW48xs9zurEqC2sM5gdWMQSZyL6isTV5tmkTZrY8gUFPBJQZgihK44v3qgfWojYaOwM8ATpiv7NG8wKN/IX7clDLRMA8xqKRCOKw==\""},
|
||||
{"(request-target)", "post /users/demiurge/inbox"}
|
||||
]
|
||||
}
|
||||
|
||||
assert HTTPSignatures.validate_conn(conn)
|
||||
end
|
||||
|
||||
test "validate this" do
|
||||
conn = %{
|
||||
params: %{"actor" => "https://niu.moe/users/rye"},
|
||||
req_headers: [
|
||||
{"x-forwarded-for", "149.202.73.191"},
|
||||
{"host", "testing.pleroma.lol"},
|
||||
{"x-cluster-client-ip", "149.202.73.191"},
|
||||
{"connection", "upgrade"},
|
||||
{"content-length", "2396"},
|
||||
{"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://niu.moe/)"},
|
||||
{"date", "Sun, 18 Feb 2018 20:31:51 GMT"},
|
||||
{"digest", "SHA-256=dzH+vLyhxxALoe9RJdMl4hbEV9bGAZnSfddHQzeidTU="},
|
||||
{"content-type", "application/activity+json"},
|
||||
{"signature",
|
||||
"keyId=\"https://niu.moe/users/rye#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"wtxDg4kIpW7nsnUcVJhBk6SgJeDZOocr8yjsnpDRqE52lR47SH6X7G16r7L1AUJdlnbfx7oqcvomoIJoHB3ghP6kRnZW6MyTMZ2jPoi3g0iC5RDqv6oAmDSO14iw6U+cqZbb3P/odS5LkbThF0UNXcfenVNfsKosIJycFjhNQc54IPCDXYq/7SArEKJp8XwEgzmiC2MdxlkVIUSTQYfjM4EG533cwlZocw1mw72e5mm/owTa80BUZAr0OOuhoWARJV9btMb02ZyAF6SCSoGPTA37wHyfM1Dk88NHf7Z0Aov/Fl65dpRM+XyoxdkpkrhDfH9qAx4iuV2VEWddQDiXHA==\""},
|
||||
{"(request-target)", "post /inbox"}
|
||||
]
|
||||
}
|
||||
|
||||
assert HTTPSignatures.validate_conn(conn)
|
||||
end
|
||||
|
||||
test "validate this too" do
|
||||
conn = %{
|
||||
params: %{"actor" => "https://niu.moe/users/rye"},
|
||||
req_headers: [
|
||||
{"x-forwarded-for", "149.202.73.191"},
|
||||
{"host", "testing.pleroma.lol"},
|
||||
{"x-cluster-client-ip", "149.202.73.191"},
|
||||
{"connection", "upgrade"},
|
||||
{"content-length", "2342"},
|
||||
{"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://niu.moe/)"},
|
||||
{"date", "Sun, 18 Feb 2018 21:44:46 GMT"},
|
||||
{"digest", "SHA-256=vS8uDOJlyAu78cF3k5EzrvaU9iilHCX3chP37gs5sS8="},
|
||||
{"content-type", "application/activity+json"},
|
||||
{"signature",
|
||||
"keyId=\"https://niu.moe/users/rye#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"IN6fHD8pLiDEf35dOaRHzJKc1wBYh3/Yq0ItaNGxUSbJTd2xMjigZbcsVKzvgYYjglDDN+disGNeD+OBKwMqkXWaWe/lyMc9wHvCH5NMhpn/A7qGLY8yToSt4vh8ytSkZKO6B97yC+Nvy6Fz/yMbvKtFycIvSXCq417cMmY6f/aG+rtMUlTbKO5gXzC7SUgGJCtBPCh1xZzu5/w0pdqdjO46ePNeR6JyJSLLV4hfo3+p2n7SRraxM4ePVCUZqhwS9LPt3Zdhy3ut+IXCZgMVIZggQFM+zXLtcXY5HgFCsFQr5WQDu+YkhWciNWtKFnWfAsnsg5sC330lZ/0Z8Z91yA==\""},
|
||||
{"(request-target)", "post /inbox"}
|
||||
]
|
||||
}
|
||||
|
||||
assert HTTPSignatures.validate_conn(conn)
|
||||
end
|
||||
|
||||
test "it generates a signature" do
|
||||
user = insert(:user)
|
||||
assert HTTPSignatures.sign(user, %{host: "mastodon.example.org"}) =~ "keyId=\""
|
||||
end
|
||||
|
||||
test "this too" do
|
||||
conn = %{
|
||||
params: %{"actor" => "https://mst3k.interlinked.me/users/luciferMysticus"},
|
||||
req_headers: [
|
||||
{"host", "soc.canned-death.us"},
|
||||
{"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://mst3k.interlinked.me/)"},
|
||||
{"date", "Sun, 11 Mar 2018 12:19:36 GMT"},
|
||||
{"digest", "SHA-256=V7Hl6qDK2m8WzNsjzNYSBISi9VoIXLFlyjF/a5o1SOc="},
|
||||
{"content-type", "application/activity+json"},
|
||||
{"signature",
|
||||
"keyId=\"https://mst3k.interlinked.me/users/luciferMysticus#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15pCpAyx8IZp53s0Fn/y8MjCTzp+absw8kxx0k2sQAXYs2iy6xhdDUe7iGzz+XLAEqLyZIZfecynaU2nb3Z2XnFDjhGjR1vj/JP7wiXpwp6o1dpDZj+KT2vxHtXuB9585V+sOHLwSB1cGDbAgTy0jx/2az2EGIKK2zkw1KJuAZm0DDMSZalp/30P8dl3qz7DV2EHdDNfaVtrs5BfbDOZ7t1hCcASllzAzgVGFl0BsrkzBfRMeUMRucr111ZG+c0BNOEtJYOHSyZsSSdNknElggCJekONYMYk5ZA==\""},
|
||||
{"x-forwarded-for", "2607:5300:203:2899::31:1337"},
|
||||
{"x-forwarded-host", "soc.canned-death.us"},
|
||||
{"x-forwarded-server", "soc.canned-death.us"},
|
||||
{"connection", "Keep-Alive"},
|
||||
{"content-length", "2006"},
|
||||
{"(request-target)", "post /inbox"}
|
||||
]
|
||||
}
|
||||
|
||||
assert HTTPSignatures.validate_conn(conn)
|
||||
end
|
||||
end
|
|
@ -1,15 +0,0 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF
|
||||
NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F
|
||||
UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB
|
||||
AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA
|
||||
QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK
|
||||
kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg
|
||||
f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u
|
||||
412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc
|
||||
mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7
|
||||
kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA
|
||||
gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW
|
||||
G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
|
||||
7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA==
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -1,6 +0,0 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3
|
||||
6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6
|
||||
Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw
|
||||
oYi+1hqp1fIekaxsyQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
|
@ -81,6 +81,19 @@ test "the public timeline", %{conn: conn} do
|
|||
end)
|
||||
end
|
||||
|
||||
test "the public timeline when public is set to false", %{conn: conn} do
|
||||
public = Pleroma.Config.get([:instance, :public])
|
||||
Pleroma.Config.put([:instance, :public], false)
|
||||
|
||||
on_exit(fn ->
|
||||
Pleroma.Config.put([:instance, :public], public)
|
||||
end)
|
||||
|
||||
assert conn
|
||||
|> get("/api/v1/timelines/public", %{"local" => "False"})
|
||||
|> json_response(403) == %{"error" => "This resource requires authentication."}
|
||||
end
|
||||
|
||||
test "posting a status", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
|
@ -433,7 +446,7 @@ test "verify_credentials", %{conn: conn} do
|
|||
end
|
||||
|
||||
test "verify_credentials default scope unlisted", %{conn: conn} do
|
||||
user = insert(:user, %{info: %Pleroma.User.Info{default_scope: "unlisted"}})
|
||||
user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}})
|
||||
|
||||
conn =
|
||||
conn
|
||||
|
@ -1309,7 +1322,7 @@ test "returns the relationships for the current user", %{conn: conn} do
|
|||
|
||||
describe "locked accounts" do
|
||||
test "/api/v1/follow_requests works" do
|
||||
user = insert(:user, %{info: %Pleroma.User.Info{locked: true}})
|
||||
user = insert(:user, %{info: %User.Info{locked: true}})
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||
|
@ -1354,7 +1367,7 @@ test "/api/v1/follow_requests/:id/authorize works" do
|
|||
end
|
||||
|
||||
test "verify_credentials", %{conn: conn} do
|
||||
user = insert(:user, %{info: %Pleroma.User.Info{default_scope: "private"}})
|
||||
user = insert(:user, %{info: %User.Info{default_scope: "private"}})
|
||||
|
||||
conn =
|
||||
conn
|
||||
|
@ -1366,7 +1379,7 @@ test "verify_credentials", %{conn: conn} do
|
|||
end
|
||||
|
||||
test "/api/v1/follow_requests/:id/reject works" do
|
||||
user = insert(:user, %{info: %Pleroma.User.Info{locked: true}})
|
||||
user = insert(:user, %{info: %User.Info{locked: true}})
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||
|
@ -2116,7 +2129,7 @@ test "returns favorited DM only when user is logged in and he is one of recipien
|
|||
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|
||||
|> json_response(:ok)
|
||||
|
||||
assert length(anonymous_response) == 0
|
||||
assert Enum.empty?(anonymous_response)
|
||||
end
|
||||
|
||||
test "does not return others' favorited DM when user is not one of recipients", %{
|
||||
|
@ -2140,7 +2153,7 @@ test "does not return others' favorited DM when user is not one of recipients",
|
|||
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|
||||
|> json_response(:ok)
|
||||
|
||||
assert length(response) == 0
|
||||
assert Enum.empty?(response)
|
||||
end
|
||||
|
||||
test "paginates favorites using since_id and max_id", %{
|
||||
|
|
|
@ -355,7 +355,7 @@ test "tries to use the information in poco fields" do
|
|||
|
||||
{:ok, user} = OStatus.find_or_make_user(uri)
|
||||
|
||||
user = Pleroma.User.get_cached_by_id(user.id)
|
||||
user = User.get_cached_by_id(user.id)
|
||||
assert user.name == "Constance Variable"
|
||||
assert user.nickname == "lambadalambda@social.heldscal.la"
|
||||
assert user.local == false
|
||||
|
@ -374,7 +374,7 @@ test "find_or_make_user sets all the nessary input fields" do
|
|||
{:ok, user} = OStatus.find_or_make_user(uri)
|
||||
|
||||
assert user.info ==
|
||||
%Pleroma.User.Info{
|
||||
%User.Info{
|
||||
id: user.info.id,
|
||||
ap_enabled: false,
|
||||
background: %{},
|
||||
|
|
Loading…
Reference in a new issue