Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into update-validator

This commit is contained in:
lain 2020-06-23 10:17:12 +02:00
commit 2c603f2009
71 changed files with 1965 additions and 370 deletions

View file

@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed ### Changed
- **Breaking:** Elixir >=1.9 is now required (was >= 1.8) - **Breaking:** Elixir >=1.9 is now required (was >= 1.8)
- In Conversations, return only direct messages as `last_status` - In Conversations, return only direct messages as `last_status`
- Using the `only_media` filter on timelines will now exclude reblog media
- MFR policy to set global expiration for all local Create activities - MFR policy to set global expiration for all local Create activities
- OGP rich media parser merged with TwitterCard - OGP rich media parser merged with TwitterCard
<details> <details>
@ -19,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- **Breaking:** removed `with_move` parameter from notifications timeline. - **Breaking:** removed `with_move` parameter from notifications timeline.
### Added ### Added
- Chats: Added support for federated chats. For details, see the docs. - Chats: Added support for federated chats. For details, see the docs.
- ActivityPub: Added support for existing AP ids for instances migrated from Mastodon. - ActivityPub: Added support for existing AP ids for instances migrated from Mastodon.
- Instance: Add `background_image` to configuration and `/api/v1/instance` - Instance: Add `background_image` to configuration and `/api/v1/instance`
@ -34,12 +36,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Notifications: Added `follow_request` notification type. - Notifications: Added `follow_request` notification type.
- Added `:reject_deletes` group to SimplePolicy - Added `:reject_deletes` group to SimplePolicy
- MRF (`EmojiStealPolicy`): New MRF Policy which allows to automatically download emojis from remote instances - MRF (`EmojiStealPolicy`): New MRF Policy which allows to automatically download emojis from remote instances
- Support pagination in emoji packs API (for packs and for files in pack)
<details> <details>
<summary>API Changes</summary> <summary>API Changes</summary>
- Mastodon API: Extended `/api/v1/instance`. - Mastodon API: Extended `/api/v1/instance`.
- Mastodon API: Support for `include_types` in `/api/v1/notifications`. - Mastodon API: Support for `include_types` in `/api/v1/notifications`.
- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint. - Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
- Mastodon API: Add support for filtering replies in public and home timelines - Mastodon API: Add support for filtering replies in public and home timelines
- Mastodon API: Support for `bot` field in `/api/v1/accounts/update_credentials`
- Admin API: endpoints for create/update/delete OAuth Apps. - Admin API: endpoints for create/update/delete OAuth Apps.
- Admin API: endpoint for status view. - Admin API: endpoint for status view.
- OTP: Add command to reload emoji packs - OTP: Add command to reload emoji packs

View file

@ -34,6 +34,16 @@ Currently Pleroma is not packaged by any OS/Distros, but if you want to package
### Docker ### Docker
While we dont provide docker files, other people have written very good ones. Take a look at <https://github.com/angristan/docker-pleroma> or <https://glitch.sh/sn0w/pleroma-docker>. While we dont provide docker files, other people have written very good ones. Take a look at <https://github.com/angristan/docker-pleroma> or <https://glitch.sh/sn0w/pleroma-docker>.
### Compilation Troubleshooting
If you ever encounter compilation issues during the updating of Pleroma, you can try these commands and see if they fix things:
- `mix deps.clean --all`
- `mix local.rebar`
- `mix local.hex`
- `rm -r _build`
If you are not developing Pleroma, it is better to use the OTP release, which comes with everything precompiled.
## Documentation ## Documentation
- Latest Released revision: <https://docs.pleroma.social> - Latest Released revision: <https://docs.pleroma.social>
- Latest Git revision: <https://docs-develop.pleroma.social> - Latest Git revision: <https://docs-develop.pleroma.social>

View file

@ -186,6 +186,7 @@
notify_email: "noreply@example.com", notify_email: "noreply@example.com",
description: "Pleroma: An efficient and flexible fediverse server", description: "Pleroma: An efficient and flexible fediverse server",
background_image: "/images/city.jpg", background_image: "/images/city.jpg",
instance_thumbnail: "/instance/thumbnail.jpeg",
limit: 5_000, limit: 5_000,
chat_limit: 5_000, chat_limit: 5_000,
remote_limit: 100_000, remote_limit: 100_000,
@ -407,6 +408,13 @@
], ],
whitelist: [] whitelist: []
config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Http,
method: :purge,
headers: [],
options: []
config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Script, script_path: nil
config :pleroma, :chat, enabled: true config :pleroma, :chat, enabled: true
config :phoenix, :format_encoders, json: Jason config :phoenix, :format_encoders, json: Jason

View file

@ -1650,6 +1650,31 @@
"The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.", "The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.",
suggestions: ["https://example.com"] suggestions: ["https://example.com"]
}, },
%{
key: :invalidation,
type: :keyword,
descpiption: "",
suggestions: [
enabled: true,
provider: Pleroma.Web.MediaProxy.Invalidation.Script
],
children: [
%{
key: :enabled,
type: :boolean,
description: "Enables invalidate media cache"
},
%{
key: :provider,
type: :module,
description: "Module which will be used to cache purge.",
suggestions: [
Pleroma.Web.MediaProxy.Invalidation.Script,
Pleroma.Web.MediaProxy.Invalidation.Http
]
}
]
},
%{ %{
key: :proxy_opts, key: :proxy_opts,
type: :keyword, type: :keyword,
@ -1722,6 +1747,45 @@
} }
] ]
}, },
%{
group: :pleroma,
key: Pleroma.Web.MediaProxy.Invalidation.Http,
type: :group,
description: "HTTP invalidate settings",
children: [
%{
key: :method,
type: :atom,
description: "HTTP method of request. Default: :purge"
},
%{
key: :headers,
type: {:list, :tuple},
description: "HTTP headers of request.",
suggestions: [{"x-refresh", 1}]
},
%{
key: :options,
type: :keyword,
description: "Request options.",
suggestions: [params: %{ts: "xxx"}]
}
]
},
%{
group: :pleroma,
key: Pleroma.Web.MediaProxy.Invalidation.Script,
type: :group,
description: "Script invalidate settings",
children: [
%{
key: :script_path,
type: :string,
description: "Path to shell script. Which will run purge cache.",
suggestions: ["./installation/nginx-cache-purge.sh.example"]
}
]
},
%{ %{
group: :pleroma, group: :pleroma,
key: :gopher, key: :gopher,

View file

@ -488,35 +488,39 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
### Change the user's email, password, display and settings-related fields ### Change the user's email, password, display and settings-related fields
- Params: * Params:
- `email` * `email`
- `password` * `password`
- `name` * `name`
- `bio` * `bio`
- `avatar` * `avatar`
- `locked` * `locked`
- `no_rich_text` * `no_rich_text`
- `default_scope` * `default_scope`
- `banner` * `banner`
- `hide_follows` * `hide_follows`
- `hide_followers` * `hide_followers`
- `hide_followers_count` * `hide_followers_count`
- `hide_follows_count` * `hide_follows_count`
- `hide_favorites` * `hide_favorites`
- `allow_following_move` * `allow_following_move`
- `background` * `background`
- `show_role` * `show_role`
- `skip_thread_containment` * `skip_thread_containment`
- `fields` * `fields`
- `discoverable` * `discoverable`
- `actor_type` * `actor_type`
- Response: * Responses:
Status: 200
```json ```json
{"status": "success"} {"status": "success"}
``` ```
Status: 400
```json ```json
{"errors": {"errors":
{"actor_type": "is invalid"}, {"actor_type": "is invalid"},
@ -525,8 +529,10 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
} }
``` ```
Status: 404
```json ```json
{"error": "Unable to update user."} {"error": "Not found"}
``` ```
## `GET /api/pleroma/admin/reports` ## `GET /api/pleroma/admin/reports`
@ -1225,3 +1231,65 @@ Loads json generated from `config/descriptions.exs`.
- On success: `204`, empty response - On success: `204`, empty response
- On failure: - On failure:
- 400 Bad Request `"Invalid parameters"` when `status` is missing - 400 Bad Request `"Invalid parameters"` when `status` is missing
## `GET /api/pleroma/admin/media_proxy_caches`
### Get a list of all banned MediaProxy URLs in Cachex
- Authentication: required
- Params:
- *optional* `page`: **integer** page number
- *optional* `page_size`: **integer** number of log entries per page (default is `50`)
- Response:
``` json
{
"urls": [
"http://example.com/media/a688346.jpg",
"http://example.com/media/fb1f4d.jpg"
]
}
```
## `POST /api/pleroma/admin/media_proxy_caches/delete`
### Remove a banned MediaProxy URL from Cachex
- Authentication: required
- Params:
- `urls` (array)
- Response:
``` json
{
"urls": [
"http://example.com/media/a688346.jpg",
"http://example.com/media/fb1f4d.jpg"
]
}
```
## `POST /api/pleroma/admin/media_proxy_caches/purge`
### Purge a MediaProxy URL
- Authentication: required
- Params:
- `urls` (array)
- `ban` (boolean)
- Response:
``` json
{
"urls": [
"http://example.com/media/a688346.jpg",
"http://example.com/media/fb1f4d.jpg"
]
}
```

View file

@ -450,18 +450,44 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa
* Response: JSON, list with updated files for updated pack (hashmap -> shortcode => filename) with status 200, either error status with error message. * Response: JSON, list with updated files for updated pack (hashmap -> shortcode => filename) with status 200, either error status with error message.
## `GET /api/pleroma/emoji/packs` ## `GET /api/pleroma/emoji/packs`
### Lists local custom emoji packs ### Lists local custom emoji packs
* Method `GET` * Method `GET`
* Authentication: not required * Authentication: not required
* Params: None * Params:
* Response: JSON, "ok" and 200 status and the JSON hashmap of pack name to pack contents * `page`: page number for packs (default 1)
* `page_size`: page size for packs (default 50)
* Response: `packs` key with JSON hashmap of pack name to pack contents and `count` key for count of packs.
```json
{
"packs": {
"pack_name": {...}, // pack contents
...
},
"count": 0 // packs count
}
```
## `GET /api/pleroma/emoji/packs/:name` ## `GET /api/pleroma/emoji/packs/:name`
### Get pack.json for the pack ### Get pack.json for the pack
* Method `GET` * Method `GET`
* Authentication: not required * Authentication: not required
* Params: None * Params:
* Response: JSON, pack json with `files` and `pack` keys with 200 status or 404 if the pack does not exist * `page`: page number for files (default 1)
* `page_size`: page size for files (default 30)
* Response: JSON, pack json with `files`, `files_count` and `pack` keys with 200 status or 404 if the pack does not exist.
```json
{
"files": {...},
"files_count": 0, // emoji count in pack
"pack": {...}
}
```
## `GET /api/pleroma/emoji/packs/:name/archive` ## `GET /api/pleroma/emoji/packs/:name/archive`
### Requests a local pack archive from the instance ### Requests a local pack archive from the instance

View file

@ -60,7 +60,7 @@ To add configuration to your config file, you can copy it from the base config.
older software for theses nicknames. older software for theses nicknames.
* `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature. * `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature.
* `autofollowed_nicknames`: Set to nicknames of (local) users that every new user should automatically follow. * `autofollowed_nicknames`: Set to nicknames of (local) users that every new user should automatically follow.
* `no_attachment_links`: Set to true to disable automatically adding attachment link text to statuses. * `attachment_links`: Set to true to enable automatically adding attachment link text to statuses.
* `welcome_message`: A message that will be send to a newly registered users as a direct message. * `welcome_message`: A message that will be send to a newly registered users as a direct message.
* `welcome_user_nickname`: The nickname of the local user that sends the welcome message. * `welcome_user_nickname`: The nickname of the local user that sends the welcome message.
* `max_report_comment_size`: The maximum size of the report comment (Default: `1000`). * `max_report_comment_size`: The maximum size of the report comment (Default: `1000`).
@ -268,7 +268,7 @@ This section describe PWA manifest instance-specific values. Currently this opti
#### Pleroma.Web.MediaProxy.Invalidation.Script #### Pleroma.Web.MediaProxy.Invalidation.Script
This strategy allow perform external bash script to purge cache. This strategy allow perform external shell script to purge cache.
Urls of attachments pass to script as arguments. Urls of attachments pass to script as arguments.
* `script_path`: path to external script. * `script_path`: path to external script.
@ -284,8 +284,8 @@ config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Script,
This strategy allow perform custom http request to purge cache. This strategy allow perform custom http request to purge cache.
* `method`: http method. default is `purge` * `method`: http method. default is `purge`
* `headers`: http headers. default is empty * `headers`: http headers.
* `options`: request options. default is empty * `options`: request options.
Example: Example:
```elixir ```elixir

View file

@ -225,10 +225,7 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
#### Further reading #### Further reading
* [Backup your instance](../administration/backup.md) {! backend/installation/further_reading.include !}
* [Hardening your instance](../configuration/hardening.md)
* [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
* [Updating your instance](../administration/updating.md)
## Questions ## Questions

View file

@ -200,10 +200,7 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
#### Further reading #### Further reading
* [Backup your instance](../administration/backup.md) {! backend/installation/further_reading.include !}
* [Hardening your instance](../configuration/hardening.md)
* [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
* [Updating your instance](../administration/updating.md)
## Questions ## Questions

View file

@ -186,10 +186,7 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
#### Further reading #### Further reading
* [Backup your instance](../administration/backup.md) {! backend/installation/further_reading.include !}
* [Hardening your instance](../configuration/hardening.md)
* [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
* [Updating your instance](../administration/updating.md)
## Questions ## Questions

View file

@ -175,10 +175,7 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
#### その他の設定とカスタマイズ #### その他の設定とカスタマイズ
* [Backup your instance](../administration/backup.md) {! backend/installation/further_reading.include !}
* [Hardening your instance](../configuration/hardening.md)
* [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
* [Updating your instance](../administration/updating.md)
## 質問ある? ## 質問ある?

View file

@ -0,0 +1,5 @@
* [How Federation Works/Why is my Federated Timeline empty?](https://blog.soykaf.com/post/how-federation-works/)
* [Backup your instance](../administration/backup.md)
* [Updating your instance](../administration/updating.md)
* [Hardening your instance](../configuration/hardening.md)
* [How to activate mediaproxy](../configuration/howto_mediaproxy.md)

View file

@ -283,10 +283,7 @@ If you opted to allow sudo for the `pleroma` user but would like to remove the a
#### Further reading #### Further reading
* [Backup your instance](../administration/backup.md) {! backend/installation/further_reading.include !}
* [Hardening your instance](../configuration/hardening.md)
* [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
* [Updating your instance](../administration/updating.md)
## Questions ## Questions

View file

@ -196,3 +196,11 @@ incorrect timestamps. You should have ntpd running.
## Instances running NetBSD ## Instances running NetBSD
* <https://catgirl.science> * <https://catgirl.science>
#### Further reading
{! backend/installation/further_reading.include !}
## Questions
Questions about the installation or didnt it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.

View file

@ -242,3 +242,11 @@ If your instance is up and running, you can create your first user with administ
``` ```
LC_ALL=en_US.UTF-8 MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin LC_ALL=en_US.UTF-8 MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
``` ```
#### Further reading
{! backend/installation/further_reading.include !}
## Questions
Questions about the installation or didnt it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.

View file

@ -270,10 +270,7 @@ This will create an account withe the username of 'joeuser' with the email addre
## Further reading ## Further reading
* [Backup your instance](../administration/backup.md) {! backend/installation/further_reading.include !}
* [Hardening your instance](../configuration/hardening.md)
* [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
* [Updating your instance](../administration/updating.md)
## Questions ## Questions

View file

@ -13,7 +13,7 @@ CACHE_DIRECTORY="/tmp/pleroma-media-cache"
## $3 - (optional) the number of parallel processes to run for grep. ## $3 - (optional) the number of parallel processes to run for grep.
get_cache_files() { get_cache_files() {
local max_parallel=${3-16} local max_parallel=${3-16}
find $2 -maxdepth 2 -type d | xargs -P $max_parallel -n 1 grep -E Rl "^KEY:.*$1" | sort -u find $2 -maxdepth 2 -type d | xargs -P $max_parallel -n 1 grep -E -Rl "^KEY:.*$1" | sort -u
} }
## Removes an item from the given cache zone. ## Removes an item from the given cache zone.
@ -37,4 +37,4 @@ purge() {
} }
purge $1 purge $@

View file

@ -148,7 +148,8 @@ defp cachex_children do
build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500), build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500),
build_cachex("web_resp", limit: 2500), build_cachex("web_resp", limit: 2500),
build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10), build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10),
build_cachex("failed_proxy_url", limit: 2500) build_cachex("failed_proxy_url", limit: 2500),
build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000)
] ]
end end

View file

@ -1,6 +1,7 @@
defmodule Pleroma.Emoji.Pack do defmodule Pleroma.Emoji.Pack do
@derive {Jason.Encoder, only: [:files, :pack]} @derive {Jason.Encoder, only: [:files, :pack, :files_count]}
defstruct files: %{}, defstruct files: %{},
files_count: 0,
pack_file: nil, pack_file: nil,
path: nil, path: nil,
pack: %{}, pack: %{},
@ -8,6 +9,7 @@ defmodule Pleroma.Emoji.Pack do
@type t() :: %__MODULE__{ @type t() :: %__MODULE__{
files: %{String.t() => Path.t()}, files: %{String.t() => Path.t()},
files_count: non_neg_integer(),
pack_file: Path.t(), pack_file: Path.t(),
path: Path.t(), path: Path.t(),
pack: map(), pack: map(),
@ -16,7 +18,7 @@ defmodule Pleroma.Emoji.Pack do
alias Pleroma.Emoji alias Pleroma.Emoji
@spec create(String.t()) :: :ok | {:error, File.posix()} | {:error, :empty_values} @spec create(String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values}
def create(name) do def create(name) do
with :ok <- validate_not_empty([name]), with :ok <- validate_not_empty([name]),
dir <- Path.join(emoji_path(), name), dir <- Path.join(emoji_path(), name),
@ -26,10 +28,27 @@ def create(name) do
end end
end end
@spec show(String.t()) :: {:ok, t()} | {:error, atom()} defp paginate(entities, 1, page_size), do: Enum.take(entities, page_size)
def show(name) do
defp paginate(entities, page, page_size) do
entities
|> Enum.chunk_every(page_size)
|> Enum.at(page - 1)
end
@spec show(keyword()) :: {:ok, t()} | {:error, atom()}
def show(opts) do
name = opts[:name]
with :ok <- validate_not_empty([name]), with :ok <- validate_not_empty([name]),
{:ok, pack} <- load_pack(name) do {:ok, pack} <- load_pack(name) do
shortcodes =
pack.files
|> Map.keys()
|> paginate(opts[:page], opts[:page_size])
pack = Map.put(pack, :files, Map.take(pack.files, shortcodes))
{:ok, validate_pack(pack)} {:ok, validate_pack(pack)}
end end
end end
@ -120,10 +139,10 @@ def list_remote(url) do
end end
end end
@spec list_local() :: {:ok, map()} @spec list_local(keyword()) :: {:ok, map(), non_neg_integer()}
def list_local do def list_local(opts) do
with {:ok, results} <- list_packs_dir() do with {:ok, results} <- list_packs_dir() do
packs = all_packs =
results results
|> Enum.map(fn name -> |> Enum.map(fn name ->
case load_pack(name) do case load_pack(name) do
@ -132,9 +151,13 @@ def list_local do
end end
end) end)
|> Enum.reject(&is_nil/1) |> Enum.reject(&is_nil/1)
packs =
all_packs
|> paginate(opts[:page], opts[:page_size])
|> Map.new(fn pack -> {pack.name, validate_pack(pack)} end) |> Map.new(fn pack -> {pack.name, validate_pack(pack)} end)
{:ok, packs} {:ok, packs, length(all_packs)}
end end
end end
@ -146,7 +169,7 @@ def get_archive(name) do
end end
end end
@spec download(String.t(), String.t(), String.t()) :: :ok | {:error, atom()} @spec download(String.t(), String.t(), String.t()) :: {:ok, t()} | {:error, atom()}
def download(name, url, as) do def download(name, url, as) do
uri = url |> String.trim() |> URI.parse() uri = url |> String.trim() |> URI.parse()
@ -197,7 +220,12 @@ def load_pack(name) do
|> Map.put(:path, Path.dirname(pack_file)) |> Map.put(:path, Path.dirname(pack_file))
|> Map.put(:name, name) |> Map.put(:name, name)
{:ok, pack} files_count =
pack.files
|> Map.keys()
|> length()
{:ok, Map.put(pack, :files_count, files_count)}
else else
{:error, :not_found} {:error, :not_found}
end end
@ -296,7 +324,9 @@ defp downloadable?(pack) do
# Otherwise, they'd have to download it from external-src # Otherwise, they'd have to download it from external-src
pack.pack["share-files"] && pack.pack["share-files"] &&
Enum.all?(pack.files, fn {_, file} -> Enum.all?(pack.files, fn {_, file} ->
File.exists?(Path.join(pack.path, file)) pack.path
|> Path.join(file)
|> File.exists?()
end) end)
end end
@ -440,7 +470,7 @@ defp list_packs_dir do
# with the API so it should be sufficient # with the API so it should be sufficient
with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_path)}, with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_path)},
{:ls, {:ok, results}} <- {:ls, File.ls(emoji_path)} do {:ls, {:ok, results}} <- {:ls, File.ls(emoji_path)} do
{:ok, results} {:ok, Enum.sort(results)}
else else
{:create_dir, {:error, e}} -> {:error, :create_dir, e} {:create_dir, {:error, e}} -> {:error, :create_dir, e}
{:ls, {:error, e}} -> {:error, :ls, e} {:ls, {:error, e}} -> {:error, :ls, e}

View file

@ -64,6 +64,12 @@ def fetch_paginated(query, params, :offset, table_binding) do
@spec paginate(Ecto.Query.t(), map(), type(), atom() | nil) :: [Ecto.Schema.t()] @spec paginate(Ecto.Query.t(), map(), type(), atom() | nil) :: [Ecto.Schema.t()]
def paginate(query, options, method \\ :keyset, table_binding \\ nil) def paginate(query, options, method \\ :keyset, table_binding \\ nil)
def paginate(list, options, _method, _table_binding) when is_list(list) do
offset = options[:offset] || 0
limit = options[:limit] || 0
Enum.slice(list, offset, limit)
end
def paginate(query, options, :keyset, table_binding) do def paginate(query, options, :keyset, table_binding) do
query query
|> restrict(:min_id, options, table_binding) |> restrict(:min_id, options, table_binding)

View file

@ -10,6 +10,8 @@ defmodule Pleroma.Plugs.UploadedMedia do
import Pleroma.Web.Gettext import Pleroma.Web.Gettext
require Logger require Logger
alias Pleroma.Web.MediaProxy
@behaviour Plug @behaviour Plug
# no slashes # no slashes
@path "media" @path "media"
@ -35,8 +37,7 @@ def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do
%{query_params: %{"name" => name}} = conn -> %{query_params: %{"name" => name}} = conn ->
name = String.replace(name, "\"", "\\\"") name = String.replace(name, "\"", "\\\"")
conn put_resp_header(conn, "content-disposition", "filename=\"#{name}\"")
|> put_resp_header("content-disposition", "filename=\"#{name}\"")
conn -> conn ->
conn conn
@ -47,7 +48,8 @@ def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do
with uploader <- Keyword.fetch!(config, :uploader), with uploader <- Keyword.fetch!(config, :uploader),
proxy_remote = Keyword.get(config, :proxy_remote, false), proxy_remote = Keyword.get(config, :proxy_remote, false),
{:ok, get_method} <- uploader.get_file(file) do {:ok, get_method} <- uploader.get_file(file),
false <- media_is_banned(conn, get_method) do
get_media(conn, get_method, proxy_remote, opts) get_media(conn, get_method, proxy_remote, opts)
else else
_ -> _ ->
@ -59,6 +61,14 @@ def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do
def call(conn, _opts), do: conn def call(conn, _opts), do: conn
defp media_is_banned(%{request_path: path} = _conn, {:static_dir, _}) do
MediaProxy.in_banned_urls(Pleroma.Web.base_url() <> path)
end
defp media_is_banned(_, {:url, url}), do: MediaProxy.in_banned_urls(url)
defp media_is_banned(_, _), do: false
defp get_media(conn, {:static_dir, directory}, _, opts) do defp get_media(conn, {:static_dir, directory}, _, opts) do
static_opts = static_opts =
Map.get(opts, :static_plug_opts) Map.get(opts, :static_plug_opts)

View file

@ -263,37 +263,60 @@ def account_status(%User{deactivated: true}), do: :deactivated
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
def account_status(%User{confirmation_pending: true}) do def account_status(%User{confirmation_pending: true}) do
case Config.get([:instance, :account_activation_required]) do if Config.get([:instance, :account_activation_required]) do
true -> :confirmation_pending :confirmation_pending
_ -> :active else
:active
end end
end end
def account_status(%User{}), do: :active def account_status(%User{}), do: :active
@spec visible_for?(User.t(), User.t() | nil) :: boolean() @spec visible_for(User.t(), User.t() | nil) ::
def visible_for?(user, for_user \\ nil) :visible
| :invisible
| :restricted_unauthenticated
| :deactivated
| :confirmation_pending
def visible_for(user, for_user \\ nil)
def visible_for?(%User{invisible: true}, _), do: false def visible_for(%User{invisible: true}, _), do: :invisible
def visible_for?(%User{id: user_id}, %User{id: user_id}), do: true def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
def visible_for?(%User{local: local} = user, nil) do def visible_for(%User{} = user, nil) do
cfg_key = if restrict_unauthenticated?(user) do
if local, :restrict_unauthenticated
do: :local, else
else: :remote visible_account_status(user)
end
if Config.get([:restrict_unauthenticated, :profiles, cfg_key]),
do: false,
else: account_status(user) == :active
end end
def visible_for?(%User{} = user, for_user) do def visible_for(%User{} = user, for_user) do
account_status(user) == :active || superuser?(for_user) if superuser?(for_user) do
:visible
else
visible_account_status(user)
end
end end
def visible_for?(_, _), do: false def visible_for(_, _), do: :invisible
defp restrict_unauthenticated?(%User{local: local}) do
config_key = if local, do: :local, else: :remote
Config.get([:restrict_unauthenticated, :profiles, config_key], false)
end
defp visible_account_status(user) do
status = account_status(user)
if status in [:active, :password_reset_pending] do
:visible
else
status
end
end
@spec superuser?(User.t()) :: boolean() @spec superuser?(User.t()) :: boolean()
def superuser?(%User{local: true, is_admin: true}), do: true def superuser?(%User{local: true, is_admin: true}), do: true
@ -465,6 +488,7 @@ def update_changeset(struct, params \\ %{}) do
|> validate_format(:nickname, local_nickname_regex()) |> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: bio_limit) |> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: name_limit) |> validate_length(:name, min: 1, max: name_limit)
|> validate_inclusion(:actor_type, ["Person", "Service"])
|> put_fields() |> put_fields()
|> put_emoji() |> put_emoji()
|> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)}) |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
@ -758,7 +782,6 @@ def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
follower follower
|> update_following_count() |> update_following_count()
|> set_cache()
end end
end end
@ -787,7 +810,6 @@ defp do_unfollow(%User{} = follower, %User{} = followed) do
{:ok, follower} = {:ok, follower} =
follower follower
|> update_following_count() |> update_following_count()
|> set_cache()
{:ok, follower, followed} {:ok, follower, followed}
@ -1139,35 +1161,25 @@ defp follow_information_changeset(user, params) do
]) ])
end end
@spec update_follower_count(User.t()) :: {:ok, User.t()}
def update_follower_count(%User{} = user) do def update_follower_count(%User{} = user) do
if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
follower_count_query = follower_count = FollowingRelationship.follower_count(user)
User.Query.build(%{followers: user, deactivated: false})
|> select([u], %{count: count(u.id)})
User user
|> where(id: ^user.id) |> follow_information_changeset(%{follower_count: follower_count})
|> join(:inner, [u], s in subquery(follower_count_query)) |> update_and_set_cache
|> update([u, s],
set: [follower_count: s.count]
)
|> select([u], u)
|> Repo.update_all([])
|> case do
{1, [user]} -> set_cache(user)
_ -> {:error, user}
end
else else
{:ok, maybe_fetch_follow_information(user)} {:ok, maybe_fetch_follow_information(user)}
end end
end end
@spec update_following_count(User.t()) :: User.t() @spec update_following_count(User.t()) :: {:ok, User.t()}
def update_following_count(%User{local: false} = user) do def update_following_count(%User{local: false} = user) do
if Pleroma.Config.get([:instance, :external_user_synchronization]) do if Pleroma.Config.get([:instance, :external_user_synchronization]) do
maybe_fetch_follow_information(user) {:ok, maybe_fetch_follow_information(user)}
else else
user {:ok, user}
end end
end end
@ -1176,7 +1188,7 @@ def update_following_count(%User{local: true} = user) do
user user
|> follow_information_changeset(%{following_count: following_count}) |> follow_information_changeset(%{following_count: following_count})
|> Repo.update!() |> update_and_set_cache()
end end
def set_unread_conversation_count(%User{local: true} = user) do def set_unread_conversation_count(%User{local: true} = user) do

View file

@ -812,7 +812,8 @@ defp restrict_media(_query, %{only_media: _val, skip_preload: true}) do
defp restrict_media(query, %{only_media: true}) do defp restrict_media(query, %{only_media: true}) do
from( from(
[_activity, object] in query, [activity, object] in query,
where: fragment("(?)->>'type' = ?", activity.data, "Create"),
where: fragment("not (?)->'attachment' = (?)", object.data, ^[]) where: fragment("not (?)->'attachment' = (?)", object.data, ^[])
) )
end end

View file

@ -514,7 +514,6 @@ defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
{new_user, for_user} {new_user, for_user}
end end
# TODO: Add support for "object" field
@doc """ @doc """
Endpoint based on <https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload> Endpoint based on <https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload>
@ -525,6 +524,8 @@ defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
Response: Response:
- HTTP Code: 201 Created - HTTP Code: 201 Created
- HTTP Body: ActivityPub object to be inserted into another's `attachment` field - HTTP Body: ActivityPub object to be inserted into another's `attachment` field
Note: Will not point to a URL with a `Location` header because no standalone Activity has been created.
""" """
def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
with {:ok, object} <- with {:ok, object} <-

View file

@ -13,8 +13,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
defp delist_message(message, threshold) when threshold > 0 do defp delist_message(message, threshold) when threshold > 0 do
follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address
to = message["to"] || []
cc = message["cc"] || []
follower_collection? = Enum.member?(message["to"] ++ message["cc"], follower_collection) follower_collection? = Enum.member?(to ++ cc, follower_collection)
message = message =
case get_recipient_count(message) do case get_recipient_count(message) do
@ -71,7 +73,8 @@ defp get_recipient_count(message) do
end end
@impl true @impl true
def filter(%{"type" => "Create"} = message) do def filter(%{"type" => "Create", "object" => %{"type" => object_type}} = message)
when object_type in ~w{Note Article} do
reject_threshold = reject_threshold =
Pleroma.Config.get( Pleroma.Config.get(
[:mrf_hellthread, :reject_threshold], [:mrf_hellthread, :reject_threshold],

View file

@ -41,7 +41,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
field(:announcements, {:array, :string}, default: []) field(:announcements, {:array, :string}, default: [])
# see if needed # see if needed
field(:conversation, :string)
field(:context_id, :string) field(:context_id, :string)
end end

View file

@ -172,8 +172,8 @@ def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
object object
|> Map.put("inReplyTo", replied_object.data["id"]) |> Map.put("inReplyTo", replied_object.data["id"])
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id) |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
|> Map.put("conversation", replied_object.data["context"] || object["conversation"])
|> Map.put("context", replied_object.data["context"] || object["conversation"]) |> Map.put("context", replied_object.data["context"] || object["conversation"])
|> Map.drop(["conversation"])
else else
e -> e ->
Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}") Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
@ -207,7 +207,7 @@ def fix_context(object) do
object object
|> Map.put("context", context) |> Map.put("context", context)
|> Map.put("conversation", context) |> Map.drop(["conversation"])
end end
def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
@ -458,7 +458,7 @@ def handle_incoming(
to: data["to"], to: data["to"],
object: object, object: object,
actor: user, actor: user,
context: object["conversation"], context: object["context"],
local: false, local: false,
published: data["published"], published: data["published"],
additional: additional:

View file

@ -111,8 +111,7 @@ def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames})
action: "delete" action: "delete"
}) })
conn json(conn, nicknames)
|> json(nicknames)
end end
def user_follow(%{assigns: %{user: admin}} = conn, %{ def user_follow(%{assigns: %{user: admin}} = conn, %{
@ -131,8 +130,7 @@ def user_follow(%{assigns: %{user: admin}} = conn, %{
}) })
end end
conn json(conn, "ok")
|> json("ok")
end end
def user_unfollow(%{assigns: %{user: admin}} = conn, %{ def user_unfollow(%{assigns: %{user: admin}} = conn, %{
@ -151,8 +149,7 @@ def user_unfollow(%{assigns: %{user: admin}} = conn, %{
}) })
end end
conn json(conn, "ok")
|> json("ok")
end end
def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
@ -191,8 +188,7 @@ def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
action: "create" action: "create"
}) })
conn json(conn, res)
|> json(res)
{:error, id, changeset, _} -> {:error, id, changeset, _} ->
res = res =
@ -363,8 +359,8 @@ defp maybe_parse_filters(filters) do
filters filters
|> String.split(",") |> String.split(",")
|> Enum.filter(&Enum.member?(@filters, &1)) |> Enum.filter(&Enum.member?(@filters, &1))
|> Enum.map(&String.to_atom(&1)) |> Enum.map(&String.to_atom/1)
|> Enum.into(%{}, &{&1, true}) |> Map.new(&{&1, true})
end end
def right_add_multiple(%{assigns: %{user: admin}} = conn, %{ def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
@ -568,10 +564,10 @@ def update_user_credentials(
{:error, changeset} -> {:error, changeset} ->
errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end) errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
json(conn, %{errors: errors}) {:errors, errors}
_ -> _ ->
json(conn, %{error: "Unable to update user."}) {:error, :not_found}
end end
end end
@ -616,7 +612,7 @@ defp configurable_from_database do
def reload_emoji(conn, _params) do def reload_emoji(conn, _params) do
Pleroma.Emoji.reload() Pleroma.Emoji.reload()
conn |> json("ok") json(conn, "ok")
end end
def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
@ -630,7 +626,7 @@ def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}
action: "confirm_email" action: "confirm_email"
}) })
conn |> json("") json(conn, "")
end end
def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
@ -644,14 +640,13 @@ def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" =
action: "resend_confirmation_email" action: "resend_confirmation_email"
}) })
conn |> json("") json(conn, "")
end end
def stats(conn, _) do def stats(conn, _) do
count = Stats.get_status_visibility_count() count = Stats.get_status_visibility_count()
conn json(conn, %{"status_visibility" => count})
|> json(%{"status_visibility" => count})
end end
defp page_params(params) do defp page_params(params) do

View file

@ -17,6 +17,12 @@ def call(conn, {:error, reason}) do
|> json(%{error: reason}) |> json(%{error: reason})
end end
def call(conn, {:errors, errors}) do
conn
|> put_status(:bad_request)
|> json(%{errors: errors})
end
def call(conn, {:param_cast, _}) do def call(conn, {:param_cast, _}) do
conn conn
|> put_status(:bad_request) |> put_status(:bad_request)

View file

@ -0,0 +1,63 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do
use Pleroma.Web, :controller
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ApiSpec.Admin, as: Spec
alias Pleroma.Web.MediaProxy
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(
OAuthScopesPlug,
%{scopes: ["read:media_proxy_caches"], admin: true} when action in [:index]
)
plug(
OAuthScopesPlug,
%{scopes: ["write:media_proxy_caches"], admin: true} when action in [:purge, :delete]
)
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
defdelegate open_api_operation(action), to: Spec.MediaProxyCacheOperation
def index(%{assigns: %{user: _}} = conn, params) do
cursor =
:banned_urls_cache
|> :ets.table([{:traverse, {:select, Cachex.Query.create(true, :key)}}])
|> :qlc.cursor()
urls =
case params.page do
1 ->
:qlc.next_answers(cursor, params.page_size)
_ ->
:qlc.next_answers(cursor, (params.page - 1) * params.page_size)
:qlc.next_answers(cursor, params.page_size)
end
:qlc.delete_cursor(cursor)
render(conn, "index.json", urls: urls)
end
def delete(%{assigns: %{user: _}, body_params: %{urls: urls}} = conn, _) do
MediaProxy.remove_from_banned_urls(urls)
render(conn, "index.json", urls: urls)
end
def purge(%{assigns: %{user: _}, body_params: %{urls: urls, ban: ban}} = conn, _) do
MediaProxy.Invalidation.purge(urls)
if ban do
MediaProxy.put_in_banned_urls(urls)
end
render(conn, "index.json", urls: urls)
end
end

View file

@ -0,0 +1,11 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.MediaProxyCacheView do
use Pleroma.Web, :view
def render("index.json", %{urls: urls}) do
%{urls: urls}
end
end

View file

@ -39,6 +39,12 @@ def pagination_params do
:string, :string,
"Return the newest items newer than this ID" "Return the newest items newer than this ID"
), ),
Operation.parameter(
:offset,
:query,
%Schema{type: :integer, default: 0},
"Return items past this number of items"
),
Operation.parameter( Operation.parameter(
:limit, :limit,
:query, :query,

View file

@ -102,6 +102,7 @@ def show_operation do
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}], parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
responses: %{ responses: %{
200 => Operation.response("Account", "application/json", Account), 200 => Operation.response("Account", "application/json", Account),
401 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError) 404 => Operation.response("Error", "application/json", ApiError)
} }
} }
@ -142,6 +143,7 @@ def statuses_operation do
] ++ pagination_params(), ] ++ pagination_params(),
responses: %{ responses: %{
200 => Operation.response("Statuses", "application/json", array_of_statuses()), 200 => Operation.response("Statuses", "application/json", array_of_statuses()),
401 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError) 404 => Operation.response("Error", "application/json", ApiError)
} }
} }

View file

@ -0,0 +1,109 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.ApiError
import Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def index_operation do
%Operation{
tags: ["Admin", "MediaProxyCache"],
summary: "Fetch a paginated list of all banned MediaProxy URLs in Cachex",
operationId: "AdminAPI.MediaProxyCacheController.index",
security: [%{"oAuth" => ["read:media_proxy_caches"]}],
parameters: [
Operation.parameter(
:page,
:query,
%Schema{type: :integer, default: 1},
"Page"
),
Operation.parameter(
:page_size,
:query,
%Schema{type: :integer, default: 50},
"Number of statuses to return"
)
],
responses: %{
200 => success_response()
}
}
end
def delete_operation do
%Operation{
tags: ["Admin", "MediaProxyCache"],
summary: "Remove a banned MediaProxy URL from Cachex",
operationId: "AdminAPI.MediaProxyCacheController.delete",
security: [%{"oAuth" => ["write:media_proxy_caches"]}],
requestBody:
request_body(
"Parameters",
%Schema{
type: :object,
required: [:urls],
properties: %{
urls: %Schema{type: :array, items: %Schema{type: :string, format: :uri}}
}
},
required: true
),
responses: %{
200 => success_response(),
400 => Operation.response("Error", "application/json", ApiError)
}
}
end
def purge_operation do
%Operation{
tags: ["Admin", "MediaProxyCache"],
summary: "Purge and optionally ban a MediaProxy URL",
operationId: "AdminAPI.MediaProxyCacheController.purge",
security: [%{"oAuth" => ["write:media_proxy_caches"]}],
requestBody:
request_body(
"Parameters",
%Schema{
type: :object,
required: [:urls],
properties: %{
urls: %Schema{type: :array, items: %Schema{type: :string, format: :uri}},
ban: %Schema{type: :boolean, default: true}
}
},
required: true
),
responses: %{
200 => success_response(),
400 => Operation.response("Error", "application/json", ApiError)
}
}
end
defp success_response do
Operation.response("Array of banned MediaProxy URLs in Cachex", "application/json", %Schema{
type: :object,
properties: %{
urls: %Schema{
type: :array,
items: %Schema{
type: :string,
format: :uri,
description: "MediaProxy URLs"
}
}
}
})
end
end

View file

@ -163,6 +163,13 @@ def notification do
description: description:
"Status that was the object of the notification, e.g. in mentions, reblogs, favourites, or polls.", "Status that was the object of the notification, e.g. in mentions, reblogs, favourites, or polls.",
nullable: true nullable: true
},
pleroma: %Schema{
type: :object,
properties: %{
is_seen: %Schema{type: :boolean},
is_muted: %Schema{type: :boolean}
}
} }
}, },
example: %{ example: %{
@ -170,7 +177,8 @@ def notification do
"type" => "mention", "type" => "mention",
"created_at" => "2019-11-23T07:49:02.064Z", "created_at" => "2019-11-23T07:49:02.064Z",
"account" => Account.schema().example, "account" => Account.schema().example,
"status" => Status.schema().example "status" => Status.schema().example,
"pleroma" => %{"is_seen" => false, "is_muted" => false}
} }
} }
end end

View file

@ -33,6 +33,20 @@ def index_operation do
tags: ["Emoji Packs"], tags: ["Emoji Packs"],
summary: "Lists local custom emoji packs", summary: "Lists local custom emoji packs",
operationId: "PleromaAPI.EmojiPackController.index", operationId: "PleromaAPI.EmojiPackController.index",
parameters: [
Operation.parameter(
:page,
:query,
%Schema{type: :integer, default: 1},
"Page"
),
Operation.parameter(
:page_size,
:query,
%Schema{type: :integer, default: 50},
"Number of emoji packs to return"
)
],
responses: %{ responses: %{
200 => emoji_packs_response() 200 => emoji_packs_response()
} }
@ -44,7 +58,21 @@ def show_operation do
tags: ["Emoji Packs"], tags: ["Emoji Packs"],
summary: "Show emoji pack", summary: "Show emoji pack",
operationId: "PleromaAPI.EmojiPackController.show", operationId: "PleromaAPI.EmojiPackController.show",
parameters: [name_param()], parameters: [
name_param(),
Operation.parameter(
:page,
:query,
%Schema{type: :integer, default: 1},
"Page"
),
Operation.parameter(
:page_size,
:query,
%Schema{type: :integer, default: 30},
"Number of emoji to return"
)
],
responses: %{ responses: %{
200 => Operation.response("Emoji Pack", "application/json", emoji_pack()), 200 => Operation.response("Emoji Pack", "application/json", emoji_pack()),
400 => Operation.response("Bad Request", "application/json", ApiError), 400 => Operation.response("Bad Request", "application/json", ApiError),

View file

@ -49,7 +49,7 @@ def manifest(conn, _params) do
|> render("manifest.json") |> render("manifest.json")
end end
@doc "PUT /api/web/settings" @doc "PUT /api/web/settings: Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere"
def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
with {:ok, _} <- User.mastodon_settings_update(user, settings) do with {:ok, _} <- User.mastodon_settings_update(user, settings) do
json(conn, %{}) json(conn, %{})

View file

@ -179,6 +179,9 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
|> Maps.put_if_present(:pleroma_settings_store, params[:pleroma_settings_store]) |> Maps.put_if_present(:pleroma_settings_store, params[:pleroma_settings_store])
|> Maps.put_if_present(:default_scope, params[:default_scope]) |> Maps.put_if_present(:default_scope, params[:default_scope])
|> Maps.put_if_present(:default_scope, params["source"]["privacy"]) |> Maps.put_if_present(:default_scope, params["source"]["privacy"])
|> Maps.put_if_present(:actor_type, params[:bot], fn bot ->
if bot, do: {:ok, "Service"}, else: {:ok, "Person"}
end)
|> Maps.put_if_present(:actor_type, params[:actor_type]) |> Maps.put_if_present(:actor_type, params[:actor_type])
# What happens here: # What happens here:
@ -238,17 +241,17 @@ def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, [])
@doc "GET /api/v1/accounts/:id" @doc "GET /api/v1/accounts/:id"
def show(%{assigns: %{user: for_user}} = conn, %{id: nickname_or_id}) do def show(%{assigns: %{user: for_user}} = conn, %{id: nickname_or_id}) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user), with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user),
true <- User.visible_for?(user, for_user) do :visible <- User.visible_for(user, for_user) do
render(conn, "show.json", user: user, for: for_user) render(conn, "show.json", user: user, for: for_user)
else else
_e -> render_error(conn, :not_found, "Can't find user") error -> user_visibility_error(conn, error)
end end
end end
@doc "GET /api/v1/accounts/:id/statuses" @doc "GET /api/v1/accounts/:id/statuses"
def statuses(%{assigns: %{user: reading_user}} = conn, params) do def statuses(%{assigns: %{user: reading_user}} = conn, params) do
with %User{} = user <- User.get_cached_by_nickname_or_id(params.id, for: reading_user), with %User{} = user <- User.get_cached_by_nickname_or_id(params.id, for: reading_user),
true <- User.visible_for?(user, reading_user) do :visible <- User.visible_for(user, reading_user) do
params = params =
params params
|> Map.delete(:tagged) |> Map.delete(:tagged)
@ -265,7 +268,17 @@ def statuses(%{assigns: %{user: reading_user}} = conn, params) do
as: :activity as: :activity
) )
else else
_e -> render_error(conn, :not_found, "Can't find user") error -> user_visibility_error(conn, error)
end
end
defp user_visibility_error(conn, error) do
case error do
:restrict_unauthenticated ->
render_error(conn, :unauthorized, "This API requires an authenticated user")
_ ->
render_error(conn, :not_found, "Can't find user")
end end
end end

View file

@ -107,21 +107,21 @@ defp resource_search(_, "statuses", query, options) do
) )
end end
defp resource_search(:v2, "hashtags", query, _options) do defp resource_search(:v2, "hashtags", query, options) do
tags_path = Web.base_url() <> "/tag/" tags_path = Web.base_url() <> "/tag/"
query query
|> prepare_tags() |> prepare_tags(options)
|> Enum.map(fn tag -> |> Enum.map(fn tag ->
%{name: tag, url: tags_path <> tag} %{name: tag, url: tags_path <> tag}
end) end)
end end
defp resource_search(:v1, "hashtags", query, _options) do defp resource_search(:v1, "hashtags", query, options) do
prepare_tags(query) prepare_tags(query, options)
end end
defp prepare_tags(query, add_joined_tag \\ true) do defp prepare_tags(query, options) do
tags = tags =
query query
|> preprocess_uri_query() |> preprocess_uri_query()
@ -139,13 +139,20 @@ defp prepare_tags(query, add_joined_tag \\ true) do
tags = Enum.map(tags, fn tag -> String.trim_leading(tag, "#") end) tags = Enum.map(tags, fn tag -> String.trim_leading(tag, "#") end)
if Enum.empty?(explicit_tags) && add_joined_tag do tags =
tags if Enum.empty?(explicit_tags) && !options[:skip_joined_tag] do
|> Kernel.++([joined_tag(tags)]) add_joined_tag(tags)
|> Enum.uniq_by(&String.downcase/1)
else else
tags tags
end end
Pleroma.Pagination.paginate(tags, options)
end
defp add_joined_tag(tags) do
tags
|> Kernel.++([joined_tag(tags)])
|> Enum.uniq_by(&String.downcase/1)
end end
# If `query` is a URI, returns last component of its path, otherwise returns `query` # If `query` is a URI, returns last component of its path, otherwise returns `query`

View file

@ -35,7 +35,7 @@ def render("index.json", %{users: users} = opts) do
end end
def render("show.json", %{user: user} = opts) do def render("show.json", %{user: user} = opts) do
if User.visible_for?(user, opts[:for]) do if User.visible_for(user, opts[:for]) == :visible do
do_render("show.json", opts) do_render("show.json", opts)
else else
%{} %{}
@ -179,7 +179,7 @@ defp do_render("show.json", %{user: user} = opts) do
0 0
end end
bot = user.actor_type in ["Application", "Service"] bot = user.actor_type == "Service"
emojis = emojis =
Enum.map(user.emoji, fn {shortcode, raw_url} -> Enum.map(user.emoji, fn {shortcode, raw_url} ->

View file

@ -23,7 +23,7 @@ def render("show.json", _) do
streaming_api: Pleroma.Web.Endpoint.websocket_url() streaming_api: Pleroma.Web.Endpoint.websocket_url()
}, },
stats: Pleroma.Stats.get_stats(), stats: Pleroma.Stats.get_stats(),
thumbnail: instance_thumbnail(), thumbnail: Keyword.get(instance, :instance_thumbnail),
languages: ["en"], languages: ["en"],
registrations: Keyword.get(instance, :registrations_open), registrations: Keyword.get(instance, :registrations_open),
# Extra (not present in Mastodon): # Extra (not present in Mastodon):
@ -88,9 +88,4 @@ def federation do
end end
|> Map.put(:enabled, Config.get([:instance, :federating])) |> Map.put(:enabled, Config.get([:instance, :federating]))
end end
defp instance_thumbnail do
Pleroma.Config.get([:instance, :instance_thumbnail]) ||
"#{Pleroma.Web.base_url()}/instance/thumbnail.jpeg"
end
end end

View file

@ -84,12 +84,7 @@ def render(
# Note: :relationships contain user mutes (needed for :muted flag in :status) # Note: :relationships contain user mutes (needed for :muted flag in :status)
status_render_opts = %{relationships: opts[:relationships]} status_render_opts = %{relationships: opts[:relationships]}
account = AccountView.render("show.json", %{user: actor, for: reading_user})
account =
AccountView.render(
"show.json",
%{user: actor, for: reading_user}
)
response = %{ response = %{
id: to_string(notification.id), id: to_string(notification.id),
@ -97,6 +92,7 @@ def render(
created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at), created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
account: account, account: account,
pleroma: %{ pleroma: %{
is_muted: User.mutes?(reading_user, actor),
is_seen: notification.seen is_seen: notification.seen
} }
} }

View file

@ -5,22 +5,34 @@
defmodule Pleroma.Web.MediaProxy.Invalidation do defmodule Pleroma.Web.MediaProxy.Invalidation do
@moduledoc false @moduledoc false
@callback purge(list(String.t()), map()) :: {:ok, String.t()} | {:error, String.t()} @callback purge(list(String.t()), Keyword.t()) :: {:ok, list(String.t())} | {:error, String.t()}
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Web.MediaProxy
@spec purge(list(String.t())) :: {:ok, String.t()} | {:error, String.t()} @spec enabled?() :: boolean()
def enabled?, do: Config.get([:media_proxy, :invalidation, :enabled])
@spec purge(list(String.t()) | String.t()) :: {:ok, list(String.t())} | {:error, String.t()}
def purge(urls) do def purge(urls) do
[:media_proxy, :invalidation, :enabled] prepared_urls = prepare_urls(urls)
|> Config.get()
|> do_purge(urls) if enabled?() do
do_purge(prepared_urls)
else
{:ok, prepared_urls}
end
end end
defp do_purge(true, urls) do defp do_purge(urls) do
provider = Config.get([:media_proxy, :invalidation, :provider]) provider = Config.get([:media_proxy, :invalidation, :provider])
options = Config.get(provider) options = Config.get(provider)
provider.purge(urls, options) provider.purge(urls, options)
end end
defp do_purge(_, _), do: :ok def prepare_urls(urls) do
urls
|> List.wrap()
|> Enum.map(&MediaProxy.url/1)
end
end end

View file

@ -9,10 +9,10 @@ defmodule Pleroma.Web.MediaProxy.Invalidation.Http do
require Logger require Logger
@impl Pleroma.Web.MediaProxy.Invalidation @impl Pleroma.Web.MediaProxy.Invalidation
def purge(urls, opts) do def purge(urls, opts \\ []) do
method = Map.get(opts, :method, :purge) method = Keyword.get(opts, :method, :purge)
headers = Map.get(opts, :headers, []) headers = Keyword.get(opts, :headers, [])
options = Map.get(opts, :options, []) options = Keyword.get(opts, :options, [])
Logger.debug("Running cache purge: #{inspect(urls)}") Logger.debug("Running cache purge: #{inspect(urls)}")
@ -22,7 +22,7 @@ def purge(urls, opts) do
end end
end) end)
{:ok, "success"} {:ok, urls}
end end
defp do_purge(method, url, headers, options) do defp do_purge(method, url, headers, options) do

View file

@ -10,32 +10,34 @@ defmodule Pleroma.Web.MediaProxy.Invalidation.Script do
require Logger require Logger
@impl Pleroma.Web.MediaProxy.Invalidation @impl Pleroma.Web.MediaProxy.Invalidation
def purge(urls, %{script_path: script_path} = _options) do def purge(urls, opts \\ []) do
args = args =
urls urls
|> List.wrap() |> List.wrap()
|> Enum.uniq() |> Enum.uniq()
|> Enum.join(" ") |> Enum.join(" ")
opts
|> Keyword.get(:script_path)
|> do_purge([args])
|> handle_result(urls)
end
defp do_purge(script_path, args) when is_binary(script_path) do
path = Path.expand(script_path) path = Path.expand(script_path)
Logger.debug("Running cache purge: #{inspect(args)}, #{inspect(path)}")
Logger.debug("Running cache purge: #{inspect(urls)}, #{path}")
case do_purge(path, [args]) do
{result, exit_status} when exit_status > 0 ->
Logger.error("Error while cache purge: #{inspect(result)}")
{:error, inspect(result)}
_ ->
{:ok, "success"}
end
end
def purge(_, _), do: {:error, "not found script path"}
defp do_purge(path, args) do
System.cmd(path, args) System.cmd(path, args)
rescue rescue
error -> {inspect(error), 1} error -> error
end
defp do_purge(_, _), do: {:error, "not found script path"}
defp handle_result({_result, 0}, urls), do: {:ok, urls}
defp handle_result({:error, error}, urls), do: handle_result(error, urls)
defp handle_result(error, _) do
Logger.error("Error while cache purge: #{inspect(error)}")
{:error, inspect(error)}
end end
end end

View file

@ -6,20 +6,53 @@ defmodule Pleroma.Web.MediaProxy do
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Upload alias Pleroma.Upload
alias Pleroma.Web alias Pleroma.Web
alias Pleroma.Web.MediaProxy.Invalidation
@base64_opts [padding: false] @base64_opts [padding: false]
@spec in_banned_urls(String.t()) :: boolean()
def in_banned_urls(url), do: elem(Cachex.exists?(:banned_urls_cache, url(url)), 1)
def remove_from_banned_urls(urls) when is_list(urls) do
Cachex.execute!(:banned_urls_cache, fn cache ->
Enum.each(Invalidation.prepare_urls(urls), &Cachex.del(cache, &1))
end)
end
def remove_from_banned_urls(url) when is_binary(url) do
Cachex.del(:banned_urls_cache, url(url))
end
def put_in_banned_urls(urls) when is_list(urls) do
Cachex.execute!(:banned_urls_cache, fn cache ->
Enum.each(Invalidation.prepare_urls(urls), &Cachex.put(cache, &1, true))
end)
end
def put_in_banned_urls(url) when is_binary(url) do
Cachex.put(:banned_urls_cache, url(url), true)
end
def url(url) when is_nil(url) or url == "", do: nil def url(url) when is_nil(url) or url == "", do: nil
def url("/" <> _ = url), do: url def url("/" <> _ = url), do: url
def url(url) do def url(url) do
if disabled?() or local?(url) or whitelisted?(url) do if disabled?() or not url_proxiable?(url) do
url url
else else
encode_url(url) encode_url(url)
end end
end end
@spec url_proxiable?(String.t()) :: boolean()
def url_proxiable?(url) do
if local?(url) or whitelisted?(url) do
false
else
true
end
end
defp disabled?, do: !Config.get([:media_proxy, :enabled], false) defp disabled?, do: !Config.get([:media_proxy, :enabled], false)
defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url()) defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url())

View file

@ -14,10 +14,11 @@ def remote(conn, %{"sig" => sig64, "url" => url64} = params) do
with config <- Pleroma.Config.get([:media_proxy], []), with config <- Pleroma.Config.get([:media_proxy], []),
true <- Keyword.get(config, :enabled, false), true <- Keyword.get(config, :enabled, false),
{:ok, url} <- MediaProxy.decode_url(sig64, url64), {:ok, url} <- MediaProxy.decode_url(sig64, url64),
{_, false} <- {:in_banned_urls, MediaProxy.in_banned_urls(url)},
:ok <- filename_matches(params, conn.request_path, url) do :ok <- filename_matches(params, conn.request_path, url) do
ReverseProxy.call(conn, url, Keyword.get(config, :proxy_opts, @default_proxy_opts)) ReverseProxy.call(conn, url, Keyword.get(config, :proxy_opts, @default_proxy_opts))
else else
false -> error when error in [false, {:in_banned_urls, true}] ->
send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404)) send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404))
{:error, :invalid_signature} -> {:error, :invalid_signature} ->

View file

@ -37,14 +37,14 @@ def remote(conn, %{url: url}) do
end end
end end
def index(conn, _params) do def index(conn, params) do
emoji_path = emoji_path =
[:instance, :static_dir] [:instance, :static_dir]
|> Pleroma.Config.get!() |> Pleroma.Config.get!()
|> Path.join("emoji") |> Path.join("emoji")
with {:ok, packs} <- Pack.list_local() do with {:ok, packs, count} <- Pack.list_local(page: params.page, page_size: params.page_size) do
json(conn, packs) json(conn, %{packs: packs, count: count})
else else
{:error, :create_dir, e} -> {:error, :create_dir, e} ->
conn conn
@ -60,10 +60,10 @@ def index(conn, _params) do
end end
end end
def show(conn, %{name: name}) do def show(conn, %{name: name, page: page, page_size: page_size}) do
name = String.trim(name) name = String.trim(name)
with {:ok, pack} <- Pack.show(name) do with {:ok, pack} <- Pack.show(name: name, page: page, page_size: page_size) do
json(conn, pack) json(conn, pack)
else else
{:error, :not_found} -> {:error, :not_found} ->

View file

@ -209,6 +209,10 @@ defmodule Pleroma.Web.Router do
post("/oauth_app", OAuthAppController, :create) post("/oauth_app", OAuthAppController, :create)
patch("/oauth_app/:id", OAuthAppController, :update) patch("/oauth_app/:id", OAuthAppController, :update)
delete("/oauth_app/:id", OAuthAppController, :delete) delete("/oauth_app/:id", OAuthAppController, :delete)
get("/media_proxy_caches", MediaProxyCacheController, :index)
post("/media_proxy_caches/delete", MediaProxyCacheController, :delete)
post("/media_proxy_caches/purge", MediaProxyCacheController, :purge)
end end
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
@ -463,6 +467,7 @@ defmodule Pleroma.Web.Router do
scope "/api/web", Pleroma.Web do scope "/api/web", Pleroma.Web do
pipe_through(:authenticated_api) pipe_through(:authenticated_api)
# Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere
put("/settings", MastoFEController, :put_settings) put("/settings", MastoFEController, :put_settings)
end end

View file

@ -18,13 +18,19 @@ def perform(
}, },
_job _job
) do ) do
hrefs = attachments
Enum.flat_map(attachments, fn attachment -> |> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end)
Enum.map(attachment["url"], & &1["href"]) |> fetch_objects
end) |> prepare_objects(actor, Enum.map(attachments, & &1["name"]))
|> filter_objects
|> do_clean
names = Enum.map(attachments, & &1["name"]) {:ok, :success}
end
def perform(%{"op" => "cleanup_attachments", "object" => _object}, _job), do: {:ok, :skip}
defp do_clean({object_ids, attachment_urls}) do
uploader = Pleroma.Config.get([Pleroma.Upload, :uploader]) uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
prefix = prefix =
@ -39,22 +45,35 @@ def perform(
"/" "/"
) )
# find all objects for copies of the attachments, name and actor doesn't matter here Enum.each(attachment_urls, fn href ->
object_ids_and_hrefs = href
from(o in Object, |> String.trim_leading("#{base_url}/#{prefix}")
where: |> uploader.delete_file()
fragment( end)
"to_jsonb(array(select jsonb_array_elements((?)#>'{url}') ->> 'href' where jsonb_typeof((?)#>'{url}') = 'array'))::jsonb \\?| (?)",
o.data, delete_objects(object_ids)
o.data, end
^hrefs
) defp delete_objects([_ | _] = object_ids) do
) Repo.delete_all(from(o in Object, where: o.id in ^object_ids))
# The query above can be time consumptive on large instances until we end
# refactor how uploads are stored
|> Repo.all(timeout: :infinity) defp delete_objects(_), do: :ok
# we should delete 1 object for any given attachment, but don't delete # we should delete 1 object for any given attachment, but don't delete
# files if there are more than 1 object for it # files if there are more than 1 object for it
defp filter_objects(objects) do
Enum.reduce(objects, {[], []}, fn {href, %{id: id, count: count}}, {ids, hrefs} ->
with 1 <- count do
{ids ++ [id], hrefs ++ [href]}
else
_ -> {ids ++ [id], hrefs}
end
end)
end
defp prepare_objects(objects, actor, names) do
objects
|> Enum.reduce(%{}, fn %{ |> Enum.reduce(%{}, fn %{
id: id, id: id,
data: %{ data: %{
@ -76,31 +95,20 @@ def perform(
end end
end) end)
end) end)
|> Enum.map(fn {href, %{id: id, count: count}} ->
# only delete files that have single instance
with 1 <- count do
href
|> String.trim_leading("#{base_url}/#{prefix}")
|> uploader.delete_file()
{id, href}
else
_ -> {id, nil}
end
end)
object_ids = Enum.map(object_ids_and_hrefs, fn {id, _} -> id end)
from(o in Object, where: o.id in ^object_ids)
|> Repo.delete_all()
object_ids_and_hrefs
|> Enum.filter(fn {_, href} -> not is_nil(href) end)
|> Enum.map(&elem(&1, 1))
|> Pleroma.Web.MediaProxy.Invalidation.purge()
{:ok, :success}
end end
def perform(%{"op" => "cleanup_attachments", "object" => _object}, _job), do: {:ok, :skip} defp fetch_objects(hrefs) do
from(o in Object,
where:
fragment(
"to_jsonb(array(select jsonb_array_elements((?)#>'{url}') ->> 'href' where jsonb_typeof((?)#>'{url}') = 'array'))::jsonb \\?| (?)",
o.data,
o.data,
^hrefs
)
)
# The query above can be time consumptive on large instances until we
# refactor how uploads are stored
|> Repo.all(timeout: :infinity)
end
end end

15
mix.exs
View file

@ -230,14 +230,17 @@ defp aliases do
defp version(version) do defp version(version) do
identifier_filter = ~r/[^0-9a-z\-]+/i identifier_filter = ~r/[^0-9a-z\-]+/i
# Pre-release version, denoted from patch version with a hyphen {_cmdgit, cmdgit_err} = System.cmd("sh", ["-c", "command -v git"])
git_pre_release =
if cmdgit_err == 0 do
{tag, tag_err} = {tag, tag_err} =
System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true) System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true)
{describe, describe_err} = System.cmd("git", ["describe", "--tags", "--abbrev=8"]) {describe, describe_err} = System.cmd("git", ["describe", "--tags", "--abbrev=8"])
{commit_hash, commit_hash_err} = System.cmd("git", ["rev-parse", "--short", "HEAD"]) {commit_hash, commit_hash_err} = System.cmd("git", ["rev-parse", "--short", "HEAD"])
git_pre_release = # Pre-release version, denoted from patch version with a hyphen
cond do cond do
tag_err == 0 and describe_err == 0 -> tag_err == 0 and describe_err == 0 ->
describe describe
@ -250,12 +253,14 @@ defp version(version) do
"0-g" <> String.trim(commit_hash) "0-g" <> String.trim(commit_hash)
true -> true ->
"" nil
end
end end
# Branch name as pre-release version component, denoted with a dot # Branch name as pre-release version component, denoted with a dot
branch_name = branch_name =
with {branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]), with 0 <- cmdgit_err,
{branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]),
branch_name <- String.trim(branch_name), branch_name <- String.trim(branch_name),
branch_name <- System.get_env("PLEROMA_BUILD_BRANCH") || branch_name, branch_name <- System.get_env("PLEROMA_BUILD_BRANCH") || branch_name,
true <- true <-
@ -269,7 +274,7 @@ defp version(version) do
branch_name branch_name
else else
_ -> "stable" _ -> ""
end end
build_name = build_name =

View file

@ -0,0 +1,580 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-06-19 14:33+0000\n"
"PO-Revision-Date: 2020-06-19 20:38+0000\n"
"Last-Translator: Ben Is <srsbzns@cock.li>\n"
"Language-Team: Italian <https://translate.pleroma.social/projects/pleroma/"
"pleroma/it/>\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.0.4\n"
## This file is a PO Template file.
##
## `msgid`s here are often extracted from source code.
## Add new translations manually only if they're dynamic
## translations that can't be statically extracted.
##
## Run `mix gettext.extract` to bring this file up to
## date. Leave `msgstr`s empty as changing them here as no
## effect: edit them in PO (`.po`) files instead.
## From Ecto.Changeset.cast/4
msgid "can't be blank"
msgstr "non può essere nullo"
## From Ecto.Changeset.unique_constraint/3
msgid "has already been taken"
msgstr ""
## From Ecto.Changeset.put_change/3
msgid "is invalid"
msgstr ""
## From Ecto.Changeset.validate_format/3
msgid "has invalid format"
msgstr ""
## From Ecto.Changeset.validate_subset/3
msgid "has an invalid entry"
msgstr ""
## From Ecto.Changeset.validate_exclusion/3
msgid "is reserved"
msgstr ""
## From Ecto.Changeset.validate_confirmation/3
msgid "does not match confirmation"
msgstr ""
## From Ecto.Changeset.no_assoc_constraint/3
msgid "is still associated with this entry"
msgstr ""
msgid "are still associated with this entry"
msgstr ""
## From Ecto.Changeset.validate_length/3
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
## From Ecto.Changeset.validate_number/3
msgid "must be less than %{number}"
msgstr ""
msgid "must be greater than %{number}"
msgstr ""
msgid "must be less than or equal to %{number}"
msgstr ""
msgid "must be greater than or equal to %{number}"
msgstr ""
msgid "must be equal to %{number}"
msgstr ""
#: lib/pleroma/web/common_api/common_api.ex:421
#, elixir-format
msgid "Account not found"
msgstr ""
#: lib/pleroma/web/common_api/common_api.ex:249
#, elixir-format
msgid "Already voted"
msgstr ""
#: lib/pleroma/web/oauth/oauth_controller.ex:360
#, elixir-format
msgid "Bad request"
msgstr ""
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425
#, elixir-format
msgid "Can't delete object"
msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:196
#, elixir-format
msgid "Can't delete this post"
msgstr ""
#: lib/pleroma/web/controller_helper.ex:95
#: lib/pleroma/web/controller_helper.ex:101
#, elixir-format
msgid "Can't display this activity"
msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:227
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:254
#, elixir-format
msgid "Can't find user"
msgstr ""
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:114
#, elixir-format
msgid "Can't get favorites"
msgstr ""
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:437
#, elixir-format
msgid "Can't like object"
msgstr ""
#: lib/pleroma/web/common_api/utils.ex:556
#, elixir-format
msgid "Cannot post an empty status without attachments"
msgstr ""
#: lib/pleroma/web/common_api/utils.ex:504
#, elixir-format
msgid "Comment must be up to %{max_size} characters"
msgstr ""
#: lib/pleroma/config/config_db.ex:222
#, elixir-format
msgid "Config with params %{params} not found"
msgstr ""
#: lib/pleroma/web/common_api/common_api.ex:95
#, elixir-format
msgid "Could not delete"
msgstr ""
#: lib/pleroma/web/common_api/common_api.ex:141
#, elixir-format
msgid "Could not favorite"
msgstr ""
#: lib/pleroma/web/common_api/common_api.ex:370
#, elixir-format
msgid "Could not pin"
msgstr ""
#: lib/pleroma/web/common_api/common_api.ex:112
#, elixir-format
msgid "Could not repeat"
msgstr ""
#: lib/pleroma/web/common_api/common_api.ex:188
#, elixir-format
msgid "Could not unfavorite"
msgstr ""
#: lib/pleroma/web/common_api/common_api.ex:380
#, elixir-format
msgid "Could not unpin"
msgstr ""
#: lib/pleroma/web/common_api/common_api.ex:126
#, elixir-format
msgid "Could not unrepeat"
msgstr ""
#: lib/pleroma/web/common_api/common_api.ex:428
#: lib/pleroma/web/common_api/common_api.ex:437
#, elixir-format
msgid "Could not update state"
msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202
#, elixir-format
msgid "Error."
msgstr ""
#: lib/pleroma/web/twitter_api/twitter_api.ex:106
#, elixir-format
msgid "Invalid CAPTCHA"
msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:117
#: lib/pleroma/web/oauth/oauth_controller.ex:569
#, elixir-format
msgid "Invalid credentials"
msgstr ""
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
#, elixir-format
msgid "Invalid credentials."
msgstr ""
#: lib/pleroma/web/common_api/common_api.ex:265
#, elixir-format
msgid "Invalid indices"
msgstr ""
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1147
#, elixir-format
msgid "Invalid parameters"
msgstr ""
#: lib/pleroma/web/common_api/utils.ex:411
#, elixir-format
msgid "Invalid password."
msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:187
#, elixir-format
msgid "Invalid request"
msgstr ""
#: lib/pleroma/web/twitter_api/twitter_api.ex:109
#, elixir-format
msgid "Kocaptcha service unavailable"
msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:113
#, elixir-format
msgid "Missing parameters"
msgstr ""
#: lib/pleroma/web/common_api/utils.ex:540
#, elixir-format
msgid "No such conversation"
msgstr ""
#: lib/pleroma/web/admin_api/admin_api_controller.ex:439
#: lib/pleroma/web/admin_api/admin_api_controller.ex:465 lib/pleroma/web/admin_api/admin_api_controller.ex:507
#, elixir-format
msgid "No such permission_group"
msgstr ""
#: lib/pleroma/plugs/uploaded_media.ex:74
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:485 lib/pleroma/web/admin_api/admin_api_controller.ex:1135
#: lib/pleroma/web/feed/user_controller.ex:73 lib/pleroma/web/ostatus/ostatus_controller.ex:143
#, elixir-format
msgid "Not found"
msgstr ""
#: lib/pleroma/web/common_api/common_api.ex:241
#, elixir-format
msgid "Poll's author can't vote"
msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:290
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
#, elixir-format
msgid "Record not found"
msgstr ""
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1153
#: lib/pleroma/web/feed/user_controller.ex:79 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:32
#: lib/pleroma/web/ostatus/ostatus_controller.ex:149
#, elixir-format
msgid "Something went wrong"
msgstr ""
#: lib/pleroma/web/common_api/activity_draft.ex:107
#, elixir-format
msgid "The message visibility must be direct"
msgstr ""
#: lib/pleroma/web/common_api/utils.ex:566
#, elixir-format
msgid "The status is over the character limit"
msgstr ""
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
#, elixir-format
msgid "This resource requires authentication."
msgstr ""
#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
#, elixir-format
msgid "Throttled"
msgstr ""
#: lib/pleroma/web/common_api/common_api.ex:266
#, elixir-format
msgid "Too many choices"
msgstr ""
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:442
#, elixir-format
msgid "Unhandled activity type"
msgstr ""
#: lib/pleroma/web/admin_api/admin_api_controller.ex:536
#, elixir-format
msgid "You can't revoke your own admin status."
msgstr ""
#: lib/pleroma/web/oauth/oauth_controller.ex:218
#: lib/pleroma/web/oauth/oauth_controller.ex:309
#, elixir-format
msgid "Your account is currently disabled"
msgstr ""
#: lib/pleroma/web/oauth/oauth_controller.ex:180
#: lib/pleroma/web/oauth/oauth_controller.ex:332
#, elixir-format
msgid "Your login is missing a confirmed e-mail address"
msgstr ""
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389
#, elixir-format
msgid "can't read inbox of %{nickname} as %{as_nickname}"
msgstr ""
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472
#, elixir-format
msgid "can't update outbox of %{nickname} as %{as_nickname}"
msgstr ""
#: lib/pleroma/web/common_api/common_api.ex:388
#, elixir-format
msgid "conversation is already muted"
msgstr ""
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:316
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491
#, elixir-format
msgid "error"
msgstr ""
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:29
#, elixir-format
msgid "mascots can only be images"
msgstr ""
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:60
#, elixir-format
msgid "not found"
msgstr ""
#: lib/pleroma/web/oauth/oauth_controller.ex:395
#, elixir-format
msgid "Bad OAuth request."
msgstr ""
#: lib/pleroma/web/twitter_api/twitter_api.ex:115
#, elixir-format
msgid "CAPTCHA already used"
msgstr ""
#: lib/pleroma/web/twitter_api/twitter_api.ex:112
#, elixir-format
msgid "CAPTCHA expired"
msgstr ""
#: lib/pleroma/plugs/uploaded_media.ex:55
#, elixir-format
msgid "Failed"
msgstr ""
#: lib/pleroma/web/oauth/oauth_controller.ex:411
#, elixir-format
msgid "Failed to authenticate: %{message}."
msgstr ""
#: lib/pleroma/web/oauth/oauth_controller.ex:442
#, elixir-format
msgid "Failed to set up user account."
msgstr ""
#: lib/pleroma/plugs/oauth_scopes_plug.ex:38
#, elixir-format
msgid "Insufficient permissions: %{permissions}."
msgstr ""
#: lib/pleroma/plugs/uploaded_media.ex:94
#, elixir-format
msgid "Internal Error"
msgstr ""
#: lib/pleroma/web/oauth/fallback_controller.ex:22
#: lib/pleroma/web/oauth/fallback_controller.ex:29
#, elixir-format
msgid "Invalid Username/Password"
msgstr ""
#: lib/pleroma/web/twitter_api/twitter_api.ex:118
#, elixir-format
msgid "Invalid answer data"
msgstr ""
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:128
#, elixir-format
msgid "Nodeinfo schema version not handled"
msgstr ""
#: lib/pleroma/web/oauth/oauth_controller.ex:169
#, elixir-format
msgid "This action is outside the authorized scopes"
msgstr ""
#: lib/pleroma/web/oauth/fallback_controller.ex:14
#, elixir-format
msgid "Unknown error, please check the details and try again."
msgstr ""
#: lib/pleroma/web/oauth/oauth_controller.ex:116
#: lib/pleroma/web/oauth/oauth_controller.ex:155
#, elixir-format
msgid "Unlisted redirect_uri."
msgstr ""
#: lib/pleroma/web/oauth/oauth_controller.ex:391
#, elixir-format
msgid "Unsupported OAuth provider: %{provider}."
msgstr ""
#: lib/pleroma/uploaders/uploader.ex:72
#, elixir-format
msgid "Uploader callback timeout"
msgstr ""
#: lib/pleroma/web/uploader_controller.ex:23
#, elixir-format
msgid "bad request"
msgstr ""
#: lib/pleroma/web/twitter_api/twitter_api.ex:103
#, elixir-format
msgid "CAPTCHA Error"
msgstr ""
#: lib/pleroma/web/common_api/common_api.ex:200
#, elixir-format
msgid "Could not add reaction emoji"
msgstr ""
#: lib/pleroma/web/common_api/common_api.ex:211
#, elixir-format
msgid "Could not remove reaction emoji"
msgstr ""
#: lib/pleroma/web/twitter_api/twitter_api.ex:129
#, elixir-format
msgid "Invalid CAPTCHA (Missing parameter: %{name})"
msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92
#, elixir-format
msgid "List not found"
msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:124
#, elixir-format
msgid "Missing parameter: %{name}"
msgstr ""
#: lib/pleroma/web/oauth/oauth_controller.ex:207
#: lib/pleroma/web/oauth/oauth_controller.ex:322
#, elixir-format
msgid "Password reset is required"
msgstr ""
#: lib/pleroma/tests/auth_test_controller.ex:9
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/admin_api_controller.ex:6
#: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/fallback_redirect_controller.ex:6
#: lib/pleroma/web/feed/tag_controller.ex:6 lib/pleroma/web/feed/user_controller.ex:6
#: lib/pleroma/web/mailer/subscription_controller.ex:2 lib/pleroma/web/masto_fe_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14 lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/report_controller.ex:8 lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7 lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6 lib/pleroma/web/media_proxy/media_proxy_controller.ex:6
#: lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6 lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6
#: lib/pleroma/web/oauth/fallback_controller.ex:6 lib/pleroma/web/oauth/mfa_controller.ex:10
#: lib/pleroma/web/oauth/oauth_controller.ex:6 lib/pleroma/web/ostatus/ostatus_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:2
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/static_fe/static_fe_controller.ex:6
#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6 lib/pleroma/web/twitter_api/twitter_api_controller.ex:6
#: lib/pleroma/web/uploader_controller.ex:6 lib/pleroma/web/web_finger/web_finger_controller.ex:6
#, elixir-format
msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
msgstr ""
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
#, elixir-format
msgid "Two-factor authentication enabled, you must use a access token."
msgstr ""
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:210
#, elixir-format
msgid "Unexpected error occurred while adding file to pack."
msgstr ""
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:138
#, elixir-format
msgid "Unexpected error occurred while creating pack."
msgstr ""
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:278
#, elixir-format
msgid "Unexpected error occurred while removing file from pack."
msgstr ""
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:250
#, elixir-format
msgid "Unexpected error occurred while updating file in pack."
msgstr ""
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:179
#, elixir-format
msgid "Unexpected error occurred while updating pack metadata."
msgstr ""
#: lib/pleroma/plugs/user_is_admin_plug.ex:40
#, elixir-format
msgid "User is not an admin or OAuth admin scope is not granted."
msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
#, elixir-format
msgid "Web push subscription is disabled on this Pleroma instance"
msgstr ""
#: lib/pleroma/web/admin_api/admin_api_controller.ex:502
#, elixir-format
msgid "You can't revoke your own admin/moderator status."
msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:105
#, elixir-format
msgid "authorization required for timeline view"
msgstr ""

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 B

View file

@ -1,6 +1,7 @@
{ {
"files": { "files": {
"blank": "blank.png" "blank": "blank.png",
"blank2": "blank2.png"
}, },
"pack": { "pack": {
"description": "Test description", "description": "Test description",

View file

@ -4,7 +4,7 @@
"homepage": "https://pleroma.social", "homepage": "https://pleroma.social",
"description": "Test description", "description": "Test description",
"fallback-src": "https://nonshared-pack", "fallback-src": "https://nonshared-pack",
"fallback-src-sha256": "74409E2674DAA06C072729C6C8426C4CB3B7E0B85ED77792DB7A436E11D76DAF", "fallback-src-sha256": "1967BB4E42BCC34BCC12D57BE7811D3B7BE52F965BCE45C87BD377B9499CE11D",
"share-files": false "share-files": false
}, },
"files": { "files": {

View file

@ -1342,11 +1342,11 @@ test "returns false for a non-invisible user" do
end end
end end
describe "visible_for?/2" do describe "visible_for/2" do
test "returns true when the account is itself" do test "returns true when the account is itself" do
user = insert(:user, local: true) user = insert(:user, local: true)
assert User.visible_for?(user, user) assert User.visible_for(user, user) == :visible
end end
test "returns false when the account is unauthenticated and auth is required" do test "returns false when the account is unauthenticated and auth is required" do
@ -1355,14 +1355,14 @@ test "returns false when the account is unauthenticated and auth is required" do
user = insert(:user, local: true, confirmation_pending: true) user = insert(:user, local: true, confirmation_pending: true)
other_user = insert(:user, local: true) other_user = insert(:user, local: true)
refute User.visible_for?(user, other_user) refute User.visible_for(user, other_user) == :visible
end end
test "returns true when the account is unauthenticated and auth is not required" do test "returns true when the account is unauthenticated and auth is not required" do
user = insert(:user, local: true, confirmation_pending: true) user = insert(:user, local: true, confirmation_pending: true)
other_user = insert(:user, local: true) other_user = insert(:user, local: true)
assert User.visible_for?(user, other_user) assert User.visible_for(user, other_user) == :visible
end end
test "returns true when the account is unauthenticated and being viewed by a privileged account (auth required)" do test "returns true when the account is unauthenticated and being viewed by a privileged account (auth required)" do
@ -1371,7 +1371,7 @@ test "returns true when the account is unauthenticated and being viewed by a pri
user = insert(:user, local: true, confirmation_pending: true) user = insert(:user, local: true, confirmation_pending: true)
other_user = insert(:user, local: true, is_admin: true) other_user = insert(:user, local: true, is_admin: true)
assert User.visible_for?(user, other_user) assert User.visible_for(user, other_user) == :visible
end end
end end

View file

@ -8,6 +8,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicyTest do
import Pleroma.Web.ActivityPub.MRF.HellthreadPolicy import Pleroma.Web.ActivityPub.MRF.HellthreadPolicy
alias Pleroma.Web.CommonAPI
setup do setup do
user = insert(:user) user = insert(:user)
@ -20,7 +22,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicyTest do
"https://instance.tld/users/user1", "https://instance.tld/users/user1",
"https://instance.tld/users/user2", "https://instance.tld/users/user2",
"https://instance.tld/users/user3" "https://instance.tld/users/user3"
] ],
"object" => %{
"type" => "Note"
}
} }
[user: user, message: message] [user: user, message: message]
@ -28,6 +33,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicyTest do
setup do: clear_config(:mrf_hellthread) setup do: clear_config(:mrf_hellthread)
test "doesn't die on chat messages" do
Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 2, reject_threshold: 0})
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post_chat_message(user, other_user, "moin")
assert {:ok, _} = filter(activity.data)
end
describe "reject" do describe "reject" do
test "rejects the message if the recipient count is above reject_threshold", %{ test "rejects the message if the recipient count is above reject_threshold", %{
message: message message: message

View file

@ -1415,9 +1415,6 @@ test "returns modified object when allowed incoming reply", %{data: data} do
assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
assert modified_object["conversation"] ==
"tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
assert modified_object["context"] == assert modified_object["context"] ==
"tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26" "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
end end

View file

@ -1599,14 +1599,14 @@ test "changes actor type from permitted list", %{conn: conn, user: user} do
assert patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{ assert patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{
"actor_type" => "Application" "actor_type" => "Application"
}) })
|> json_response(200) == %{"errors" => %{"actor_type" => "is invalid"}} |> json_response(400) == %{"errors" => %{"actor_type" => "is invalid"}}
end end
test "update non existing user", %{conn: conn} do test "update non existing user", %{conn: conn} do
assert patch(conn, "/api/pleroma/admin/users/non-existing/credentials", %{ assert patch(conn, "/api/pleroma/admin/users/non-existing/credentials", %{
"password" => "new_password" "password" => "new_password"
}) })
|> json_response(200) == %{"error" => "Unable to update user."} |> json_response(404) == %{"error" => "Not found"}
end end
end end

View file

@ -0,0 +1,145 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.MediaProxyCacheControllerTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
import Mock
alias Pleroma.Web.MediaProxy
setup do: clear_config([:media_proxy])
setup do
on_exit(fn -> Cachex.clear(:banned_urls_cache) end)
end
setup do
admin = insert(:user, is_admin: true)
token = insert(:oauth_admin_token, user: admin)
conn =
build_conn()
|> assign(:user, admin)
|> assign(:token, token)
Config.put([:media_proxy, :enabled], true)
Config.put([:media_proxy, :invalidation, :enabled], true)
Config.put([:media_proxy, :invalidation, :provider], MediaProxy.Invalidation.Script)
{:ok, %{admin: admin, token: token, conn: conn}}
end
describe "GET /api/pleroma/admin/media_proxy_caches" do
test "shows banned MediaProxy URLs", %{conn: conn} do
MediaProxy.put_in_banned_urls([
"http://localhost:4001/media/a688346.jpg",
"http://localhost:4001/media/fb1f4d.jpg"
])
MediaProxy.put_in_banned_urls("http://localhost:4001/media/gb1f44.jpg")
MediaProxy.put_in_banned_urls("http://localhost:4001/media/tb13f47.jpg")
MediaProxy.put_in_banned_urls("http://localhost:4001/media/wb1f46.jpg")
response =
conn
|> get("/api/pleroma/admin/media_proxy_caches?page_size=2")
|> json_response_and_validate_schema(200)
assert response["urls"] == [
"http://localhost:4001/media/fb1f4d.jpg",
"http://localhost:4001/media/a688346.jpg"
]
response =
conn
|> get("/api/pleroma/admin/media_proxy_caches?page_size=2&page=2")
|> json_response_and_validate_schema(200)
assert response["urls"] == [
"http://localhost:4001/media/gb1f44.jpg",
"http://localhost:4001/media/tb13f47.jpg"
]
response =
conn
|> get("/api/pleroma/admin/media_proxy_caches?page_size=2&page=3")
|> json_response_and_validate_schema(200)
assert response["urls"] == ["http://localhost:4001/media/wb1f46.jpg"]
end
end
describe "POST /api/pleroma/admin/media_proxy_caches/delete" do
test "deleted MediaProxy URLs from banned", %{conn: conn} do
MediaProxy.put_in_banned_urls([
"http://localhost:4001/media/a688346.jpg",
"http://localhost:4001/media/fb1f4d.jpg"
])
response =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/media_proxy_caches/delete", %{
urls: ["http://localhost:4001/media/a688346.jpg"]
})
|> json_response_and_validate_schema(200)
assert response["urls"] == ["http://localhost:4001/media/a688346.jpg"]
refute MediaProxy.in_banned_urls("http://localhost:4001/media/a688346.jpg")
assert MediaProxy.in_banned_urls("http://localhost:4001/media/fb1f4d.jpg")
end
end
describe "POST /api/pleroma/admin/media_proxy_caches/purge" do
test "perform invalidates cache of MediaProxy", %{conn: conn} do
urls = [
"http://example.com/media/a688346.jpg",
"http://example.com/media/fb1f4d.jpg"
]
with_mocks [
{MediaProxy.Invalidation.Script, [],
[
purge: fn _, _ -> {"ok", 0} end
]}
] do
response =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/media_proxy_caches/purge", %{urls: urls, ban: false})
|> json_response_and_validate_schema(200)
assert response["urls"] == urls
refute MediaProxy.in_banned_urls("http://example.com/media/a688346.jpg")
refute MediaProxy.in_banned_urls("http://example.com/media/fb1f4d.jpg")
end
end
test "perform invalidates cache of MediaProxy and adds url to banned", %{conn: conn} do
urls = [
"http://example.com/media/a688346.jpg",
"http://example.com/media/fb1f4d.jpg"
]
with_mocks [{MediaProxy.Invalidation.Script, [], [purge: fn _, _ -> {"ok", 0} end]}] do
response =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/media_proxy_caches/purge", %{
urls: urls,
ban: true
})
|> json_response_and_validate_schema(200)
assert response["urls"] == urls
assert MediaProxy.in_banned_urls("http://example.com/media/a688346.jpg")
assert MediaProxy.in_banned_urls("http://example.com/media/fb1f4d.jpg")
end
end
end
end

View file

@ -400,4 +400,71 @@ test "update fields when invalid request", %{conn: conn} do
|> json_response_and_validate_schema(403) |> json_response_and_validate_schema(403)
end end
end end
describe "Mark account as bot" do
setup do: oauth_access(["write:accounts"])
setup :request_content_type
test "changing actor_type to Service makes account a bot", %{conn: conn} do
account =
conn
|> patch("/api/v1/accounts/update_credentials", %{actor_type: "Service"})
|> json_response_and_validate_schema(200)
assert account["bot"]
assert account["source"]["pleroma"]["actor_type"] == "Service"
end
test "changing actor_type to Person makes account a human", %{conn: conn} do
account =
conn
|> patch("/api/v1/accounts/update_credentials", %{actor_type: "Person"})
|> json_response_and_validate_schema(200)
refute account["bot"]
assert account["source"]["pleroma"]["actor_type"] == "Person"
end
test "changing actor_type to Application causes error", %{conn: conn} do
response =
conn
|> patch("/api/v1/accounts/update_credentials", %{actor_type: "Application"})
|> json_response_and_validate_schema(403)
assert %{"error" => "Invalid request"} == response
end
test "changing bot field to true changes actor_type to Service", %{conn: conn} do
account =
conn
|> patch("/api/v1/accounts/update_credentials", %{bot: "true"})
|> json_response_and_validate_schema(200)
assert account["bot"]
assert account["source"]["pleroma"]["actor_type"] == "Service"
end
test "changing bot field to false changes actor_type to Person", %{conn: conn} do
account =
conn
|> patch("/api/v1/accounts/update_credentials", %{bot: "false"})
|> json_response_and_validate_schema(200)
refute account["bot"]
assert account["source"]["pleroma"]["actor_type"] == "Person"
end
test "actor_type field has a higher priority than bot", %{conn: conn} do
account =
conn
|> patch("/api/v1/accounts/update_credentials", %{
actor_type: "Person",
bot: "true"
})
|> json_response_and_validate_schema(200)
refute account["bot"]
assert account["source"]["pleroma"]["actor_type"] == "Person"
end
end
end end

View file

@ -127,6 +127,15 @@ test "returns 404 for internal.fetch actor", %{conn: conn} do
|> get("/api/v1/accounts/internal.fetch") |> get("/api/v1/accounts/internal.fetch")
|> json_response_and_validate_schema(404) |> json_response_and_validate_schema(404)
end end
test "returns 404 for deactivated user", %{conn: conn} do
user = insert(:user, deactivated: true)
assert %{"error" => "Can't find user"} =
conn
|> get("/api/v1/accounts/#{user.id}")
|> json_response_and_validate_schema(:not_found)
end
end end
defp local_and_remote_users do defp local_and_remote_users do
@ -143,15 +152,15 @@ defp local_and_remote_users do
setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true) setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true)
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
assert %{"error" => "Can't find user"} == assert %{"error" => "This API requires an authenticated user"} ==
conn conn
|> get("/api/v1/accounts/#{local.id}") |> get("/api/v1/accounts/#{local.id}")
|> json_response_and_validate_schema(:not_found) |> json_response_and_validate_schema(:unauthorized)
assert %{"error" => "Can't find user"} == assert %{"error" => "This API requires an authenticated user"} ==
conn conn
|> get("/api/v1/accounts/#{remote.id}") |> get("/api/v1/accounts/#{remote.id}")
|> json_response_and_validate_schema(:not_found) |> json_response_and_validate_schema(:unauthorized)
end end
test "if user is authenticated", %{local: local, remote: remote} do test "if user is authenticated", %{local: local, remote: remote} do
@ -173,8 +182,8 @@ test "if user is authenticated", %{local: local, remote: remote} do
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
res_conn = get(conn, "/api/v1/accounts/#{local.id}") res_conn = get(conn, "/api/v1/accounts/#{local.id}")
assert json_response_and_validate_schema(res_conn, :not_found) == %{ assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
"error" => "Can't find user" "error" => "This API requires an authenticated user"
} }
res_conn = get(conn, "/api/v1/accounts/#{remote.id}") res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
@ -203,8 +212,8 @@ test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} d
res_conn = get(conn, "/api/v1/accounts/#{remote.id}") res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
assert json_response_and_validate_schema(res_conn, :not_found) == %{ assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
"error" => "Can't find user" "error" => "This API requires an authenticated user"
} }
end end
@ -249,6 +258,24 @@ test "works with announces that are just addressed to public", %{conn: conn} do
assert id == announce.id assert id == announce.id
end end
test "deactivated user", %{conn: conn} do
user = insert(:user, deactivated: true)
assert %{"error" => "Can't find user"} ==
conn
|> get("/api/v1/accounts/#{user.id}/statuses")
|> json_response_and_validate_schema(:not_found)
end
test "returns 404 when user is invisible", %{conn: conn} do
user = insert(:user, %{invisible: true})
assert %{"error" => "Can't find user"} =
conn
|> get("/api/v1/accounts/#{user.id}")
|> json_response_and_validate_schema(404)
end
test "respects blocks", %{user: user_one, conn: conn} do test "respects blocks", %{user: user_one, conn: conn} do
user_two = insert(:user) user_two = insert(:user)
user_three = insert(:user) user_three = insert(:user)
@ -350,9 +377,10 @@ test "unimplemented pinned statuses feature", %{conn: conn} do
assert json_response_and_validate_schema(conn, 200) == [] assert json_response_and_validate_schema(conn, 200) == []
end end
test "gets an users media", %{conn: conn} do test "gets an users media, excludes reblogs", %{conn: conn} do
note = insert(:note_activity) note = insert(:note_activity)
user = User.get_cached_by_ap_id(note.data["actor"]) user = User.get_cached_by_ap_id(note.data["actor"])
other_user = insert(:user)
file = %Plug.Upload{ file = %Plug.Upload{
content_type: "image/jpg", content_type: "image/jpg",
@ -364,6 +392,13 @@ test "gets an users media", %{conn: conn} do
{:ok, %{id: image_post_id}} = CommonAPI.post(user, %{status: "cofe", media_ids: [media_id]}) {:ok, %{id: image_post_id}} = CommonAPI.post(user, %{status: "cofe", media_ids: [media_id]})
{:ok, %{id: media_id}} = ActivityPub.upload(file, actor: other_user.ap_id)
{:ok, %{id: other_image_post_id}} =
CommonAPI.post(other_user, %{status: "cofe2", media_ids: [media_id]})
{:ok, _announce} = CommonAPI.repeat(other_image_post_id, user)
conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?only_media=true") conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?only_media=true")
assert [%{"id" => ^image_post_id}] = json_response_and_validate_schema(conn, 200) assert [%{"id" => ^image_post_id}] = json_response_and_validate_schema(conn, 200)
@ -422,15 +457,15 @@ defp local_and_remote_activities(%{local: local, remote: remote}) do
setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true) setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true)
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
assert %{"error" => "Can't find user"} == assert %{"error" => "This API requires an authenticated user"} ==
conn conn
|> get("/api/v1/accounts/#{local.id}/statuses") |> get("/api/v1/accounts/#{local.id}/statuses")
|> json_response_and_validate_schema(:not_found) |> json_response_and_validate_schema(:unauthorized)
assert %{"error" => "Can't find user"} == assert %{"error" => "This API requires an authenticated user"} ==
conn conn
|> get("/api/v1/accounts/#{remote.id}/statuses") |> get("/api/v1/accounts/#{remote.id}/statuses")
|> json_response_and_validate_schema(:not_found) |> json_response_and_validate_schema(:unauthorized)
end end
test "if user is authenticated", %{local: local, remote: remote} do test "if user is authenticated", %{local: local, remote: remote} do
@ -451,10 +486,10 @@ test "if user is authenticated", %{local: local, remote: remote} do
setup do: clear_config([:restrict_unauthenticated, :profiles, :local], true) setup do: clear_config([:restrict_unauthenticated, :profiles, :local], true)
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
assert %{"error" => "Can't find user"} == assert %{"error" => "This API requires an authenticated user"} ==
conn conn
|> get("/api/v1/accounts/#{local.id}/statuses") |> get("/api/v1/accounts/#{local.id}/statuses")
|> json_response_and_validate_schema(:not_found) |> json_response_and_validate_schema(:unauthorized)
res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses") res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
assert length(json_response_and_validate_schema(res_conn, 200)) == 1 assert length(json_response_and_validate_schema(res_conn, 200)) == 1
@ -481,10 +516,10 @@ test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} d
res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses") res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
assert length(json_response_and_validate_schema(res_conn, 200)) == 1 assert length(json_response_and_validate_schema(res_conn, 200)) == 1
assert %{"error" => "Can't find user"} == assert %{"error" => "This API requires an authenticated user"} ==
conn conn
|> get("/api/v1/accounts/#{remote.id}/statuses") |> get("/api/v1/accounts/#{remote.id}/statuses")
|> json_response_and_validate_schema(:not_found) |> json_response_and_validate_schema(:unauthorized)
end end
test "if user is authenticated", %{local: local, remote: remote} do test "if user is authenticated", %{local: local, remote: remote} do

View file

@ -151,6 +151,22 @@ test "constructs hashtags from search query", %{conn: conn} do
] ]
end end
test "supports pagination of hashtags search results", %{conn: conn} do
results =
conn
|> get(
"/api/v2/search?#{
URI.encode_query(%{q: "#some #text #with #hashtags", limit: 2, offset: 1})
}"
)
|> json_response_and_validate_schema(200)
assert results["hashtags"] == [
%{"name" => "text", "url" => "#{Web.base_url()}/tag/text"},
%{"name" => "with", "url" => "#{Web.base_url()}/tag/with"}
]
end
test "excludes a blocked users from search results", %{conn: conn} do test "excludes a blocked users from search results", %{conn: conn} do
user = insert(:user) user = insert(:user)
user_smith = insert(:user, %{nickname: "Agent", name: "I love 2hu"}) user_smith = insert(:user, %{nickname: "Agent", name: "I love 2hu"})

View file

@ -1561,7 +1561,7 @@ test "favorites paginate correctly" do
# Using the header for pagination works correctly # Using the header for pagination works correctly
[next, _] = get_resp_header(result, "link") |> hd() |> String.split(", ") [next, _] = get_resp_header(result, "link") |> hd() |> String.split(", ")
[_, max_id] = Regex.run(~r/max_id=(.*)>;/, next) [_, max_id] = Regex.run(~r/max_id=([^&]+)/, next)
assert max_id == third_favorite.id assert max_id == third_favorite.id

View file

@ -49,7 +49,7 @@ test "ChatMessage notification" do
expected = %{ expected = %{
id: to_string(notification.id), id: to_string(notification.id),
pleroma: %{is_seen: false}, pleroma: %{is_seen: false, is_muted: false},
type: "pleroma:chat_mention", type: "pleroma:chat_mention",
account: AccountView.render("show.json", %{user: user, for: recipient}), account: AccountView.render("show.json", %{user: user, for: recipient}),
chat_message: MessageReferenceView.render("show.json", %{chat_message_reference: cm_ref}), chat_message: MessageReferenceView.render("show.json", %{chat_message_reference: cm_ref}),
@ -68,7 +68,7 @@ test "Mention notification" do
expected = %{ expected = %{
id: to_string(notification.id), id: to_string(notification.id),
pleroma: %{is_seen: false}, pleroma: %{is_seen: false, is_muted: false},
type: "mention", type: "mention",
account: account:
AccountView.render("show.json", %{ AccountView.render("show.json", %{
@ -92,7 +92,7 @@ test "Favourite notification" do
expected = %{ expected = %{
id: to_string(notification.id), id: to_string(notification.id),
pleroma: %{is_seen: false}, pleroma: %{is_seen: false, is_muted: false},
type: "favourite", type: "favourite",
account: AccountView.render("show.json", %{user: another_user, for: user}), account: AccountView.render("show.json", %{user: another_user, for: user}),
status: StatusView.render("show.json", %{activity: create_activity, for: user}), status: StatusView.render("show.json", %{activity: create_activity, for: user}),
@ -112,7 +112,7 @@ test "Reblog notification" do
expected = %{ expected = %{
id: to_string(notification.id), id: to_string(notification.id),
pleroma: %{is_seen: false}, pleroma: %{is_seen: false, is_muted: false},
type: "reblog", type: "reblog",
account: AccountView.render("show.json", %{user: another_user, for: user}), account: AccountView.render("show.json", %{user: another_user, for: user}),
status: StatusView.render("show.json", %{activity: reblog_activity, for: user}), status: StatusView.render("show.json", %{activity: reblog_activity, for: user}),
@ -130,7 +130,7 @@ test "Follow notification" do
expected = %{ expected = %{
id: to_string(notification.id), id: to_string(notification.id),
pleroma: %{is_seen: false}, pleroma: %{is_seen: false, is_muted: false},
type: "follow", type: "follow",
account: AccountView.render("show.json", %{user: follower, for: followed}), account: AccountView.render("show.json", %{user: follower, for: followed}),
created_at: Utils.to_masto_date(notification.inserted_at) created_at: Utils.to_masto_date(notification.inserted_at)
@ -171,7 +171,7 @@ test "Move notification" do
expected = %{ expected = %{
id: to_string(notification.id), id: to_string(notification.id),
pleroma: %{is_seen: false}, pleroma: %{is_seen: false, is_muted: false},
type: "move", type: "move",
account: AccountView.render("show.json", %{user: old_user, for: follower}), account: AccountView.render("show.json", %{user: old_user, for: follower}),
target: AccountView.render("show.json", %{user: new_user, for: follower}), target: AccountView.render("show.json", %{user: new_user, for: follower}),
@ -196,7 +196,7 @@ test "EmojiReact notification" do
expected = %{ expected = %{
id: to_string(notification.id), id: to_string(notification.id),
pleroma: %{is_seen: false}, pleroma: %{is_seen: false, is_muted: false},
type: "pleroma:emoji_reaction", type: "pleroma:emoji_reaction",
emoji: "", emoji: "",
account: AccountView.render("show.json", %{user: other_user, for: user}), account: AccountView.render("show.json", %{user: other_user, for: user}),
@ -206,4 +206,26 @@ test "EmojiReact notification" do
test_notifications_rendering([notification], user, [expected]) test_notifications_rendering([notification], user, [expected])
end end
test "muted notification" do
user = insert(:user)
another_user = insert(:user)
{:ok, _} = Pleroma.UserRelationship.create_mute(user, another_user)
{:ok, create_activity} = CommonAPI.post(user, %{status: "hey"})
{:ok, favorite_activity} = CommonAPI.favorite(another_user, create_activity.id)
{:ok, [notification]} = Notification.create_notifications(favorite_activity)
create_activity = Activity.get_by_id(create_activity.id)
expected = %{
id: to_string(notification.id),
pleroma: %{is_seen: false, is_muted: true},
type: "favourite",
account: AccountView.render("show.json", %{user: another_user, for: user}),
status: StatusView.render("show.json", %{activity: create_activity, for: user}),
created_at: Utils.to_masto_date(notification.inserted_at)
}
test_notifications_rendering([notification], user, [expected])
end
end end

View file

@ -0,0 +1,64 @@
defmodule Pleroma.Web.MediaProxy.InvalidationTest do
use ExUnit.Case
use Pleroma.Tests.Helpers
alias Pleroma.Config
alias Pleroma.Web.MediaProxy.Invalidation
import ExUnit.CaptureLog
import Mock
import Tesla.Mock
setup do: clear_config([:media_proxy])
setup do
on_exit(fn -> Cachex.clear(:banned_urls_cache) end)
end
describe "Invalidation.Http" do
test "perform request to clear cache" do
Config.put([:media_proxy, :enabled], false)
Config.put([:media_proxy, :invalidation, :enabled], true)
Config.put([:media_proxy, :invalidation, :provider], Invalidation.Http)
Config.put([Invalidation.Http], method: :purge, headers: [{"x-refresh", 1}])
image_url = "http://example.com/media/example.jpg"
Pleroma.Web.MediaProxy.put_in_banned_urls(image_url)
mock(fn
%{
method: :purge,
url: "http://example.com/media/example.jpg",
headers: [{"x-refresh", 1}]
} ->
%Tesla.Env{status: 200}
end)
assert capture_log(fn ->
assert Pleroma.Web.MediaProxy.in_banned_urls(image_url)
assert Invalidation.purge([image_url]) == {:ok, [image_url]}
assert Pleroma.Web.MediaProxy.in_banned_urls(image_url)
end) =~ "Running cache purge: [\"#{image_url}\"]"
end
end
describe "Invalidation.Script" do
test "run script to clear cache" do
Config.put([:media_proxy, :enabled], false)
Config.put([:media_proxy, :invalidation, :enabled], true)
Config.put([:media_proxy, :invalidation, :provider], Invalidation.Script)
Config.put([Invalidation.Script], script_path: "purge-nginx")
image_url = "http://example.com/media/example.jpg"
Pleroma.Web.MediaProxy.put_in_banned_urls(image_url)
with_mocks [{System, [], [cmd: fn _, _ -> {"ok", 0} end]}] do
assert capture_log(fn ->
assert Pleroma.Web.MediaProxy.in_banned_urls(image_url)
assert Invalidation.purge([image_url]) == {:ok, [image_url]}
assert Pleroma.Web.MediaProxy.in_banned_urls(image_url)
end) =~ "Running cache purge: [\"#{image_url}\"]"
end
end
end
end

View file

@ -5,6 +5,10 @@ defmodule Pleroma.Web.MediaProxy.Invalidation.HttpTest do
import ExUnit.CaptureLog import ExUnit.CaptureLog
import Tesla.Mock import Tesla.Mock
setup do
on_exit(fn -> Cachex.clear(:banned_urls_cache) end)
end
test "logs hasn't error message when request is valid" do test "logs hasn't error message when request is valid" do
mock(fn mock(fn
%{method: :purge, url: "http://example.com/media/example.jpg"} -> %{method: :purge, url: "http://example.com/media/example.jpg"} ->
@ -14,8 +18,8 @@ test "logs hasn't error message when request is valid" do
refute capture_log(fn -> refute capture_log(fn ->
assert Invalidation.Http.purge( assert Invalidation.Http.purge(
["http://example.com/media/example.jpg"], ["http://example.com/media/example.jpg"],
%{} []
) == {:ok, "success"} ) == {:ok, ["http://example.com/media/example.jpg"]}
end) =~ "Error while cache purge" end) =~ "Error while cache purge"
end end
@ -28,8 +32,8 @@ test "it write error message in logs when request invalid" do
assert capture_log(fn -> assert capture_log(fn ->
assert Invalidation.Http.purge( assert Invalidation.Http.purge(
["http://example.com/media/example1.jpg"], ["http://example.com/media/example1.jpg"],
%{} []
) == {:ok, "success"} ) == {:ok, ["http://example.com/media/example1.jpg"]}
end) =~ "Error while cache purge: url - http://example.com/media/example1.jpg" end) =~ "Error while cache purge: url - http://example.com/media/example1.jpg"
end end
end end

View file

@ -4,17 +4,23 @@ defmodule Pleroma.Web.MediaProxy.Invalidation.ScriptTest do
import ExUnit.CaptureLog import ExUnit.CaptureLog
setup do
on_exit(fn -> Cachex.clear(:banned_urls_cache) end)
end
test "it logger error when script not found" do test "it logger error when script not found" do
assert capture_log(fn -> assert capture_log(fn ->
assert Invalidation.Script.purge( assert Invalidation.Script.purge(
["http://example.com/media/example.jpg"], ["http://example.com/media/example.jpg"],
%{script_path: "./example"} script_path: "./example"
) == {:error, "\"%ErlangError{original: :enoent}\""} ) == {:error, "%ErlangError{original: :enoent}"}
end) =~ "Error while cache purge: \"%ErlangError{original: :enoent}\"" end) =~ "Error while cache purge: %ErlangError{original: :enoent}"
capture_log(fn ->
assert Invalidation.Script.purge( assert Invalidation.Script.purge(
["http://example.com/media/example.jpg"], ["http://example.com/media/example.jpg"],
%{} []
) == {:error, "not found script path"} ) == {:error, "\"not found script path\""}
end)
end end
end end

View file

@ -10,6 +10,10 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
setup do: clear_config(:media_proxy) setup do: clear_config(:media_proxy)
setup do: clear_config([Pleroma.Web.Endpoint, :secret_key_base]) setup do: clear_config([Pleroma.Web.Endpoint, :secret_key_base])
setup do
on_exit(fn -> Cachex.clear(:banned_urls_cache) end)
end
test "it returns 404 when MediaProxy disabled", %{conn: conn} do test "it returns 404 when MediaProxy disabled", %{conn: conn} do
Config.put([:media_proxy, :enabled], false) Config.put([:media_proxy, :enabled], false)
@ -66,4 +70,16 @@ test "it performs ReverseProxy.call when signature valid", %{conn: conn} do
assert %Plug.Conn{status: :success} = get(conn, url) assert %Plug.Conn{status: :success} = get(conn, url)
end end
end end
test "it returns 404 when url contains in banned_urls cache", %{conn: conn} do
Config.put([:media_proxy, :enabled], true)
Config.put([Pleroma.Web.Endpoint, :secret_key_base], "00000000000")
url = Pleroma.Web.MediaProxy.encode_url("https://google.fn/test.png")
Pleroma.Web.MediaProxy.put_in_banned_urls("https://google.fn/test.png")
with_mock Pleroma.ReverseProxy,
call: fn _conn, _url, _opts -> %Plug.Conn{status: :success} end do
assert %Plug.Conn{status: 404, resp_body: "Not Found"} = get(conn, url)
end
end
end end

View file

@ -30,15 +30,55 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
test "GET /api/pleroma/emoji/packs", %{conn: conn} do test "GET /api/pleroma/emoji/packs", %{conn: conn} do
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200) resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
shared = resp["test_pack"] assert resp["count"] == 3
assert shared["files"] == %{"blank" => "blank.png"}
assert resp["packs"]
|> Map.keys()
|> length() == 3
shared = resp["packs"]["test_pack"]
assert shared["files"] == %{"blank" => "blank.png", "blank2" => "blank2.png"}
assert Map.has_key?(shared["pack"], "download-sha256") assert Map.has_key?(shared["pack"], "download-sha256")
assert shared["pack"]["can-download"] assert shared["pack"]["can-download"]
assert shared["pack"]["share-files"] assert shared["pack"]["share-files"]
non_shared = resp["test_pack_nonshared"] non_shared = resp["packs"]["test_pack_nonshared"]
assert non_shared["pack"]["share-files"] == false assert non_shared["pack"]["share-files"] == false
assert non_shared["pack"]["can-download"] == false assert non_shared["pack"]["can-download"] == false
resp =
conn
|> get("/api/pleroma/emoji/packs?page_size=1")
|> json_response_and_validate_schema(200)
assert resp["count"] == 3
packs = Map.keys(resp["packs"])
assert length(packs) == 1
[pack1] = packs
resp =
conn
|> get("/api/pleroma/emoji/packs?page_size=1&page=2")
|> json_response_and_validate_schema(200)
assert resp["count"] == 3
packs = Map.keys(resp["packs"])
assert length(packs) == 1
[pack2] = packs
resp =
conn
|> get("/api/pleroma/emoji/packs?page_size=1&page=3")
|> json_response_and_validate_schema(200)
assert resp["count"] == 3
packs = Map.keys(resp["packs"])
assert length(packs) == 1
[pack3] = packs
assert [pack1, pack2, pack3] |> Enum.uniq() |> length() == 3
end end
describe "GET /api/pleroma/emoji/packs/remote" do describe "GET /api/pleroma/emoji/packs/remote" do
@ -332,7 +372,7 @@ test "for a pack with a fallback source", ctx do
Map.put( Map.put(
new_data, new_data,
"fallback-src-sha256", "fallback-src-sha256",
"74409E2674DAA06C072729C6C8426C4CB3B7E0B85ED77792DB7A436E11D76DAF" "1967BB4E42BCC34BCC12D57BE7811D3B7BE52F965BCE45C87BD377B9499CE11D"
) )
assert ctx[:admin_conn] assert ctx[:admin_conn]
@ -398,7 +438,7 @@ test "don't rewrite old emoji", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank2", shortcode: "blank3",
filename: "dir/blank.png", filename: "dir/blank.png",
file: %Plug.Upload{ file: %Plug.Upload{
filename: "blank.png", filename: "blank.png",
@ -407,7 +447,8 @@ test "don't rewrite old emoji", %{admin_conn: admin_conn} do
}) })
|> json_response_and_validate_schema(200) == %{ |> json_response_and_validate_schema(200) == %{
"blank" => "blank.png", "blank" => "blank.png",
"blank2" => "dir/blank.png" "blank2" => "blank2.png",
"blank3" => "dir/blank.png"
} }
assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png")
@ -431,7 +472,7 @@ test "rewrite old emoji with force option", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank2", shortcode: "blank3",
filename: "dir/blank.png", filename: "dir/blank.png",
file: %Plug.Upload{ file: %Plug.Upload{
filename: "blank.png", filename: "blank.png",
@ -440,7 +481,8 @@ test "rewrite old emoji with force option", %{admin_conn: admin_conn} do
}) })
|> json_response_and_validate_schema(200) == %{ |> json_response_and_validate_schema(200) == %{
"blank" => "blank.png", "blank" => "blank.png",
"blank2" => "dir/blank.png" "blank2" => "blank2.png",
"blank3" => "dir/blank.png"
} }
assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png")
@ -448,14 +490,15 @@ test "rewrite old emoji with force option", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{ |> patch("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank2", shortcode: "blank3",
new_shortcode: "blank3", new_shortcode: "blank4",
new_filename: "dir_2/blank_3.png", new_filename: "dir_2/blank_3.png",
force: true force: true
}) })
|> json_response_and_validate_schema(200) == %{ |> json_response_and_validate_schema(200) == %{
"blank" => "blank.png", "blank" => "blank.png",
"blank3" => "dir_2/blank_3.png" "blank2" => "blank2.png",
"blank4" => "dir_2/blank_3.png"
} }
assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png") assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png")
@ -481,7 +524,7 @@ test "add file with not loaded pack", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/not_loaded/files", %{ |> post("/api/pleroma/emoji/packs/not_loaded/files", %{
shortcode: "blank2", shortcode: "blank3",
filename: "dir/blank.png", filename: "dir/blank.png",
file: %Plug.Upload{ file: %Plug.Upload{
filename: "blank.png", filename: "blank.png",
@ -535,7 +578,8 @@ test "new with shortcode as file with update", %{admin_conn: admin_conn} do
}) })
|> json_response_and_validate_schema(200) == %{ |> json_response_and_validate_schema(200) == %{
"blank" => "blank.png", "blank" => "blank.png",
"blank4" => "dir/blank.png" "blank4" => "dir/blank.png",
"blank2" => "blank2.png"
} }
assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png")
@ -549,7 +593,8 @@ test "new with shortcode as file with update", %{admin_conn: admin_conn} do
}) })
|> json_response_and_validate_schema(200) == %{ |> json_response_and_validate_schema(200) == %{
"blank3" => "dir_2/blank_3.png", "blank3" => "dir_2/blank_3.png",
"blank" => "blank.png" "blank" => "blank.png",
"blank2" => "blank2.png"
} }
refute File.exists?("#{@emoji_path}/test_pack/dir/") refute File.exists?("#{@emoji_path}/test_pack/dir/")
@ -557,7 +602,10 @@ test "new with shortcode as file with update", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank3") |> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank3")
|> json_response_and_validate_schema(200) == %{"blank" => "blank.png"} |> json_response_and_validate_schema(200) == %{
"blank" => "blank.png",
"blank2" => "blank2.png"
}
refute File.exists?("#{@emoji_path}/test_pack/dir_2/") refute File.exists?("#{@emoji_path}/test_pack/dir_2/")
@ -581,7 +629,8 @@ test "new with shortcode from url", %{admin_conn: admin_conn} do
}) })
|> json_response_and_validate_schema(200) == %{ |> json_response_and_validate_schema(200) == %{
"blank_url" => "blank_url.png", "blank_url" => "blank_url.png",
"blank" => "blank.png" "blank" => "blank.png",
"blank2" => "blank2.png"
} }
assert File.exists?("#{@emoji_path}/test_pack/blank_url.png") assert File.exists?("#{@emoji_path}/test_pack/blank_url.png")
@ -602,15 +651,16 @@ test "new without shortcode", %{admin_conn: admin_conn} do
}) })
|> json_response_and_validate_schema(200) == %{ |> json_response_and_validate_schema(200) == %{
"shortcode" => "shortcode.png", "shortcode" => "shortcode.png",
"blank" => "blank.png" "blank" => "blank.png",
"blank2" => "blank2.png"
} }
end end
test "remove non existing shortcode in pack.json", %{admin_conn: admin_conn} do test "remove non existing shortcode in pack.json", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank2") |> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank3")
|> json_response_and_validate_schema(:bad_request) == %{ |> json_response_and_validate_schema(:bad_request) == %{
"error" => "Emoji \"blank2\" does not exist" "error" => "Emoji \"blank3\" does not exist"
} }
end end
@ -618,12 +668,12 @@ test "update non existing emoji", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{ |> patch("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank2", shortcode: "blank3",
new_shortcode: "blank3", new_shortcode: "blank4",
new_filename: "dir_2/blank_3.png" new_filename: "dir_2/blank_3.png"
}) })
|> json_response_and_validate_schema(:bad_request) == %{ |> json_response_and_validate_schema(:bad_request) == %{
"error" => "Emoji \"blank2\" does not exist" "error" => "Emoji \"blank3\" does not exist"
} }
end end
@ -651,7 +701,8 @@ test "creating and deleting a pack", %{admin_conn: admin_conn} do
assert Jason.decode!(File.read!("#{@emoji_path}/test_created/pack.json")) == %{ assert Jason.decode!(File.read!("#{@emoji_path}/test_created/pack.json")) == %{
"pack" => %{}, "pack" => %{},
"files" => %{} "files" => %{},
"files_count" => 0
} }
assert admin_conn assert admin_conn
@ -709,14 +760,14 @@ test "filesystem import", %{admin_conn: admin_conn, conn: conn} do
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200) resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
refute Map.has_key?(resp, "test_pack_for_import") refute Map.has_key?(resp["packs"], "test_pack_for_import")
assert admin_conn assert admin_conn
|> get("/api/pleroma/emoji/packs/import") |> get("/api/pleroma/emoji/packs/import")
|> json_response_and_validate_schema(200) == ["test_pack_for_import"] |> json_response_and_validate_schema(200) == ["test_pack_for_import"]
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200) resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
assert resp["test_pack_for_import"]["files"] == %{"blank" => "blank.png"} assert resp["packs"]["test_pack_for_import"]["files"] == %{"blank" => "blank.png"}
File.rm!("#{@emoji_path}/test_pack_for_import/pack.json") File.rm!("#{@emoji_path}/test_pack_for_import/pack.json")
refute File.exists?("#{@emoji_path}/test_pack_for_import/pack.json") refute File.exists?("#{@emoji_path}/test_pack_for_import/pack.json")
@ -736,7 +787,7 @@ test "filesystem import", %{admin_conn: admin_conn, conn: conn} do
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200) resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
assert resp["test_pack_for_import"]["files"] == %{ assert resp["packs"]["test_pack_for_import"]["files"] == %{
"blank" => "blank.png", "blank" => "blank.png",
"blank2" => "blank.png", "blank2" => "blank.png",
"foo" => "blank.png" "foo" => "blank.png"
@ -746,7 +797,8 @@ test "filesystem import", %{admin_conn: admin_conn, conn: conn} do
describe "GET /api/pleroma/emoji/packs/:name" do describe "GET /api/pleroma/emoji/packs/:name" do
test "shows pack.json", %{conn: conn} do test "shows pack.json", %{conn: conn} do
assert %{ assert %{
"files" => %{"blank" => "blank.png"}, "files" => files,
"files_count" => 2,
"pack" => %{ "pack" => %{
"can-download" => true, "can-download" => true,
"description" => "Test description", "description" => "Test description",
@ -759,6 +811,28 @@ test "shows pack.json", %{conn: conn} do
conn conn
|> get("/api/pleroma/emoji/packs/test_pack") |> get("/api/pleroma/emoji/packs/test_pack")
|> json_response_and_validate_schema(200) |> json_response_and_validate_schema(200)
assert files == %{"blank" => "blank.png", "blank2" => "blank2.png"}
assert %{
"files" => files,
"files_count" => 2
} =
conn
|> get("/api/pleroma/emoji/packs/test_pack?page_size=1")
|> json_response_and_validate_schema(200)
assert files |> Map.keys() |> length() == 1
assert %{
"files" => files,
"files_count" => 2
} =
conn
|> get("/api/pleroma/emoji/packs/test_pack?page_size=1&page=2")
|> json_response_and_validate_schema(200)
assert files |> Map.keys() |> length() == 1
end end
test "non existing pack", %{conn: conn} do test "non existing pack", %{conn: conn} do