Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into emr_develop

This commit is contained in:
a1batross 2020-07-18 20:56:37 +02:00
commit 6bedfd81fa
235 changed files with 2138 additions and 3196 deletions

View File

@ -8,9 +8,7 @@
### Environment ### Environment
* Installation type: * Installation type (OTP or From Source):
- [ ] OTP
- [ ] From source
* Pleroma version (could be found in the "Version" tab of settings in Pleroma-FE): * Pleroma version (could be found in the "Version" tab of settings in Pleroma-FE):
* Elixir version (`elixir -v` for from source installations, N/A for OTP): * Elixir version (`elixir -v` for from source installations, N/A for OTP):
* Operating system: * Operating system:

View File

@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- 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
- Configuration: `:instance, rewrite_policy` moved to `:mrf, policies`, `:instance, :mrf_transparency` moved to `:mrf, :transparency`, `:instance, :mrf_transparency_exclusions` moved to `:mrf, :transparency_exclusions`. Old config namespace is deprecated. - Configuration: `:instance, rewrite_policy` moved to `:mrf, policies`, `:instance, :mrf_transparency` moved to `:mrf, :transparency`, `:instance, :mrf_transparency_exclusions` moved to `:mrf, :transparency_exclusions`. Old config namespace is deprecated.
- Configuration: `:media_proxy, whitelist` format changed to host with scheme (e.g. `http://example.com` instead of `example.com`). Domain format is deprecated.
<details> <details>
<summary>API Changes</summary> <summary>API Changes</summary>
@ -24,6 +25,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mastodon API: Added `pleroma.metadata.fields_limits` to /api/v1/instance - Mastodon API: Added `pleroma.metadata.fields_limits` to /api/v1/instance
- Mastodon API: On deletion, returns the original post text. - Mastodon API: On deletion, returns the original post text.
- Mastodon API: Add `pleroma.unread_count` to the Marker entity. - Mastodon API: Add `pleroma.unread_count` to the Marker entity.
- **Breaking:** Notification Settings API for suppressing notifications
has been simplified down to `block_from_strangers`.
- **Breaking:** Notification Settings API option for hiding push notification
contents has been renamed to `hide_notification_contents`
- Mastodon API: Added `pleroma.metadata.post_formats` to /api/v1/instance
</details> </details>
<details> <details>

View File

@ -172,7 +172,7 @@ config :mime, :types, %{
"application/ld+json" => ["activity+json"] "application/ld+json" => ["activity+json"]
} }
config :tesla, adapter: Tesla.Adapter.Hackney config :tesla, adapter: Tesla.Adapter.Gun
# Configures http settings, upstream proxy etc. # Configures http settings, upstream proxy etc.
config :pleroma, :http, config :pleroma, :http,
@ -512,6 +512,7 @@ config :pleroma, Oban,
attachments_cleanup: 5, attachments_cleanup: 5,
new_users_digest: 1 new_users_digest: 1
], ],
plugins: [Oban.Plugins.Pruner],
crontab: [ crontab: [
{"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker}, {"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker},
{"0 * * * *", Pleroma.Workers.Cron.StatsWorker}, {"0 * * * *", Pleroma.Workers.Cron.StatsWorker},
@ -647,32 +648,30 @@ config :pleroma, Pleroma.Repo,
prepare: :unnamed prepare: :unnamed
config :pleroma, :connections_pool, config :pleroma, :connections_pool,
checkin_timeout: 250, reclaim_multiplier: 0.1,
connection_acquisition_wait: 250,
connection_acquisition_retries: 5,
max_connections: 250, max_connections: 250,
retry: 1, max_idle_time: 30_000,
retry_timeout: 1000, retry: 0,
await_up_timeout: 5_000 await_up_timeout: 5_000
config :pleroma, :pools, config :pleroma, :pools,
federation: [ federation: [
size: 50, size: 50,
max_overflow: 10, max_waiting: 10
timeout: 150_000
], ],
media: [ media: [
size: 50, size: 50,
max_overflow: 10, max_waiting: 10
timeout: 150_000
], ],
upload: [ upload: [
size: 25, size: 25,
max_overflow: 5, max_waiting: 5
timeout: 300_000
], ],
default: [ default: [
size: 10, size: 10,
max_overflow: 2, max_waiting: 2
timeout: 10_000
] ]
config :pleroma, :hackney_pools, config :pleroma, :hackney_pools,

View File

@ -23,18 +23,14 @@ config :pleroma, :config_description, [
key: :uploader, key: :uploader,
type: :module, type: :module,
description: "Module which will be used for uploads", description: "Module which will be used for uploads",
suggestions: [Pleroma.Uploaders.Local, Pleroma.Uploaders.S3] suggestions: {:list_behaviour_implementations, Pleroma.Uploaders.Uploader}
}, },
%{ %{
key: :filters, key: :filters,
type: {:list, :module}, type: {:list, :module},
description: description:
"List of filter modules for uploads. Module names are shortened (removed leading `Pleroma.Upload.Filter.` part), but on adding custom module you need to use full name.", "List of filter modules for uploads. Module names are shortened (removed leading `Pleroma.Upload.Filter.` part), but on adding custom module you need to use full name.",
suggestions: suggestions: {:list_behaviour_implementations, Pleroma.Upload.Filter}
Generator.list_modules_in_dir(
"lib/pleroma/upload/filter",
"Elixir.Pleroma.Upload.Filter."
)
}, },
%{ %{
key: :link_name, key: :link_name,
@ -1071,6 +1067,7 @@ config :pleroma, :config_description, [
}, },
%{ %{
key: :webhook_url, key: :webhook_url,
label: "Webhook URL",
type: :string, type: :string,
description: "Configure the Slack incoming webhook", description: "Configure the Slack incoming webhook",
suggestions: ["https://hooks.slack.com/services/YOUR-KEY-HERE"] suggestions: ["https://hooks.slack.com/services/YOUR-KEY-HERE"]
@ -1404,11 +1401,7 @@ config :pleroma, :config_description, [
type: [:module, {:list, :module}], type: [:module, {:list, :module}],
description: description:
"A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.", "A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
suggestions: suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF}
Generator.list_modules_in_dir(
"lib/pleroma/web/activity_pub/mrf",
"Elixir.Pleroma.Web.ActivityPub.MRF."
)
}, },
%{ %{
key: :transparency, key: :transparency,
@ -1523,7 +1516,7 @@ config :pleroma, :config_description, [
children: [ children: [
%{ %{
key: :match_actor, key: :match_actor,
type: :map, type: {:map, {:list, :string}},
description: "Matches a series of regular expressions against the actor field", description: "Matches a series of regular expressions against the actor field",
suggestions: [ suggestions: [
%{ %{
@ -1589,21 +1582,21 @@ config :pleroma, :config_description, [
children: [ children: [
%{ %{
key: :reject, key: :reject,
type: [:string, :regex], type: {:list, :string},
description: description:
"A list of patterns which result in message being rejected. Each pattern can be a string or a regular expression.", "A list of patterns which result in message being rejected. Each pattern can be a string or a regular expression.",
suggestions: ["foo", ~r/foo/iu] suggestions: ["foo", ~r/foo/iu]
}, },
%{ %{
key: :federated_timeline_removal, key: :federated_timeline_removal,
type: [:string, :regex], type: {:list, :string},
description: description:
"A list of patterns which result in message being removed from federated timelines (a.k.a unlisted). Each pattern can be a string or a regular expression.", "A list of patterns which result in message being removed from federated timelines (a.k.a unlisted). Each pattern can be a string or a regular expression.",
suggestions: ["foo", ~r/foo/iu] suggestions: ["foo", ~r/foo/iu]
}, },
%{ %{
key: :replace, key: :replace,
type: [{:tuple, :string, :string}, {:tuple, :regex, :string}], type: {:list, :tuple},
description: description:
"A list of tuples containing {pattern, replacement}. Each pattern can be a string or a regular expression.", "A list of tuples containing {pattern, replacement}. Each pattern can be a string or a regular expression.",
suggestions: [{"foo", "bar"}, {~r/foo/iu, "bar"}] suggestions: [{"foo", "bar"}, {~r/foo/iu, "bar"}]
@ -1775,8 +1768,8 @@ config :pleroma, :config_description, [
%{ %{
key: :whitelist, key: :whitelist,
type: {:list, :string}, type: {:list, :string},
description: "List of domains to bypass the mediaproxy", description: "List of hosts with scheme to bypass the mediaproxy",
suggestions: ["example.com"] suggestions: ["http://example.com"]
} }
] ]
}, },
@ -1793,15 +1786,20 @@ config :pleroma, :config_description, [
}, },
%{ %{
key: :headers, key: :headers,
type: {:list, :tuple}, type: {:keyword, :string},
description: "HTTP headers of request.", description: "HTTP headers of request",
suggestions: [{"x-refresh", 1}] suggestions: [{"x-refresh", 1}]
}, },
%{ %{
key: :options, key: :options,
type: :keyword, type: :keyword,
description: "Request options.", description: "Request options",
suggestions: [params: %{ts: "xxx"}] children: [
%{
key: :params,
type: {:map, :string}
}
]
} }
] ]
}, },
@ -2010,13 +2008,15 @@ config :pleroma, :config_description, [
label: "Pleroma Admin Token", label: "Pleroma Admin Token",
type: :group, type: :group,
description: description:
"Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the `admin_token` parameter", "Allows setting a token that can be used to authenticate requests with admin privileges without a normal user account token. Append the `admin_token` parameter to requests to utilize it. (Please reconsider using HTTP Basic Auth or OAuth-based authentication if possible)",
children: [ children: [
%{ %{
key: :admin_token, key: :admin_token,
type: :string, type: :string,
description: "Admin token", description: "Admin token",
suggestions: ["We recommend a secure random string or UUID"] suggestions: [
"Please use a high entropy string or UUID"
]
} }
] ]
}, },
@ -2517,7 +2517,7 @@ config :pleroma, :config_description, [
%{ %{
key: :styling, key: :styling,
type: :map, type: :map,
description: "a map with color settings for email templates.", description: "A map with color settings for email templates.",
suggestions: [ suggestions: [
%{ %{
link_color: "#d8a070", link_color: "#d8a070",
@ -2622,7 +2622,7 @@ config :pleroma, :config_description, [
}, },
%{ %{
key: :groups, key: :groups,
type: {:keyword, :string, {:list, :string}}, type: {:keyword, {:list, :string}},
description: description:
"Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the group name" <> "Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the group name" <>
" and the value is the location or array of locations. * can be used as a wildcard.", " and the value is the location or array of locations. * can be used as a wildcard.",
@ -3161,36 +3161,37 @@ config :pleroma, :config_description, [
description: "Advanced settings for `gun` connections pool", description: "Advanced settings for `gun` connections pool",
children: [ children: [
%{ %{
key: :checkin_timeout, key: :connection_acquisition_wait,
type: :integer, type: :integer,
description: "Timeout to checkin connection from pool. Default: 250ms.", description:
"Timeout to acquire a connection from pool.The total max time is this value multiplied by the number of retries. Default: 250ms.",
suggestions: [250] suggestions: [250]
}, },
%{
key: :connection_acquisition_retries,
type: :integer,
description:
"Number of attempts to acquire the connection from the pool if it is overloaded. Default: 5",
suggestions: [5]
},
%{ %{
key: :max_connections, key: :max_connections,
type: :integer, type: :integer,
description: "Maximum number of connections in the pool. Default: 250 connections.", description: "Maximum number of connections in the pool. Default: 250 connections.",
suggestions: [250] suggestions: [250]
}, },
%{
key: :retry,
type: :integer,
description:
"Number of retries, while `gun` will try to reconnect if connection goes down. Default: 1.",
suggestions: [1]
},
%{
key: :retry_timeout,
type: :integer,
description:
"Time between retries when `gun` will try to reconnect in milliseconds. Default: 1000ms.",
suggestions: [1000]
},
%{ %{
key: :await_up_timeout, key: :await_up_timeout,
type: :integer, type: :integer,
description: "Timeout while `gun` will wait until connection is up. Default: 5000ms.", description: "Timeout while `gun` will wait until connection is up. Default: 5000ms.",
suggestions: [5000] suggestions: [5000]
},
%{
key: :reclaim_multiplier,
type: :integer,
description:
"Multiplier for the number of idle connection to be reclaimed if the pool is full. For example if the pool maxes out at 250 connections and this setting is set to 0.3, the pool will reclaim at most 75 idle connections if it's overloaded. Default: 0.1",
suggestions: [0.1]
} }
] ]
}, },
@ -3199,108 +3200,29 @@ config :pleroma, :config_description, [
key: :pools, key: :pools,
type: :group, type: :group,
description: "Advanced settings for `gun` workers pools", description: "Advanced settings for `gun` workers pools",
children: [ children:
%{ Enum.map([:federation, :media, :upload, :default], fn pool_name ->
key: :federation, %{
type: :keyword, key: pool_name,
description: "Settings for federation pool.", type: :keyword,
children: [ description: "Settings for #{pool_name} pool.",
%{ children: [
key: :size, %{
type: :integer, key: :size,
description: "Number workers in the pool.", type: :integer,
suggestions: [50] description: "Maximum number of concurrent requests in the pool.",
}, suggestions: [50]
%{ },
key: :max_overflow, %{
type: :integer, key: :max_waiting,
description: "Number of additional workers if pool is under load.", type: :integer,
suggestions: [10] description:
}, "Maximum number of requests waiting for other requests to finish. After this number is reached, the pool will start returning errrors when a new request is made",
%{ suggestions: [10]
key: :timeout, }
type: :integer, ]
description: "Timeout while `gun` will wait for response.", }
suggestions: [150_000] end)
}
]
},
%{
key: :media,
type: :keyword,
description: "Settings for media pool.",
children: [
%{
key: :size,
type: :integer,
description: "Number workers in the pool.",
suggestions: [50]
},
%{
key: :max_overflow,
type: :integer,
description: "Number of additional workers if pool is under load.",
suggestions: [10]
},
%{
key: :timeout,
type: :integer,
description: "Timeout while `gun` will wait for response.",
suggestions: [150_000]
}
]
},
%{
key: :upload,
type: :keyword,
description: "Settings for upload pool.",
children: [
%{
key: :size,
type: :integer,
description: "Number workers in the pool.",
suggestions: [25]
},
%{
key: :max_overflow,
type: :integer,
description: "Number of additional workers if pool is under load.",
suggestions: [5]
},
%{
key: :timeout,
type: :integer,
description: "Timeout while `gun` will wait for response.",
suggestions: [300_000]
}
]
},
%{
key: :default,
type: :keyword,
description: "Settings for default pool.",
children: [
%{
key: :size,
type: :integer,
description: "Number workers in the pool.",
suggestions: [10]
},
%{
key: :max_overflow,
type: :integer,
description: "Number of additional workers if pool is under load.",
suggestions: [2]
},
%{
key: :timeout,
type: :integer,
description: "Timeout while `gun` will wait for response.",
suggestions: [10_000]
}
]
}
]
}, },
%{ %{
group: :pleroma, group: :pleroma,

View File

@ -113,6 +113,11 @@ config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true
config :pleroma, :instances_favicons, enabled: true config :pleroma, :instances_favicons, enabled: true
config :pleroma, Pleroma.Uploaders.S3,
bucket: nil,
streaming_enabled: true,
public_endpoint: nil
if File.exists?("./config/test.secret.exs") do if File.exists?("./config/test.secret.exs") do
import_config "test.secret.exs" import_config "test.secret.exs"
else else

View File

@ -236,6 +236,7 @@ Has theses additional parameters (which are the same as in Pleroma-API):
- `pleroma.metadata.features`: A list of supported features - `pleroma.metadata.features`: A list of supported features
- `pleroma.metadata.federation`: The federation restrictions of this instance - `pleroma.metadata.federation`: The federation restrictions of this instance
- `pleroma.metadata.fields_limits`: A list of values detailing the length and count limitation for various instance-configurable fields. - `pleroma.metadata.fields_limits`: A list of values detailing the length and count limitation for various instance-configurable fields.
- `pleroma.metadata.post_formats`: A list of the allowed post format types
- `vapid_public_key`: The public key needed for push messages - `vapid_public_key`: The public key needed for push messages
## Markers ## Markers

View File

@ -287,11 +287,8 @@ See [Admin-API](admin_api.md)
* Method `PUT` * Method `PUT`
* Authentication: required * Authentication: required
* Params: * Params:
* `followers`: BOOLEAN field, receives notifications from followers * `block_from_strangers`: BOOLEAN field, blocks notifications from accounts you do not follow
* `follows`: BOOLEAN field, receives notifications from people the user follows * `hide_notification_contents`: BOOLEAN field. When set to true, it removes the contents of a message from the push notification.
* `remote`: BOOLEAN field, receives notifications from people on remote instances
* `local`: BOOLEAN field, receives notifications from people on the local instance
* `privacy_option`: BOOLEAN field. When set to true, it removes the contents of a message from the push notification.
* Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}` * Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}`
## `/api/pleroma/healthcheck` ## `/api/pleroma/healthcheck`

View File

@ -252,6 +252,7 @@ This section describe PWA manifest instance-specific values. Currently this opti
* `background_color`: Describe the background color of the app. (Example: `"#191b22"`, `"aliceblue"`). * `background_color`: Describe the background color of the app. (Example: `"#191b22"`, `"aliceblue"`).
## :emoji ## :emoji
* `shortcode_globs`: Location of custom emoji files. `*` can be used as a wildcard. Example `["/emoji/custom/**/*.png"]` * `shortcode_globs`: Location of custom emoji files. `*` can be used as a wildcard. Example `["/emoji/custom/**/*.png"]`
* `pack_extensions`: A list of file extensions for emojis, when no emoji.txt for a pack is present. Example `[".png", ".gif"]` * `pack_extensions`: A list of file extensions for emojis, when no emoji.txt for a pack is present. Example `[".png", ".gif"]`
* `groups`: Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the groupname and the value the location or array of locations. `*` can be used as a wildcard. Example `[Custom: ["/emoji/*.png", "/emoji/custom/*.png"]]` * `groups`: Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the groupname and the value the location or array of locations. `*` can be used as a wildcard. Example `[Custom: ["/emoji/*.png", "/emoji/custom/*.png"]]`
@ -260,13 +261,14 @@ This section describe PWA manifest instance-specific values. Currently this opti
memory for this amount of seconds multiplied by the number of files. memory for this amount of seconds multiplied by the number of files.
## :media_proxy ## :media_proxy
* `enabled`: Enables proxying of remote media to the instances proxy * `enabled`: Enables proxying of remote media to the instances proxy
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts. * `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.
* `proxy_opts`: All options defined in `Pleroma.ReverseProxy` documentation, defaults to `[max_body_length: (25*1_048_576)]`. * `proxy_opts`: All options defined in `Pleroma.ReverseProxy` documentation, defaults to `[max_body_length: (25*1_048_576)]`.
* `whitelist`: List of domains to bypass the mediaproxy * `whitelist`: List of hosts with scheme to bypass the mediaproxy (e.g. `https://example.com`)
* `invalidation`: options for remove media from cache after delete object: * `invalidation`: options for remove media from cache after delete object:
* `enabled`: Enables purge cache * `enabled`: Enables purge cache
* `provider`: Which one of the [purge cache strategy](#purge-cache-strategy) to use. * `provider`: Which one of the [purge cache strategy](#purge-cache-strategy) to use.
### Purge cache strategy ### Purge cache strategy
@ -278,6 +280,7 @@ Urls of attachments pass to script as arguments.
* `script_path`: path to external script. * `script_path`: path to external script.
Example: Example:
```elixir ```elixir
config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Script, config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Script,
script_path: "./installation/nginx-cache-purge.example" script_path: "./installation/nginx-cache-purge.example"
@ -445,36 +448,32 @@ For each pool, the options are:
*For `gun` adapter* *For `gun` adapter*
Advanced settings for connections pool. Pool with opened connections. These connections can be reused in worker pools. Settings for HTTP connection pool.
For big instances it's recommended to increase `config :pleroma, :connections_pool, max_connections: 500` up to 500-1000. * `:connection_acquisition_wait` - Timeout to acquire a connection from pool.The total max time is this value multiplied by the number of retries.
It will increase memory usage, but federation would work faster. * `connection_acquisition_retries` - Number of attempts to acquire the connection from the pool if it is overloaded. Each attempt is timed `:connection_acquisition_wait` apart.
* `:max_connections` - Maximum number of connections in the pool.
* `:checkin_timeout` - timeout to checkin connection from pool. Default: 250ms. * `:await_up_timeout` - Timeout to connect to the host.
* `:max_connections` - maximum number of connections in the pool. Default: 250 connections. * `:reclaim_multiplier` - Multiplied by `:max_connections` this will be the maximum number of idle connections that will be reclaimed in case the pool is overloaded.
* `:retry` - number of retries, while `gun` will try to reconnect if connection goes down. Default: 1.
* `:retry_timeout` - time between retries when `gun` will try to reconnect in milliseconds. Default: 1000ms.
* `:await_up_timeout` - timeout while `gun` will wait until connection is up. Default: 5000ms.
### :pools ### :pools
*For `gun` adapter* *For `gun` adapter*
Advanced settings for workers pools. Settings for request pools. These pools are limited on top of `:connections_pool`.
There are four pools used: There are four pools used:
* `:federation` for the federation jobs. * `:federation` for the federation jobs. You may want this pool's max_connections to be at least equal to the number of federator jobs + retry queue jobs.
You may want this pool max_connections to be at least equal to the number of federator jobs + retry queue jobs. * `:media` - for rich media, media proxy.
* `:media` for rich media, media proxy * `:upload` - for proxying media when a remote uploader is used and `proxy_remote: true`.
* `:upload` for uploaded media (if using a remote uploader and `proxy_remote: true`) * `:default` - for other requests.
* `:default` for other requests
For each pool, the options are: For each pool, the options are:
* `:size` - how much workers the pool can hold * `:size` - limit to how much requests can be concurrently executed.
* `:timeout` - timeout while `gun` will wait for response * `:timeout` - timeout while `gun` will wait for response
* `:max_overflow` - additional workers if pool is under load * `:max_waiting` - limit to how much requests can be waiting for others to finish, after this is reached, subsequent requests will be dropped.
## Captcha ## Captcha
@ -629,8 +628,7 @@ Email notifications settings.
Configuration options described in [Oban readme](https://github.com/sorentwo/oban#usage): Configuration options described in [Oban readme](https://github.com/sorentwo/oban#usage):
* `repo` - app's Ecto repo (`Pleroma.Repo`) * `repo` - app's Ecto repo (`Pleroma.Repo`)
* `verbose` - logs verbosity * `log` - logs verbosity
* `prune` - non-retryable jobs [pruning settings](https://github.com/sorentwo/oban#pruning) (`:disabled` / `{:maxlen, value}` / `{:maxage, value}`)
* `queues` - job queues (see below) * `queues` - job queues (see below)
* `crontab` - periodic jobs, see [`Oban.Cron`](#obancron) * `crontab` - periodic jobs, see [`Oban.Cron`](#obancron)
@ -815,6 +813,8 @@ or
curl -H "X-Admin-Token: somerandomtoken" "http://localhost:4000/api/pleroma/admin/users/invites" curl -H "X-Admin-Token: somerandomtoken" "http://localhost:4000/api/pleroma/admin/users/invites"
``` ```
Warning: it's discouraged to use this feature because of the associated security risk: static / rarely changed instance-wide token is much weaker compared to email-password pair of a real admin user; consider using HTTP Basic Auth or OAuth-based authentication instead.
### :auth ### :auth
* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator. * `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator.

View File

@ -0,0 +1,151 @@
# How to activate Pleroma in-database configuration
## Explanation
The configuration of Pleroma has traditionally been managed with a config file, e.g. `config/prod.secret.exs`. This method requires a restart of the application for any configuration changes to take effect. We have made it possible to control most settings in the AdminFE interface after running a migration script.
## Migration to database config
1. Stop your Pleroma instance and edit your Pleroma config to enable database configuration:
```
config :pleroma, configurable_from_database: true
```
2. Run the mix task to migrate to the database. You'll receive some debugging output and a few messages informing you of what happened.
**Source:**
```
$ mix pleroma.config migrate_to_db
```
or
**OTP:**
```
$ ./bin/pleroma_ctl config migrate_to_db
```
```
10:04:34.155 [debug] QUERY OK source="config" db=1.6ms decode=2.0ms queue=33.5ms idle=0.0ms
SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 []
Migrating settings from file: /home/pleroma/config/dev.secret.exs
10:04:34.240 [debug] QUERY OK db=4.5ms queue=0.3ms idle=92.2ms
TRUNCATE config; []
10:04:34.244 [debug] QUERY OK db=2.8ms queue=0.3ms idle=97.2ms
ALTER SEQUENCE config_id_seq RESTART; []
10:04:34.256 [debug] QUERY OK source="config" db=0.8ms queue=1.4ms idle=109.8ms
SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 WHERE ((c0."group" = $1) AND (c0."key" = $2)) [":pleroma", ":instance"]
10:04:34.292 [debug] QUERY OK db=2.6ms queue=1.7ms idle=137.7ms
INSERT INTO "config" ("group","key","value","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" [":pleroma", ":instance", <<131, 108, 0, 0, 0, 1, 104, 2, 100, 0, 4, 110, 97, 109, 101, 109, 0, 0, 0, 7, 66, 108, 101, 114, 111, 109, 97, 106>>, ~N[2020-07-12 15:04:34], ~N[2020-07-12 15:04:34]]
Settings for key instance migrated.
Settings for group :pleroma migrated.
```
3. It is recommended to backup your config file now.
```
cp config/dev.secret.exs config/dev.secret.exs.orig
```
4. Now you can edit your config file and strip it down to the only settings which are not possible to control in the database. e.g., the Postgres and webserver (Endpoint) settings cannot be controlled in the database because the application needs the settings to start up and access the database.
⚠️ **THIS IS NOT REQUIRED**
Any settings in the database will override those in the config file, but you may find it less confusing if the setting is only declared in one place.
A non-exhaustive list of settings that are only possible in the config file include the following:
* config :pleroma, Pleroma.Web.Endpoint
* config :pleroma, Pleroma.Repo
* config :pleroma, configurable_from_database
* config :pleroma, :database, rum_enabled
* config :pleroma, :connections_pool
Here is an example of a server config stripped down after migration:
```
use Mix.Config
config :pleroma, Pleroma.Web.Endpoint,
url: [host: "cool.pleroma.site", scheme: "https", port: 443]
config :pleroma, Pleroma.Repo,
adapter: Ecto.Adapters.Postgres,
username: "pleroma",
password: "MySecretPassword",
database: "pleroma_prod",
hostname: "localhost"
config :pleroma, configurable_from_database: true
```
5. Start your instance back up and you can now access the Settings tab in AdminFE.
## Reverting back from database config
1. Stop your Pleroma instance.
2. Run the mix task to migrate back from the database. You'll receive some debugging output and a few messages informing you of what happened.
**Source:**
```
$ mix pleroma.config migrate_from_db
```
or
**OTP:**
```
$ ./bin/pleroma_ctl config migrate_from_db
```
```
10:26:30.593 [debug] QUERY OK source="config" db=9.8ms decode=1.2ms queue=26.0ms idle=0.0ms
SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 []
10:26:30.659 [debug] QUERY OK source="config" db=1.1ms idle=80.7ms
SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 []
Database configuration settings have been saved to config/dev.exported_from_db.secret.exs
```
3. The in-database configuration still exists, but it will not be used if you remove `config :pleroma, configurable_from_database: true` from your config.
## Debugging
### Clearing database config
You can clear the database config by truncating the `config` table in the database. e.g.,
```
psql -d pleroma_dev
pleroma_dev=# TRUNCATE config;
TRUNCATE TABLE
```
Additionally, every time you migrate the configuration to the database the config table is automatically truncated to ensure a clean migration.
### Manually removing a setting
If you encounter a situation where the server cannot run properly because of an invalid setting in the database and this is preventing you from accessing AdminFE, you can manually remove the offending setting if you know which one it is.
e.g., here is an example showing a minimal configuration in the database. Only the `config :pleroma, :instance` settings are in the table:
```
psql -d pleroma_dev
pleroma_dev=# select * from config;
id | key | value | inserted_at | updated_at | group
----+-----------+------------------------------------------------------------+---------------------+---------------------+----------
1 | :instance | \x836c0000000168026400046e616d656d00000007426c65726f6d616a | 2020-07-12 15:33:29 | 2020-07-12 15:33:29 | :pleroma
(1 row)
pleroma_dev=# delete from config where key = ':instance' and group = ':pleroma';
DELETE 1
```
Now the `config :pleroma, :instance` settings have been removed from the database.

View File

@ -83,7 +83,7 @@ defmodule Mix.Tasks.Pleroma.Config do
defp migrate_from_db(opts) do defp migrate_from_db(opts) do
if Pleroma.Config.get([:configurable_from_database]) do if Pleroma.Config.get([:configurable_from_database]) do
env = opts[:env] || "prod" env = opts[:env] || Pleroma.Config.get(:env)
config_path = config_path =
if Pleroma.Config.get(:release) do if Pleroma.Config.get(:release) do
@ -105,6 +105,10 @@ defmodule Mix.Tasks.Pleroma.Config do
:ok = File.close(file) :ok = File.close(file)
System.cmd("mix", ["format", config_path]) System.cmd("mix", ["format", config_path])
shell_info(
"Database configuration settings have been exported to config/#{env}.exported_from_db.secret.exs"
)
else else
migration_error() migration_error()
end end
@ -112,7 +116,7 @@ defmodule Mix.Tasks.Pleroma.Config do
defp migration_error do defp migration_error do
shell_error( shell_error(
"Migration is not allowed in config. You can change this behavior by setting `configurable_from_database` to true." "Migration is not allowed in config. You can change this behavior by setting `config :pleroma, configurable_from_database: true`"
) )
end end

View File

@ -3,8 +3,8 @@ defmodule Mix.Tasks.Pleroma.NotificationSettings do
@moduledoc """ @moduledoc """
Example: Example:
> mix pleroma.notification_settings --privacy-option=false --nickname-users="parallel588" # set false only for parallel588 user > mix pleroma.notification_settings --hide-notification-contents=false --nickname-users="parallel588" # set false only for parallel588 user
> mix pleroma.notification_settings --privacy-option=true # set true for all users > mix pleroma.notification_settings --hide-notification-contents=true # set true for all users
""" """
@ -19,16 +19,16 @@ defmodule Mix.Tasks.Pleroma.NotificationSettings do
OptionParser.parse( OptionParser.parse(
args, args,
strict: [ strict: [
privacy_option: :boolean, hide_notification_contents: :boolean,
email_users: :string, email_users: :string,
nickname_users: :string nickname_users: :string
] ]
) )
privacy_option = Keyword.get(options, :privacy_option) hide_notification_contents = Keyword.get(options, :hide_notification_contents)
if not is_nil(privacy_option) do if not is_nil(hide_notification_contents) do
privacy_option hide_notification_contents
|> build_query(options) |> build_query(options)
|> Pleroma.Repo.update_all([]) |> Pleroma.Repo.update_all([])
end end
@ -36,15 +36,15 @@ defmodule Mix.Tasks.Pleroma.NotificationSettings do
shell_info("Done") shell_info("Done")
end end
defp build_query(privacy_option, options) do defp build_query(hide_notification_contents, options) do
query = query =
from(u in Pleroma.User, from(u in Pleroma.User,
update: [ update: [
set: [ set: [
notification_settings: notification_settings:
fragment( fragment(
"jsonb_set(notification_settings, '{privacy_option}', ?)", "jsonb_set(notification_settings, '{hide_notification_contents}', ?)",
^privacy_option ^hide_notification_contents
) )
] ]
] ]

View File

@ -35,6 +35,11 @@ defmodule Pleroma.Application do
# See http://elixir-lang.org/docs/stable/elixir/Application.html # See http://elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications # for more information on OTP Applications
def start(_type, _args) do def start(_type, _args) do
# Scrubbers are compiled at runtime and therefore will cause a conflict
# every time the application is restarted, so we disable module
# conflicts at runtime
Code.compiler_options(ignore_module_conflict: true)
Pleroma.Telemetry.Logger.attach()
Config.Holder.save_default() Config.Holder.save_default()
Pleroma.HTML.compile_scrubbers() Pleroma.HTML.compile_scrubbers()
Config.DeprecationWarnings.warn() Config.DeprecationWarnings.warn()
@ -42,6 +47,7 @@ defmodule Pleroma.Application do
Pleroma.ApplicationRequirements.verify!() Pleroma.ApplicationRequirements.verify!()
setup_instrumenters() setup_instrumenters()
load_custom_modules() load_custom_modules()
Pleroma.Docs.JSON.compile()
adapter = Application.get_env(:tesla, :adapter) adapter = Application.get_env(:tesla, :adapter)
@ -218,9 +224,7 @@ defmodule Pleroma.Application do
# start hackney and gun pools in tests # start hackney and gun pools in tests
defp http_children(_, :test) do defp http_children(_, :test) do
hackney_options = Config.get([:hackney_pools, :federation]) http_children(Tesla.Adapter.Hackney, nil) ++ http_children(Tesla.Adapter.Gun, nil)
hackney_pool = :hackney_pool.child_spec(:federation, hackney_options)
[hackney_pool, Pleroma.Pool.Supervisor]
end end
defp http_children(Tesla.Adapter.Hackney, _) do defp http_children(Tesla.Adapter.Hackney, _) do
@ -239,7 +243,10 @@ defmodule Pleroma.Application do
end end
end end
defp http_children(Tesla.Adapter.Gun, _), do: [Pleroma.Pool.Supervisor] defp http_children(Tesla.Adapter.Gun, _) do
Pleroma.Gun.ConnectionPool.children() ++
[{Task, &Pleroma.HTTP.AdapterHelper.Gun.limiter_setup/0}]
end
defp http_children(_, _), do: [] defp http_children(_, _), do: []
end end

View File

@ -54,6 +54,7 @@ defmodule Pleroma.Config.DeprecationWarnings do
check_hellthread_threshold() check_hellthread_threshold()
mrf_user_allowlist() mrf_user_allowlist()
check_old_mrf_config() check_old_mrf_config()
check_media_proxy_whitelist_config()
end end
def check_old_mrf_config do def check_old_mrf_config do
@ -65,7 +66,7 @@ defmodule Pleroma.Config.DeprecationWarnings do
move_namespace_and_warn(@mrf_config_map, warning_preface) move_namespace_and_warn(@mrf_config_map, warning_preface)
end end
@spec move_namespace_and_warn([config_map()], String.t()) :: :ok @spec move_namespace_and_warn([config_map()], String.t()) :: :ok | nil
def move_namespace_and_warn(config_map, warning_preface) do def move_namespace_and_warn(config_map, warning_preface) do
warning = warning =
Enum.reduce(config_map, "", fn Enum.reduce(config_map, "", fn
@ -84,4 +85,16 @@ defmodule Pleroma.Config.DeprecationWarnings do
Logger.warn(warning_preface <> warning) Logger.warn(warning_preface <> warning)
end end
end end
@spec check_media_proxy_whitelist_config() :: :ok | nil
def check_media_proxy_whitelist_config do
whitelist = Config.get([:media_proxy, :whitelist])
if Enum.any?(whitelist, &(not String.starts_with?(&1, "http"))) do
Logger.warn("""
!!!DEPRECATION WARNING!!!
Your config is using old format (only domain) for MediaProxy whitelist option. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later.
""")
end
end
end end

View File

@ -6,16 +6,21 @@ defmodule Pleroma.Docs.Generator do
implementation.process(descriptions) implementation.process(descriptions)
end end
@spec list_modules_in_dir(String.t(), String.t()) :: [module()] @spec list_behaviour_implementations(behaviour :: module()) :: [module()]
def list_modules_in_dir(dir, start) do def list_behaviour_implementations(behaviour) do
with {:ok, files} <- File.ls(dir) do :code.all_loaded()
files |> Enum.filter(fn {module, _} ->
|> Enum.filter(&String.ends_with?(&1, ".ex")) # This shouldn't be needed as all modules are expected to have module_info/1,
|> Enum.map(fn filename -> # but in test enviroments some transient modules `:elixir_compiler_XX`
module = filename |> String.trim_trailing(".ex") |> Macro.camelize() # are loaded for some reason (where XX is a random integer).
String.to_atom(start <> module) if function_exported?(module, :module_info, 1) do
end) module.module_info(:attributes)
end |> Keyword.get_values(:behaviour)
|> List.flatten()
|> Enum.member?(behaviour)
end
end)
|> Enum.map(fn {module, _} -> module end)
end end
@doc """ @doc """
@ -87,6 +92,12 @@ defmodule Pleroma.Docs.Generator do
else: string else: string
end end
defp format_suggestions({:list_behaviour_implementations, behaviour}) do
behaviour
|> list_behaviour_implementations()
|> format_suggestions()
end
defp format_suggestions([]), do: [] defp format_suggestions([]), do: []
defp format_suggestions([suggestion | tail]) do defp format_suggestions([suggestion | tail]) do

View File

@ -1,5 +1,19 @@
defmodule Pleroma.Docs.JSON do defmodule Pleroma.Docs.JSON do
@behaviour Pleroma.Docs.Generator @behaviour Pleroma.Docs.Generator
@external_resource "config/description.exs"
@raw_config Pleroma.Config.Loader.read("config/description.exs")
@raw_descriptions @raw_config[:pleroma][:config_description]
@term __MODULE__.Compiled
@spec compile :: :ok
def compile do
:persistent_term.put(@term, Pleroma.Docs.Generator.convert_to_strings(@raw_descriptions))
end
@spec compiled_descriptions :: Map.t()
def compiled_descriptions do
:persistent_term.get(@term)
end
@spec process(keyword()) :: {:ok, String.t()} @spec process(keyword()) :: {:ok, String.t()}
def process(descriptions) do def process(descriptions) do
@ -13,11 +27,4 @@ defmodule Pleroma.Docs.JSON do
{:ok, path} {:ok, path}
end end
end end
def compile do
with config <- Pleroma.Config.Loader.read("config/description.exs") do
config[:pleroma][:config_description]
|> Pleroma.Docs.Generator.convert_to_strings()
end
end
end end

View File

@ -68,6 +68,11 @@ defmodule Pleroma.Docs.Markdown do
IO.write(file, " #{list_mark}`#{inspect(suggestion)}`\n") IO.write(file, " #{list_mark}`#{inspect(suggestion)}`\n")
end end
defp print_suggestions(file, {:list_behaviour_implementations, behaviour}) do
suggestions = Pleroma.Docs.Generator.list_behaviour_implementations(behaviour)
print_suggestions(file, suggestions)
end
defp print_suggestions(_file, nil), do: nil defp print_suggestions(_file, nil), do: nil
defp print_suggestions(_file, ""), do: nil defp print_suggestions(_file, ""), do: nil

View File

@ -19,7 +19,8 @@ defmodule Pleroma.Gun.API do
:tls_opts, :tls_opts,
:tcp_opts, :tcp_opts,
:socks_opts, :socks_opts,
:ws_opts :ws_opts,
:supervise
] ]
@impl Gun @impl Gun

View File

@ -3,85 +3,33 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Gun.Conn do defmodule Pleroma.Gun.Conn do
@moduledoc """
Struct for gun connection data
"""
alias Pleroma.Gun alias Pleroma.Gun
alias Pleroma.Pool.Connections
require Logger require Logger
@type gun_state :: :up | :down def open(%URI{} = uri, opts) do
@type conn_state :: :active | :idle
@type t :: %__MODULE__{
conn: pid(),
gun_state: gun_state(),
conn_state: conn_state(),
used_by: [pid()],
last_reference: pos_integer(),
crf: float(),
retries: pos_integer()
}
defstruct conn: nil,
gun_state: :open,
conn_state: :init,
used_by: [],
last_reference: 0,
crf: 1,
retries: 0
@spec open(String.t() | URI.t(), atom(), keyword()) :: :ok | nil
def open(url, name, opts \\ [])
def open(url, name, opts) when is_binary(url), do: open(URI.parse(url), name, opts)
def open(%URI{} = uri, name, opts) do
pool_opts = Pleroma.Config.get([:connections_pool], []) pool_opts = Pleroma.Config.get([:connections_pool], [])
opts = opts =
opts opts
|> Enum.into(%{}) |> Enum.into(%{})
|> Map.put_new(:retry, pool_opts[:retry] || 1)
|> Map.put_new(:retry_timeout, pool_opts[:retry_timeout] || 1000)
|> Map.put_new(:await_up_timeout, pool_opts[:await_up_timeout] || 5_000) |> Map.put_new(:await_up_timeout, pool_opts[:await_up_timeout] || 5_000)
|> Map.put_new(:supervise, false)
|> maybe_add_tls_opts(uri) |> maybe_add_tls_opts(uri)
key = "#{uri.scheme}:#{uri.host}:#{uri.port}" do_open(uri, opts)
max_connections = pool_opts[:max_connections] || 250
conn_pid =
if Connections.count(name) < max_connections do
do_open(uri, opts)
else
close_least_used_and_do_open(name, uri, opts)
end
if is_pid(conn_pid) do
conn = %Pleroma.Gun.Conn{
conn: conn_pid,
gun_state: :up,
conn_state: :active,
last_reference: :os.system_time(:second)
}
:ok = Gun.set_owner(conn_pid, Process.whereis(name))
Connections.add_conn(name, key, conn)
end
end end
defp maybe_add_tls_opts(opts, %URI{scheme: "http"}), do: opts defp maybe_add_tls_opts(opts, %URI{scheme: "http"}), do: opts
defp maybe_add_tls_opts(opts, %URI{scheme: "https", host: host}) do defp maybe_add_tls_opts(opts, %URI{scheme: "https"}) do
tls_opts = [ tls_opts = [
verify: :verify_peer, verify: :verify_peer,
cacertfile: CAStore.file_path(), cacertfile: CAStore.file_path(),
depth: 20, depth: 20,
reuse_sessions: false, reuse_sessions: false,
verify_fun: log_level: :warning,
{&:ssl_verify_hostname.verify_fun/3, customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]
[check_hostname: Pleroma.HTTP.Connection.format_host(host)]}
] ]
tls_opts = tls_opts =
@ -105,7 +53,7 @@ defmodule Pleroma.Gun.Conn do
{:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]), {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]),
stream <- Gun.connect(conn, connect_opts), stream <- Gun.connect(conn, connect_opts),
{:response, :fin, 200, _} <- Gun.await(conn, stream) do {:response, :fin, 200, _} <- Gun.await(conn, stream) do
conn {:ok, conn}
else else
error -> error ->
Logger.warn( Logger.warn(
@ -141,7 +89,7 @@ defmodule Pleroma.Gun.Conn do
with {:ok, conn} <- Gun.open(proxy_host, proxy_port, opts), with {:ok, conn} <- Gun.open(proxy_host, proxy_port, opts),
{:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do
conn {:ok, conn}
else else
error -> error ->
Logger.warn( Logger.warn(
@ -155,11 +103,11 @@ defmodule Pleroma.Gun.Conn do
end end
defp do_open(%URI{host: host, port: port} = uri, opts) do defp do_open(%URI{host: host, port: port} = uri, opts) do
host = Pleroma.HTTP.Connection.parse_host(host) host = Pleroma.HTTP.AdapterHelper.parse_host(host)
with {:ok, conn} <- Gun.open(host, port, opts), with {:ok, conn} <- Gun.open(host, port, opts),
{:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do
conn {:ok, conn}
else else
error -> error ->
Logger.warn( Logger.warn(
@ -171,7 +119,7 @@ defmodule Pleroma.Gun.Conn do
end end
defp destination_opts(%URI{host: host, port: port}) do defp destination_opts(%URI{host: host, port: port}) do
host = Pleroma.HTTP.Connection.parse_host(host) host = Pleroma.HTTP.AdapterHelper.parse_host(host)
%{host: host, port: port} %{host: host, port: port}
end end
@ -181,17 +129,6 @@ defmodule Pleroma.Gun.Conn do
defp add_http2_opts(opts, _, _), do: opts defp add_http2_opts(opts, _, _), do: opts
defp close_least_used_and_do_open(name, uri, opts) do
with [{key, conn} | _conns] <- Connections.get_unused_conns(name),
:ok <- Gun.close(conn.conn) do
Connections.remove_conn(name, key)
do_open(uri, opts)
else
[] -> {:error, :pool_overflowed}
end
end
def compose_uri_log(%URI{scheme: scheme, host: host, path: path}) do def compose_uri_log(%URI{scheme: scheme, host: host, path: path}) do
"#{scheme}://#{host}#{path}" "#{scheme}://#{host}#{path}"
end end

View File

@ -0,0 +1,79 @@
defmodule Pleroma.Gun.ConnectionPool do
@registry __MODULE__
alias Pleroma.Gun.ConnectionPool.WorkerSupervisor
def children do
[
{Registry, keys: :unique, name: @registry},
Pleroma.Gun.ConnectionPool.WorkerSupervisor
]
end
def get_conn(uri, opts) do
key = "#{uri.scheme}:#{uri.host}:#{uri.port}"
case Registry.lookup(@registry, key) do
# The key has already been registered, but connection is not up yet
[{worker_pid, nil}] ->
get_gun_pid_from_worker(worker_pid, true)
[{worker_pid, {gun_pid, _used_by, _crf, _last_reference}}] ->
GenServer.cast(worker_pid, {:add_client, self(), false})
{:ok, gun_pid}
[] ->
# :gun.set_owner fails in :connected state for whatevever reason,
# so we open the connection in the process directly and send it's pid back
# We trust gun to handle timeouts by itself
case WorkerSupervisor.start_worker([key, uri, opts, self()]) do
{:ok, worker_pid} ->
get_gun_pid_from_worker(worker_pid, false)
{:error, {:already_started, worker_pid}} ->
get_gun_pid_from_worker(worker_pid, true)
err ->
err
end
end
end
defp get_gun_pid_from_worker(worker_pid, register) do
# GenServer.call will block the process for timeout length if
# the server crashes on startup (which will happen if gun fails to connect)
# so instead we use cast + monitor
ref = Process.monitor(worker_pid)
if register, do: GenServer.cast(worker_pid, {:add_client, self(), true})
receive do
{:conn_pid, pid} ->
Process.demonitor(ref)
{:ok, pid}
{:DOWN, ^ref, :process, ^worker_pid, reason} ->
case reason do
{:shutdown, error} -> error
_ -> {:error, reason}
end
end
end
def release_conn(conn_pid) do
# :ets.fun2ms(fn {_, {worker_pid, {gun_pid, _, _, _}}} when gun_pid == conn_pid ->
# worker_pid end)
query_result =
Registry.select(@registry, [
{{:_, :"$1", {:"$2", :_, :_, :_}}, [{:==, :"$2", conn_pid}], [:"$1"]}
])
case query_result do
[worker_pid] ->
GenServer.cast(worker_pid, {:remove_client, self()})
[] ->
:ok
end
end
end

View File

@ -0,0 +1,85 @@
defmodule Pleroma.Gun.ConnectionPool.Reclaimer do
use GenServer, restart: :temporary
@registry Pleroma.Gun.ConnectionPool
def start_monitor do
pid =
case :gen_server.start(__MODULE__, [], name: {:via, Registry, {@registry, "reclaimer"}}) do
{:ok, pid} ->
pid
{:error, {:already_registered, pid}} ->
pid
end
{pid, Process.monitor(pid)}
end
@impl true
def init(_) do
{:ok, nil, {:continue, :reclaim}}
end
@impl true
def handle_continue(:reclaim, _) do
max_connections = Pleroma.Config.get([:connections_pool, :max_connections])
reclaim_max =
[:connections_pool, :reclaim_multiplier]
|> Pleroma.Config.get()
|> Kernel.*(max_connections)
|> round
|> max(1)
:telemetry.execute([:pleroma, :connection_pool, :reclaim, :start], %{}, %{
max_connections: max_connections,
reclaim_max: reclaim_max
})
# :ets.fun2ms(
# fn {_, {worker_pid, {_, used_by, crf, last_reference}}} when used_by == [] ->
# {worker_pid, crf, last_reference} end)
unused_conns =
Registry.select(
@registry,
[
{{:_, :"$1", {:_, :"$2", :"$3", :"$4"}}, [{:==, :"$2", []}], [{{:"$1", :"$3", :"$4"}}]}
]
)
case unused_conns do
[] ->
:telemetry.execute(
[:pleroma, :connection_pool, :reclaim, :stop],
%{reclaimed_count: 0},
%{
max_connections: max_connections
}
)
{:stop, :no_unused_conns, nil}
unused_conns ->
reclaimed =
unused_conns
|> Enum.sort(fn {_pid1, crf1, last_reference1}, {_pid2, crf2, last_reference2} ->
crf1 <= crf2 and last_reference1 <= last_reference2
end)
|> Enum.take(reclaim_max)
reclaimed
|> Enum.each(fn {pid, _, _} ->
DynamicSupervisor.terminate_child(Pleroma.Gun.ConnectionPool.WorkerSupervisor, pid)
end)
:telemetry.execute(
[:pleroma, :connection_pool, :reclaim, :stop],
%{reclaimed_count: Enum.count(reclaimed)},
%{max_connections: max_connections}
)
{:stop, :normal, nil}
end
end
end

View File

@ -0,0 +1,127 @@
defmodule Pleroma.Gun.ConnectionPool.Worker do
alias Pleroma.Gun
use GenServer, restart: :temporary
@registry Pleroma.Gun.ConnectionPool
def start_link([key | _] = opts) do
GenServer.start_link(__MODULE__, opts, name: {:via, Registry, {@registry, key}})
end
@impl true
def init([_key, _uri, _opts, _client_pid] = opts) do
{:ok, nil, {:continue, {:connect, opts}}}
end
@impl true
def handle_continue({:connect, [key, uri, opts, client_pid]}, _) do
with {:ok, conn_pid} <- Gun.Conn.open(uri, opts),
Process.link(conn_pid) do
time = :erlang.monotonic_time(:millisecond)
{_, _} =
Registry.update_value(@registry, key, fn _ ->
{conn_pid, [client_pid], 1, time}
end)
send(client_pid, {:conn_pid, conn_pid})
{:noreply,
%{key: key, timer: nil, client_monitors: %{client_pid => Process.monitor(client_pid)}},
:hibernate}
else
err ->
{:stop, {:shutdown, err}, nil}
end
end
@impl true
def handle_cast({:add_client, client_pid, send_pid_back}, %{key: key} = state) do
time = :erlang.monotonic_time(:millisecond)
{{conn_pid, _, _, _}, _} =
Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} ->
{conn_pid, [client_pid | used_by], crf(time - last_reference, crf), time}
end)
if send_pid_back, do: send(client_pid, {:conn_pid, conn_pid})
state =
if state.timer != nil do
Process.cancel_timer(state[:timer])
%{state | timer: nil}
else
state
end
ref = Process.monitor(client_pid)
state = put_in(state.client_monitors[client_pid], ref)
{:noreply, state, :hibernate}
end
@impl true
def handle_cast({:remove_client, client_pid}, %{key: key} = state) do
{{_conn_pid, used_by, _crf, _last_reference}, _} =
Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} ->
{conn_pid, List.delete(used_by, client_pid), crf, last_reference}
end)
{ref, state} = pop_in(state.client_monitors[client_pid])
Process.demonitor(ref)
timer =
if used_by == [] do
max_idle = Pleroma.Config.get([:connections_pool, :max_idle_time], 30_000)
Process.send_after(self(), :idle_close, max_idle)
else
nil
end
{:noreply, %{state | timer: timer}, :hibernate}
end
@impl true
def handle_info(:idle_close, state) do
# Gun monitors the owner process, and will close the connection automatically
# when it's terminated
{:stop, :normal, state}
end
# Gracefully shutdown if the connection got closed without any streams left
@impl true
def handle_info({:gun_down, _pid, _protocol, _reason, []}, state) do
{:stop, :normal, state}
end
# Otherwise, shutdown with an error
@impl true
def handle_info({:gun_down, _pid, _protocol, _reason, _killed_streams} = down_message, state) do
{:stop, {:error, down_message}, state}
end
@impl true
def handle_info({:DOWN, _ref, :process, pid, reason}, state) do
# Sometimes the client is dead before we demonitor it in :remove_client, so the message
# arrives anyway
case state.client_monitors[pid] do
nil ->
{:noreply, state, :hibernate}
_ref ->
:telemetry.execute(
[:pleroma, :connection_pool, :client_death],
%{client_pid: pid, reason: reason},
%{key: state.key}
)
handle_cast({:remove_client, pid}, state)
end
end
# LRFU policy: https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.55.1478
defp crf(time_delta, prev_crf) do
1 + :math.pow(0.5, 0.0001 * time_delta) * prev_crf
end
end

View File

@ -0,0 +1,45 @@
defmodule Pleroma.Gun.ConnectionPool.WorkerSupervisor do
@moduledoc "Supervisor for pool workers. Does not do anything except enforce max connection limit"
use DynamicSupervisor
def start_link(opts) do
DynamicSupervisor.start_link(__MODULE__, opts, name: __MODULE__)
end
def init(_opts) do
DynamicSupervisor.init(
strategy: :one_for_one,
max_children: Pleroma.Config.get([:connections_pool, :max_connections])
)
end
def start_worker(opts, retry \\ false) do
case DynamicSupervisor.start_child(__MODULE__, {Pleroma.Gun.ConnectionPool.Worker, opts}) do
{:error, :max_children} ->
if retry or free_pool() == :error do
:telemetry.execute([:pleroma, :connection_pool, :provision_failure], %{opts: opts})
{:error, :pool_full}
else
start_worker(opts, true)
end
res ->
res
end
end
defp free_pool do
wait_for_reclaimer_finish(Pleroma.Gun.ConnectionPool.Reclaimer.start_monitor())
end
defp wait_for_reclaimer_finish({pid, mon}) do
receive do
{:DOWN, ^mon, :process, ^pid, :no_unused_conns} ->
:error
{:DOWN, ^mon, :process, ^pid, :normal} ->
:ok
end
end
end

View File

@ -3,32 +3,30 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.AdapterHelper do defmodule Pleroma.HTTP.AdapterHelper do
alias Pleroma.HTTP.Connection @moduledoc """
Configure Tesla.Client with default and customized adapter options.
"""
@defaults [pool: :federation]
@type proxy_type() :: :socks4 | :socks5
@type host() :: charlist() | :inet.ip_address()
alias Pleroma.Config
alias Pleroma.HTTP.AdapterHelper
require Logger
@type proxy :: @type proxy ::
{Connection.host(), pos_integer()} {Connection.host(), pos_integer()}
| {Connection.proxy_type(), Connection.host(), pos_integer()} | {Connection.proxy_type(), Connection.host(), pos_integer()}
@callback options(keyword(), URI.t()) :: keyword() @callback options(keyword(), URI.t()) :: keyword()
@callback after_request(keyword()) :: :ok @callback get_conn(URI.t(), keyword()) :: {:ok, term()} | {:error, term()}
@spec options(keyword(), URI.t()) :: keyword()
def options(opts, _uri) do
proxy = Pleroma.Config.get([:http, :proxy_url], nil)
maybe_add_proxy(opts, format_proxy(proxy))
end
@spec maybe_get_conn(URI.t(), keyword()) :: keyword()
def maybe_get_conn(_uri, opts), do: opts
@spec after_request(keyword()) :: :ok
def after_request(_opts), do: :ok
@spec format_proxy(String.t() | tuple() | nil) :: proxy() | nil @spec format_proxy(String.t() | tuple() | nil) :: proxy() | nil
def format_proxy(nil), do: nil def format_proxy(nil), do: nil
def format_proxy(proxy_url) do def format_proxy(proxy_url) do
case Connection.parse_proxy(proxy_url) do case parse_proxy(proxy_url) do
{:ok, host, port} -> {host, port} {:ok, host, port} -> {host, port}
{:ok, type, host, port} -> {type, host, port} {:ok, type, host, port} -> {type, host, port}
_ -> nil _ -> nil
@ -38,4 +36,105 @@ defmodule Pleroma.HTTP.AdapterHelper do
@spec maybe_add_proxy(keyword(), proxy() | nil) :: keyword() @spec maybe_add_proxy(keyword(), proxy() | nil) :: keyword()
def maybe_add_proxy(opts, nil), do: opts def maybe_add_proxy(opts, nil), do: opts
def maybe_add_proxy(opts, proxy), do: Keyword.put_new(opts, :proxy, proxy) def maybe_add_proxy(opts, proxy), do: Keyword.put_new(opts, :proxy, proxy)
@doc """
Merge default connection & adapter options with received ones.
"""
@spec options(URI.t(), keyword()) :: keyword()
def options(%URI{} = uri, opts \\ []) do
@defaults
|> put_timeout()
|> Keyword.merge(opts)
|> adapter_helper().options(uri)
end
# For Hackney, this is the time a connection can stay idle in the pool.
# For Gun, this is the timeout to receive a message from Gun.
defp put_timeout(opts) do
{config_key, default} =
if adapter() == Tesla.Adapter.Gun do
{:pools, Config.get([:pools, :default, :timeout], 5_000)}
else
{:hackney_pools, 10_000}
end
timeout = Config.get([config_key, opts[:pool], :timeout], default)
Keyword.merge(opts, timeout: timeout)
end
def get_conn(uri, opts), do: adapter_helper().get_conn(uri, opts)
defp adapter, do: Application.get_env(:tesla, :adapter)
defp adapter_helper do
case adapter() do
Tesla.Adapter.Gun -> AdapterHelper.Gun
Tesla.Adapter.Hackney -> AdapterHelper.Hackney
_ -> AdapterHelper.Default
end
end
@spec parse_proxy(String.t() | tuple() | nil) ::
{:ok, host(), pos_integer()}
| {:ok, proxy_type(), host(), pos_integer()}
| {:error, atom()}
| nil
def parse_proxy(nil), do: nil
def parse_proxy(proxy) when is_binary(proxy) do
with [host, port] <- String.split(proxy, ":"),
{port, ""} <- Integer.parse(port) do
{:ok, parse_host(host), port}
else
{_, _} ->
Logger.warn("Parsing port failed #{inspect(proxy)}")
{:error, :invalid_proxy_port}
:error ->
Logger.warn("Parsing port failed #{inspect(proxy)}")
{:error, :invalid_proxy_port}
_ ->
Logger.warn("Parsing proxy failed #{inspect(proxy)}")
{:error, :invalid_proxy}
end
end
def parse_proxy(proxy) when is_tuple(proxy) do
with {type, host, port} <- proxy do
{:ok, type, parse_host(host), port}
else
_ ->
Logger.warn("Parsing proxy failed #{inspect(proxy)}")
{:error, :invalid_proxy}
end
end
@spec parse_host(String.t() | atom() | charlist()) :: charlist() | :inet.ip_address()
def parse_host(host) when is_list(host), do: host
def parse_host(host) when is_atom(host), do: to_charlist(host)
def parse_host(host) when is_binary(host) do
host = to_charlist(host)
case :inet.parse_address(host) do
{:error, :einval} -> host
{:ok, ip} -> ip
end
end
@spec format_host(String.t()) :: charlist()
def format_host(host) do
host_charlist = to_charlist(host)
case :inet.parse_address(host_charlist) do
{:error, :einval} ->
:idna.encode(host_charlist)
{:ok, _ip} ->
host_charlist
end
end
end end

View File

@ -0,0 +1,14 @@
defmodule Pleroma.HTTP.AdapterHelper.Default do
alias Pleroma.HTTP.AdapterHelper
@behaviour Pleroma.HTTP.AdapterHelper
@spec options(keyword(), URI.t()) :: keyword()
def options(opts, _uri) do
proxy = Pleroma.Config.get([:http, :proxy_url], nil)
AdapterHelper.maybe_add_proxy(opts, AdapterHelper.format_proxy(proxy))
end
@spec get_conn(URI.t(), keyword()) :: {:ok, keyword()}
def get_conn(_uri, opts), do: {:ok, opts}
end

View File

@ -5,8 +5,8 @@
defmodule Pleroma.HTTP.AdapterHelper.Gun do defmodule Pleroma.HTTP.AdapterHelper.Gun do
@behaviour Pleroma.HTTP.AdapterHelper @behaviour Pleroma.HTTP.AdapterHelper
alias Pleroma.Gun.ConnectionPool
alias Pleroma.HTTP.AdapterHelper alias Pleroma.HTTP.AdapterHelper
alias Pleroma.Pool.Connections
require Logger require Logger
@ -14,7 +14,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
connect_timeout: 5_000, connect_timeout: 5_000,
domain_lookup_timeout: 5_000, domain_lookup_timeout: 5_000,
tls_handshake_timeout: 5_000, tls_handshake_timeout: 5_000,
retry: 1, retry: 0,
retry_timeout: 1000, retry_timeout: 1000,
await_up_timeout: 5_000 await_up_timeout: 5_000
] ]
@ -31,16 +31,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
|> Keyword.merge(config_opts) |> Keyword.merge(config_opts)
|> add_scheme_opts(uri) |> add_scheme_opts(uri)
|> AdapterHelper.maybe_add_proxy(proxy) |> AdapterHelper.maybe_add_proxy(proxy)
|> maybe_get_conn(uri, incoming_opts) |> Keyword.merge(incoming_opts)
end
@spec after_request(keyword()) :: :ok
def after_request(opts) do
if opts[:conn] && opts[:body_as] != :chunks do
Connections.checkout(opts[:conn], self(), :gun_connections)
end
:ok
end end
defp add_scheme_opts(opts, %{scheme: "http"}), do: opts defp add_scheme_opts(opts, %{scheme: "http"}), do: opts
@ -48,30 +39,40 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
defp add_scheme_opts(opts, %{scheme: "https"}) do defp add_scheme_opts(opts, %{scheme: "https"}) do
opts opts
|> Keyword.put(:certificates_verification, true) |> Keyword.put(:certificates_verification, true)
|> Keyword.put(:tls_opts, log_level: :warning)
end end
defp maybe_get_conn(adapter_opts, uri, incoming_opts) do @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} | {:error, atom()}
{receive_conn?, opts} = def get_conn(uri, opts) do
adapter_opts case ConnectionPool.get_conn(uri, opts) do
|> Keyword.merge(incoming_opts) {:ok, conn_pid} -> {:ok, Keyword.merge(opts, conn: conn_pid, close_conn: false)}
|> Keyword.pop(:receive_conn, true) err -> err
if Connections.alive?(:gun_connections) and receive_conn? do
checkin_conn(uri, opts)
else
opts
end end
end end
defp checkin_conn(uri, opts) do @prefix Pleroma.Gun.ConnectionPool
case Connections.checkin(uri, :gun_connections) do def limiter_setup do
nil -> wait = Pleroma.Config.get([:connections_pool, :connection_acquisition_wait])
Task.start(Pleroma.Gun.Conn, :open, [uri, :gun_connections, opts]) retries = Pleroma.Config.get([:connections_pool, :connection_acquisition_retries])
opts
conn when is_pid(conn) -> :pools
Keyword.merge(opts, conn: conn, close_conn: false) |> Pleroma.Config.get([])
end |> Enum.each(fn {name, opts} ->
max_running = Keyword.get(opts, :size, 50)
max_waiting = Keyword.get(opts, :max_waiting, 10)
result =
ConcurrentLimiter.new(:"#{@prefix}.#{name}", max_running, max_waiting,
wait: wait,
max_retries: retries
)
case result do
:ok -> :ok
{:error, :existing} -> :ok
e -> raise e
end
end)
:ok
end end
end end

View File

@ -24,5 +24,6 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do
defp add_scheme_opts(opts, _), do: opts defp add_scheme_opts(opts, _), do: opts
def after_request(_), do: :ok @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()}
def get_conn(_uri, opts), do: {:ok, opts}
end end

View File

@ -1,124 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.Connection do
@moduledoc """
Configure Tesla.Client with default and customized adapter options.
"""
alias Pleroma.Config
alias Pleroma.HTTP.AdapterHelper
require Logger
@defaults [pool: :federation]
@type ip_address :: ipv4_address() | ipv6_address()
@type ipv4_address :: {0..255, 0..255, 0..255, 0..255}
@type ipv6_address ::
{0..65_535, 0..65_535, 0..65_535, 0..65_535, 0..65_535, 0..65_535, 0..65_535, 0..65_535}
@type proxy_type() :: :socks4 | :socks5
@type host() :: charlist() | ip_address()
@doc """
Merge default connection & adapter options with received ones.
"""
@spec options(URI.t(), keyword()) :: keyword()
def options(%URI{} = uri, opts \\ []) do
@defaults
|> pool_timeout()
|> Keyword.merge(opts)
|> adapter_helper().options(uri)
end
defp pool_timeout(opts) do
{config_key, default} =
if adapter() == Tesla.Adapter.Gun do
{:pools, Config.get([:pools, :default, :timeout])}
else
{:hackney_pools, 10_000}
end
timeout = Config.get([config_key, opts[:pool], :timeout], default)
Keyword.merge(opts, timeout: timeout)
end
@spec after_request(keyword()) :: :ok
def after_request(opts), do: adapter_helper().after_request(opts)
defp adapter, do: Application.get_env(:tesla, :adapter)
defp adapter_helper do
case adapter() do
Tesla.Adapter.Gun -> AdapterHelper.Gun
Tesla.Adapter.Hackney -> AdapterHelper.Hackney
_ -> AdapterHelper
end
end
@spec parse_proxy(String.t() | tuple() | nil) ::
{:ok, host(), pos_integer()}
| {:ok, proxy_type(), host(), pos_integer()}
| {:error, atom()}
| nil
def parse_proxy(nil), do: nil
def parse_proxy(proxy) when is_binary(proxy) do
with [host, port] <- String.split(proxy, ":"),
{port, ""} <- Integer.parse(port) do
{:ok, parse_host(host), port}
else
{_, _} ->
Logger.warn("Parsing port failed #{inspect(proxy)}")
{:error, :invalid_proxy_port}
:error ->
Logger.warn("Parsing port failed #{inspect(proxy)}")
{:error, :invalid_proxy_port}
_ ->
Logger.warn("Parsing proxy failed #{inspect(proxy)}")
{:error, :invalid_proxy}
end
end
def parse_proxy(proxy) when is_tuple(proxy) do
with {type, host, port} <- proxy do
{:ok, type, parse_host(host), port}
else
_ ->
Logger.warn("Parsing proxy failed #{inspect(proxy)}")
{:error, :invalid_proxy}
end
end
@spec parse_host(String.t() | atom() | charlist()) :: charlist() | ip_address()
def parse_host(host) when is_list(host), do: host
def parse_host(host) when is_atom(host), do: to_charlist(host)
def parse_host(host) when is_binary(host) do
host = to_charlist(host)
case :inet.parse_address(host) do
{:error, :einval} -> host
{:ok, ip} -> ip
end
end
@spec format_host(String.t()) :: charlist()
def format_host(host) do
host_charlist = to_charlist(host)
case :inet.parse_address(host_charlist) do
{:error, :einval} ->
:idna.encode(host_charlist)
{:ok, _ip} ->
host_charlist
end
end
end

View File

@ -7,7 +7,7 @@ defmodule Pleroma.HTTP do
Wrapper for `Tesla.request/2`. Wrapper for `Tesla.request/2`.
""" """
alias Pleroma.HTTP.Connection alias Pleroma.HTTP.AdapterHelper
alias Pleroma.HTTP.Request alias Pleroma.HTTP.Request
alias Pleroma.HTTP.RequestBuilder, as: Builder alias Pleroma.HTTP.RequestBuilder, as: Builder
alias Tesla.Client alias Tesla.Client
@ -60,49 +60,30 @@ defmodule Pleroma.HTTP do
{:ok, Env.t()} | {:error, any()} {:ok, Env.t()} | {:error, any()}
def request(method, url, body, headers, options) when is_binary(url) do def request(method, url, body, headers, options) when is_binary(url) do
uri = URI.parse(url) uri = URI.parse(url)
adapter_opts = Connection.options(uri, options[:adapter] || []) adapter_opts = AdapterHelper.options(uri, options[:adapter] || [])
options = put_in(options[:adapter], adapter_opts)
params = options[:params] || []
request = build_request(method, headers, options, url, body, params)
adapter = Application.get_env(:tesla, :adapter) case AdapterHelper.get_conn(uri, adapter_opts) do
client = Tesla.client([Tesla.Middleware.FollowRedirects], adapter) {:ok, adapter_opts} ->
options = put_in(options[:adapter], adapter_opts)
params = options[:params] || []
request = build_request(method, headers, options, url, body, params)
pid = Process.whereis(adapter_opts[:pool]) adapter = Application.get_env(:tesla, :adapter)
pool_alive? = client = Tesla.client(adapter_middlewares(adapter), adapter)
if adapter == Tesla.Adapter.Gun && pid do
Process.alive?(pid)
else
false
end
request_opts = maybe_limit(
adapter_opts fn ->
|> Enum.into(%{}) request(client, request)
|> Map.put(:env, Pleroma.Config.get([:env])) end,
|> Map.put(:pool_alive?, pool_alive?) adapter,
adapter_opts
)
response = request(client, request, request_opts) # Connection release is handled in a custom FollowRedirects middleware
err ->
Connection.after_request(adapter_opts) err
end
response
end
@spec request(Client.t(), keyword(), map()) :: {:ok, Env.t()} | {:error, any()}
def request(%Client{} = client, request, %{env: :test}), do: request(client, request)
def request(%Client{} = client, request, %{body_as: :chunks}), do: request(client, request)
def request(%Client{} = client, request, %{pool_alive?: false}), do: request(client, request)
def request(%Client{} = client, request, %{pool: pool, timeout: timeout}) do
:poolboy.transaction(
pool,
&Pleroma.Pool.Request.execute(&1, client, request, timeout),
timeout
)
end end
@spec request(Client.t(), keyword()) :: {:ok, Env.t()} | {:error, any()} @spec request(Client.t(), keyword()) :: {:ok, Env.t()} | {:error, any()}
@ -118,4 +99,19 @@ defmodule Pleroma.HTTP do
|> Builder.add_param(:query, :query, params) |> Builder.add_param(:query, :query, params)
|> Builder.convert_to_keyword() |> Builder.convert_to_keyword()
end end
@prefix Pleroma.Gun.ConnectionPool
defp maybe_limit(fun, Tesla.Adapter.Gun, opts) do
ConcurrentLimiter.limit(:"#{@prefix}.#{opts[:pool] || :default}", fun)
end
defp maybe_limit(fun, _, _) do
fun.()
end
defp adapter_middlewares(Tesla.Adapter.Gun) do
[Pleroma.HTTP.Middleware.FollowRedirects]
end
defp adapter_middlewares(_), do: []
end end

View File

@ -571,10 +571,7 @@ defmodule Pleroma.Notification do
[ [
:self, :self,
:invisible, :invisible,
:followers, :block_from_strangers,
:follows,
:non_followers,
:non_follows,
:recently_followed, :recently_followed,
:filtered :filtered
] ]
@ -595,45 +592,15 @@ defmodule Pleroma.Notification do
end end
def skip?( def skip?(
:followers, :block_from_strangers,
%Activity{} = activity, %Activity{} = activity,
%User{notification_settings: %{followers: false}} = user %User{notification_settings: %{block_from_strangers: true}} = user
) do
actor = activity.data["actor"]
follower = User.get_cached_by_ap_id(actor)
User.following?(follower, user)
end
def skip?(
:non_followers,
%Activity{} = activity,
%User{notification_settings: %{non_followers: false}} = user
) do ) do
actor = activity.data["actor"] actor = activity.data["actor"]
follower = User.get_cached_by_ap_id(actor) follower = User.get_cached_by_ap_id(actor)
!User.following?(follower, user) !User.following?(follower, user)
end end
def skip?(
:follows,
%Activity{} = activity,
%User{notification_settings: %{follows: false}} = user
) do
actor = activity.data["actor"]
followed = User.get_cached_by_ap_id(actor)
User.following?(user, followed)
end
def skip?(
:non_follows,
%Activity{} = activity,
%User{notification_settings: %{non_follows: false}} = user
) do
actor = activity.data["actor"]
followed = User.get_cached_by_ap_id(actor)
!User.following?(user, followed)
end
# To do: consider defining recency in hours and checking FollowingRelationship with a single SQL # To do: consider defining recency in hours and checking FollowingRelationship with a single SQL
def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity, %User{} = user) do def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity, %User{} = user) do
actor = activity.data["actor"] actor = activity.data["actor"]

View File

@ -4,6 +4,9 @@
defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do
import Plug.Conn import Plug.Conn
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter
alias Pleroma.User alias Pleroma.User
def init(options) do def init(options) do
@ -11,7 +14,10 @@ defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do
end end
def secret_token do def secret_token do
Pleroma.Config.get(:admin_token) case Pleroma.Config.get(:admin_token) do
blank when blank in [nil, ""] -> nil
token -> token
end
end end
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
@ -26,9 +32,9 @@ defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do
def authenticate(%{params: %{"admin_token" => admin_token}} = conn) do def authenticate(%{params: %{"admin_token" => admin_token}} = conn) do
if admin_token == secret_token() do if admin_token == secret_token() do
assign(conn, :user, %User{is_admin: true}) assign_admin_user(conn)
else else
conn handle_bad_token(conn)
end end
end end
@ -36,8 +42,19 @@ defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do
token = secret_token() token = secret_token()
case get_req_header(conn, "x-admin-token") do case get_req_header(conn, "x-admin-token") do
[^token] -> assign(conn, :user, %User{is_admin: true}) blank when blank in [[], [""]] -> conn
_ -> conn [^token] -> assign_admin_user(conn)
_ -> handle_bad_token(conn)
end end
end end
defp assign_admin_user(conn) do
conn
|> assign(:user, %User{is_admin: true})
|> OAuthScopesPlug.skip_plug()
end
defp handle_bad_token(conn) do
RateLimiter.call(conn, name: :authentication)
end
end end

View File

@ -108,31 +108,48 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|> :erlang.iolist_to_binary() |> :erlang.iolist_to_binary()
end end
defp build_csp_from_whitelist([], acc), do: acc
defp build_csp_from_whitelist([last], acc) do
[build_csp_param_from_whitelist(last) | acc]
end
defp build_csp_from_whitelist([head | tail], acc) do
build_csp_from_whitelist(tail, [[?\s, build_csp_param_from_whitelist(head)] | acc])
end
# TODO: use `build_csp_param/1` after removing support bare domains for media proxy whitelist
defp build_csp_param_from_whitelist("http" <> _ = url) do
build_csp_param(url)
end
defp build_csp_param_from_whitelist(url), do: url
defp build_csp_multimedia_source_list do defp build_csp_multimedia_source_list do
media_proxy_whitelist = media_proxy_whitelist =
Enum.reduce(Config.get([:media_proxy, :whitelist]), [], fn host, acc -> [:media_proxy, :whitelist]
add_source(acc, host) |> Config.get()
end) |> build_csp_from_whitelist([])
media_proxy_base_url = build_csp_param(Config.get([:media_proxy, :base_url]))
upload_base_url = build_csp_param(Config.get([Pleroma.Upload, :base_url]))
s3_endpoint = build_csp_param(Config.get([Pleroma.Uploaders.S3, :public_endpoint]))
captcha_method = Config.get([Pleroma.Captcha, :method]) captcha_method = Config.get([Pleroma.Captcha, :method])
captcha_endpoint = Config.get([captcha_method, :endpoint])
captcha_endpoint = build_csp_param(Config.get([captcha_method, :endpoint])) base_endpoints =
[
[:media_proxy, :base_url],
[Pleroma.Upload, :base_url],
[Pleroma.Uploaders.S3, :public_endpoint]
]
|> Enum.map(&Config.get/1)
[] [captcha_endpoint | base_endpoints]
|> add_source(media_proxy_base_url) |> Enum.map(&build_csp_param/1)
|> add_source(upload_base_url) |> Enum.reduce([], &add_source(&2, &1))
|> add_source(s3_endpoint)
|> add_source(media_proxy_whitelist) |> add_source(media_proxy_whitelist)
|> add_source(captcha_endpoint)
end end
defp add_source(iodata, nil), do: iodata defp add_source(iodata, nil), do: iodata
defp add_source(iodata, []), do: iodata
defp add_source(iodata, source), do: [[?\s, source] | iodata] defp add_source(iodata, source), do: [[?\s, source] | iodata]
defp add_csp_param(csp_iodata, nil), do: csp_iodata defp add_csp_param(csp_iodata, nil), do: csp_iodata

View File

@ -7,37 +7,18 @@ defmodule Pleroma.Plugs.UserIsAdminPlug do
import Plug.Conn import Plug.Conn
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.OAuth
def init(options) do def init(options) do
options options
end end
def call(%{assigns: %{user: %User{is_admin: true}} = assigns} = conn, _) do def call(%{assigns: %{user: %User{is_admin: true}}} = conn, _) do
token = assigns[:token] conn
cond do
not Pleroma.Config.enforce_oauth_admin_scope_usage?() ->
conn
token && OAuth.Scopes.contains_admin_scopes?(token.scopes) ->
# Note: checking for _any_ admin scope presence, not necessarily fitting requested action.
# Thus, controller must explicitly invoke OAuthScopesPlug to verify scope requirements.
# Admin might opt out of admin scope for some apps to block any admin actions from them.
conn
true ->
fail(conn)
end
end end
def call(conn, _) do def call(conn, _) do
fail(conn)
end
defp fail(conn) do
conn conn
|> render_error(:forbidden, "User is not an admin or OAuth admin scope is not granted.") |> render_error(:forbidden, "User is not an admin.")
|> halt() |> halt()
end end
end end

View File

@ -1,283 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Pool.Connections do
use GenServer
alias Pleroma.Config
alias Pleroma.Gun
require Logger
@type domain :: String.t()
@type conn :: Pleroma.Gun.Conn.t()
@type t :: %__MODULE__{
conns: %{domain() => conn()},
opts: keyword()
}
defstruct conns: %{}, opts: []
@spec start_link({atom(), keyword()}) :: {:ok, pid()}
def start_link({name, opts}) do
GenServer.start_link(__MODULE__, opts, name: name)
end
@impl true
def init(opts), do: {:ok, %__MODULE__{conns: %{}, opts: opts}}
@spec checkin(String.t() | URI.t(), atom()) :: pid() | nil
def checkin(url, name)
def checkin(url, name) when is_binary(url), do: checkin(URI.parse(url), name)
def checkin(%URI{} = uri, name) do
timeout = Config.get([:connections_pool, :checkin_timeout], 250)
GenServer.call(name, {:checkin, uri}, timeout)
end
@spec alive?(atom()) :: boolean()
def alive?(name) do
if pid = Process.whereis(name) do
Process.alive?(pid)
else
false
end
end
@spec get_state(atom()) :: t()
def get_state(name) do
GenServer.call(name, :state)
end
@spec count(atom()) :: pos_integer()
def count(name) do
GenServer.call(name, :count)
end
@spec get_unused_conns(atom()) :: [{domain(), conn()}]
def get_unused_conns(name) do
GenServer.call(name, :unused_conns)
end
@spec checkout(pid(), pid(), atom()) :: :ok
def checkout(conn, pid, name) do
GenServer.cast(name, {:checkout, conn, pid})
end
@spec add_conn(atom(), String.t(), Pleroma.Gun.Conn.t()) :: :ok
def add_conn(name, key, conn) do
GenServer.cast(name, {:add_conn, key, conn})
end
@spec remove_conn(atom(), String.t()) :: :ok
def remove_conn(name, key) do
GenServer.cast(name, {:remove_conn, key})
end
@impl true
def handle_cast({:add_conn, key, conn}, state) do
state = put_in(state.conns[key], conn)
Process.monitor(conn.conn)
{:noreply, state}
end
@impl true
def handle_cast({:checkout, conn_pid, pid}, state) do
state =
with true <- Process.alive?(conn_pid),
{key, conn} <- find_conn(state.conns, conn_pid),
used_by <- List.keydelete(conn.used_by, pid, 0) do
conn_state = if used_by == [], do: :idle, else: conn.conn_state
put_in(state.conns[key], %{conn | conn_state: conn_state, used_by: used_by})
else
false ->
Logger.debug("checkout for closed conn #{inspect(conn_pid)}")
state
nil ->
Logger.debug("checkout for alive conn #{inspect(conn_pid)}, but is not in state")
state
end
{:noreply, state}
end
@impl true
def handle_cast({:remove_conn, key}, state) do
state = put_in(state.conns, Map.delete(state.conns, key))
{:noreply, state}
end
@impl true
def handle_call({:checkin, uri}, from, state) do
key = "#{uri.scheme}:#{uri.host}:#{uri.port}"
case state.conns[key] do
%{conn: pid, gun_state: :up} = conn ->
time = :os.system_time(:second)
last_reference = time - conn.last_reference
crf = crf(last_reference, 100, conn.crf)
state =
put_in(state.conns[key], %{
conn
| last_reference: time,
crf: crf,
conn_state: :active,
used_by: [from | conn.used_by]
})
{:reply, pid, state}
%{gun_state: :down} ->
{:reply, nil, state}
nil ->
{:reply, nil, state}
end
end
@impl true
def handle_call(:state, _from, state), do: {:reply, state, state}
@impl true
def handle_call(:count, _from, state) do
{:reply, Enum.count(state.conns), state}
end
@impl true
def handle_call(:unused_conns, _from, state) do
unused_conns =
state.conns
|> Enum.filter(&filter_conns/1)
|> Enum.sort(&sort_conns/2)
{:reply, unused_conns, state}
end
defp filter_conns({_, %{conn_state: :idle, used_by: []}}), do: true
defp filter_conns(_), do: false
defp sort_conns({_, c1}, {_, c2}) do
c1.crf <= c2.crf and c1.last_reference <= c2.last_reference
end
@impl true
def handle_info({:gun_up, conn_pid, _protocol}, state) do
%{origin_host: host, origin_scheme: scheme, origin_port: port} = Gun.info(conn_pid)
host =
case :inet.ntoa(host) do
{:error, :einval} -> host
ip -> ip
end
key = "#{scheme}:#{host}:#{port}"
state =
with {key, conn} <- find_conn(state.conns, conn_pid, key),
{true, key} <- {Process.alive?(conn_pid), key} do
put_in(state.conns[key], %{
conn
| gun_state: :up,
conn_state: :active,
retries: 0
})
else
{false, key} ->
put_in(
state.conns,
Map.delete(state.conns, key)
)
nil ->
:ok = Gun.close(conn_pid)
state
end
{:noreply, state}
end
@impl true
def handle_info({:gun_down, conn_pid, _protocol, _reason, _killed}, state) do
retries = Config.get([:connections_pool, :retry], 1)
# we can't get info on this pid, because pid is dead
state =
with {key, conn} <- find_conn(state.conns, conn_pid),
{true, key} <- {Process.alive?(conn_pid), key} do
if conn.retries == retries do
:ok = Gun.close(conn.conn)
put_in(
state.conns,
Map.delete(state.conns, key)
)
else
put_in(state.conns[key], %{
conn
| gun_state: :down,
retries: conn.retries + 1
})
end
else
{false, key} ->
put_in(
state.conns,
Map.delete(state.conns, key)
)
nil ->
Logger.debug(":gun_down for conn which isn't found in state")
state
end
{:noreply, state}
end
@impl true
def handle_info({:DOWN, _ref, :process, conn_pid, reason}, state) do
Logger.debug("received DOWN message for #{inspect(conn_pid)} reason -> #{inspect(reason)}")
state =
with {key, conn} <- find_conn(state.conns, conn_pid) do
Enum.each(conn.used_by, fn {pid, _ref} ->
Process.exit(pid, reason)
end)
put_in(
state.conns,
Map.delete(state.conns, key)
)
else
nil ->
Logger.debug(":DOWN for conn which isn't found in state")
state
end
{:noreply, state}
end
defp find_conn(conns, conn_pid) do
Enum.find(conns, fn {_key, conn} ->
conn.conn == conn_pid
end)
end
defp find_conn(conns, conn_pid, conn_key) do
Enum.find(conns, fn {key, conn} ->
key == conn_key and conn.conn == conn_pid
end)
end
def crf(current, steps, crf) do
1 + :math.pow(0.5, current / steps) * crf
end
end

View File

@ -1,22 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Pool do
def child_spec(opts) do
poolboy_opts =
opts
|> Keyword.put(:worker_module, Pleroma.Pool.Request)
|> Keyword.put(:name, {:local, opts[:name]})
|> Keyword.put(:size, opts[:size])
|> Keyword.put(:max_overflow, opts[:max_overflow])
%{
id: opts[:id] || {__MODULE__, make_ref()},
start: {:poolboy, :start_link, [poolboy_opts, [name: opts[:name]]]},
restart: :permanent,
shutdown: 5000,
type: :worker
}
end
end

View File

@ -1,65 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Pool.Request do
use GenServer
require Logger
def start_link(args) do
GenServer.start_link(__MODULE__, args)
end
@impl true
def init(_), do: {:ok, []}
@spec execute(pid() | atom(), Tesla.Client.t(), keyword(), pos_integer()) ::
{:ok, Tesla.Env.t()} | {:error, any()}
def execute(pid, client, request, timeout) do
GenServer.call(pid, {:execute, client, request}, timeout)
end
@impl true
def handle_call({:execute, client, request}, _from, state) do
response = Pleroma.HTTP.request(client, request)
{:reply, response, state}
end
@impl true
def handle_info({:gun_data, _conn, _stream, _, _}, state) do
{:noreply, state}
end
@impl true
def handle_info({:gun_up, _conn, _protocol}, state) do
{:noreply, state}
end
@impl true
def handle_info({:gun_down, _conn, _protocol, _reason, _killed}, state) do
{:noreply, state}
end
@impl true
def handle_info({:gun_error, _conn, _stream, _error}, state) do
{:noreply, state}
end
@impl true
def handle_info({:gun_push, _conn, _stream, _new_stream, _method, _uri, _headers}, state) do
{:noreply, state}
end
@impl true
def handle_info({:gun_response, _conn, _stream, _, _status, _headers}, state) do
{:noreply, state}
end
@impl true
def handle_info(msg, state) do
Logger.warn("Received unexpected message #{inspect(__MODULE__)} #{inspect(msg)}")
{:noreply, state}
end
end

View File

@ -1,42 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Pool.Supervisor do
use Supervisor
alias Pleroma.Config
alias Pleroma.Pool
def start_link(args) do
Supervisor.start_link(__MODULE__, args, name: __MODULE__)
end
def init(_) do
conns_child = %{
id: Pool.Connections,
start:
{Pool.Connections, :start_link, [{:gun_connections, Config.get([:connections_pool])}]}
}
Supervisor.init([conns_child | pools()], strategy: :one_for_one)
end
defp pools do
pools = Config.get(:pools)
pools =
if Config.get([Pleroma.Upload, :proxy_remote]) == false do
Keyword.delete(pools, :upload)
else
pools
end
for {pool_name, pool_opts} <- pools do
pool_opts
|> Keyword.put(:id, {Pool, pool_name})
|> Keyword.put(:name, pool_name)
|> Pool.child_spec()
end
end
end

View File

@ -48,7 +48,7 @@ defmodule Pleroma.ReverseProxy.Client.Tesla do
# if there were redirects we need to checkout old conn # if there were redirects we need to checkout old conn
conn = opts[:old_conn] || opts[:conn] conn = opts[:old_conn] || opts[:conn]
if conn, do: :ok = Pleroma.Pool.Connections.checkout(conn, self(), :gun_connections) if conn, do: :ok = Pleroma.Gun.ConnectionPool.release_conn(conn)
:done :done
end end

View File

@ -0,0 +1,76 @@
defmodule Pleroma.Telemetry.Logger do
@moduledoc "Transforms Pleroma telemetry events to logs"
require Logger
@events [
[:pleroma, :connection_pool, :reclaim, :start],
[:pleroma, :connection_pool, :reclaim, :stop],
[:pleroma, :connection_pool, :provision_failure],
[:pleroma, :connection_pool, :client_death]
]
def attach do
:telemetry.attach_many("pleroma-logger", @events, &handle_event/4, [])
end
# Passing anonymous functions instead of strings to logger is intentional,
# that way strings won't be concatenated if the message is going to be thrown
# out anyway due to higher log level configured
def handle_event(
[:pleroma, :connection_pool, :reclaim, :start],
_,
%{max_connections: max_connections, reclaim_max: reclaim_max},
_
) do
Logger.debug(fn ->
"Connection pool is exhausted (reached #{max_connections} connections). Starting idle connection cleanup to reclaim as much as #{
reclaim_max
} connections"
end)
end
def handle_event(
[:pleroma, :connection_pool, :reclaim, :stop],
%{reclaimed_count: 0},
_,
_
) do
Logger.error(fn ->
"Connection pool failed to reclaim any connections due to all of them being in use. It will have to drop requests for opening connections to new hosts"
end)
end
def handle_event(
[:pleroma, :connection_pool, :reclaim, :stop],
%{reclaimed_count: reclaimed_count},
_,
_
) do
Logger.debug(fn -> "Connection pool cleaned up #{reclaimed_count} idle connections" end)
end
def handle_event(
[:pleroma, :connection_pool, :provision_failure],
%{opts: [key | _]},
_,
_
) do
Logger.error(fn ->
"Connection pool had to refuse opening a connection to #{key} due to connection limit exhaustion"
end)
end
def handle_event(
[:pleroma, :connection_pool, :client_death],
%{client_pid: client_pid, reason: reason},
%{key: key},
_
) do
Logger.warn(fn ->
"Pool worker for #{key}: Client #{inspect(client_pid)} died before releasing the connection with #{
inspect(reason)
}"
end)
end
end

View File

@ -0,0 +1,110 @@
# Pleroma: A lightweight social networking server
# Copyright © 2015-2020 Tymon Tobolski <https://github.com/teamon/tesla/blob/master/lib/tesla/middleware/follow_redirects.ex>
# Copyright © 2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.Middleware.FollowRedirects do
@moduledoc """
Pool-aware version of https://github.com/teamon/tesla/blob/master/lib/tesla/middleware/follow_redirects.ex
Follow 3xx redirects
## Options
- `:max_redirects` - limit number of redirects (default: `5`)
"""
alias Pleroma.Gun.ConnectionPool
@behaviour Tesla.Middleware
@max_redirects 5
@redirect_statuses [301, 302, 303, 307, 308]
@impl Tesla.Middleware
def call(env, next, opts \\ []) do
max = Keyword.get(opts, :max_redirects, @max_redirects)
redirect(env, next, max)
end
defp redirect(env, next, left) do
opts = env.opts[:adapter]
case Tesla.run(env, next) do
{:ok, %{status: status} = res} when status in @redirect_statuses and left > 0 ->
release_conn(opts)
case Tesla.get_header(res, "location") do
nil ->
{:ok, res}
location ->
location = parse_location(location, res)
case get_conn(location, opts) do
{:ok, opts} ->
%{env | opts: Keyword.put(env.opts, :adapter, opts)}
|> new_request(res.status, location)
|> redirect(next, left - 1)
e ->
e
end
end
{:ok, %{status: status}} when status in @redirect_statuses ->
release_conn(opts)
{:error, {__MODULE__, :too_many_redirects}}
{:error, _} = e ->
release_conn(opts)
e
other ->
unless opts[:body_as] == :chunks do
release_conn(opts)
end
other
end
end
defp get_conn(location, opts) do
uri = URI.parse(location)
case ConnectionPool.get_conn(uri, opts) do
{:ok, conn} ->
{:ok, Keyword.merge(opts, conn: conn)}
e ->
e
end
end
defp release_conn(opts) do
ConnectionPool.release_conn(opts[:conn])
end
# The 303 (See Other) redirect was added in HTTP/1.1 to indicate that the originally
# requested resource is not available, however a related resource (or another redirect)
# available via GET is available at the specified location.
# https://tools.ietf.org/html/rfc7231#section-6.4.4
defp new_request(env, 303, location), do: %{env | url: location, method: :get, query: []}
# The 307 (Temporary Redirect) status code indicates that the target
# resource resides temporarily under a different URI and the user agent
# MUST NOT change the request method (...)
# https://tools.ietf.org/html/rfc7231#section-6.4.7
defp new_request(env, 307, location), do: %{env | url: location}
defp new_request(env, _, location), do: %{env | url: location, query: []}
defp parse_location("https://" <> _rest = location, _env), do: location
defp parse_location("http://" <> _rest = location, _env), do: location
defp parse_location(location, env) do
env.url
|> URI.parse()
|> URI.merge(location)
|> URI.to_string()
end
end

View File

@ -530,11 +530,21 @@ defmodule Pleroma.User do
end end
defp put_emoji(changeset) do defp put_emoji(changeset) do
bio = get_change(changeset, :bio) emojified_fields = [:bio, :name, :raw_fields]
name = get_change(changeset, :name)
if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))
emoji = Map.merge(bio, name)
emoji =
changeset
|> get_field(:raw_fields)
|> Enum.reduce(emoji, fn x, acc ->
Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
end)
if bio || name do
emoji = Map.merge(Emoji.Formatter.get_emoji_map(bio), Emoji.Formatter.get_emoji_map(name))
put_change(changeset, :emoji, emoji) put_change(changeset, :emoji, emoji)
else else
changeset changeset

View File

@ -10,21 +10,15 @@ defmodule Pleroma.User.NotificationSetting do
@primary_key false @primary_key false
embedded_schema do embedded_schema do
field(:followers, :boolean, default: true) field(:block_from_strangers, :boolean, default: false)
field(:follows, :boolean, default: true) field(:hide_notification_contents, :boolean, default: false)
field(:non_follows, :boolean, default: true)
field(:non_followers, :boolean, default: true)
field(:privacy_option, :boolean, default: false)
end end
def changeset(schema, params) do def changeset(schema, params) do
schema schema
|> cast(prepare_attrs(params), [ |> cast(prepare_attrs(params), [
:followers, :block_from_strangers,
:follows, :hide_notification_contents
:non_follows,
:non_followers,
:privacy_option
]) ])
end end

View File

@ -60,7 +60,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
if score < 0.8 do if score < 0.8 do
{:ok, message} {:ok, message}
else else
{:reject, nil} {:reject, "[AntiFollowbotPolicy] Scored #{actor_id} as #{score}"}
end end
end end

View File

@ -39,14 +39,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
{:ok, message} {:ok, message}
{:old_user, false} -> {:old_user, false} ->
{:reject, nil} {:reject, "[AntiLinkSpamPolicy] User has no posts nor followers"}
{:error, _} -> {:error, _} ->
{:reject, nil} {:reject, "[AntiLinkSpamPolicy] Failed to get or fetch user by ap_id"}
e -> e ->
Logger.warn("[MRF anti-link-spam] WTF: unhandled error #{inspect(e)}") {:reject, "[AntiLinkSpamPolicy] Unhandled error #{inspect(e)}"}
{:reject, nil}
end end
end end

View File

@ -43,7 +43,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
defp reject_message(message, threshold) when threshold > 0 do defp reject_message(message, threshold) when threshold > 0 do
with {_, recipients} <- get_recipient_count(message) do with {_, recipients} <- get_recipient_count(message) do
if recipients > threshold do if recipients > threshold do
{:reject, nil} {:reject, "[HellthreadPolicy] #{recipients} recipients is over the limit of #{threshold}"}
else else
{:ok, message} {:ok, message}
end end
@ -87,7 +87,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
{:ok, message} <- delist_message(message, delist_threshold) do {:ok, message} <- delist_message(message, delist_threshold) do
{:ok, message} {:ok, message}
else else
_e -> {:reject, nil} e -> e
end end
end end

View File

@ -24,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern -> if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern ->
string_matches?(content, pattern) or string_matches?(summary, pattern) string_matches?(content, pattern) or string_matches?(summary, pattern)
end) do end) do
{:reject, nil} {:reject, "[KeywordPolicy] Matches with rejected keyword"}
else else
{:ok, message} {:ok, message}
end end
@ -89,8 +89,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
{:ok, message} <- check_replace(message) do {:ok, message} <- check_replace(message) do
{:ok, message} {:ok, message}
else else
_e -> {:reject, nil} -> {:reject, "[KeywordPolicy] "}
{:reject, nil} {:reject, _} = e -> e
_e -> {:reject, "[KeywordPolicy] "}
end end
end end

View File

@ -12,8 +12,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do
reject_actors = Pleroma.Config.get([:mrf_mention, :actors], []) reject_actors = Pleroma.Config.get([:mrf_mention, :actors], [])
recipients = (message["to"] || []) ++ (message["cc"] || []) recipients = (message["to"] || []) ++ (message["cc"] || [])
if Enum.any?(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do if rejected_mention =
{:reject, nil} Enum.find(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do
{:reject, "[MentionPolicy] Rejected for mention of #{rejected_mention}"}
else else
{:ok, message} {:ok, message}
end end

View File

@ -28,7 +28,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
defp check_reject(message, actions) do defp check_reject(message, actions) do
if :reject in actions do if :reject in actions do
{:reject, nil} {:reject, "[ObjectAgePolicy]"}
else else
{:ok, message} {:ok, message}
end end
@ -47,9 +47,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
{:ok, message} {:ok, message}
else else
# Unhandleable error: somebody is messing around, just drop the message.
_e -> _e ->
{:reject, nil} {:reject, "[ObjectAgePolicy] Unhandled error"}
end end
else else
{:ok, message} {:ok, message}
@ -69,9 +68,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
{:ok, message} {:ok, message}
else else
# Unhandleable error: somebody is messing around, just drop the message.
_e -> _e ->
{:reject, nil} {:reject, "[ObjectAgePolicy] Unhandled error"}
end end
else else
{:ok, message} {:ok, message}

View File

@ -38,7 +38,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
{:ok, object} {:ok, object}
true -> true ->
{:reject, nil} {:reject, "[RejectNonPublic] visibility: #{visibility}"}
end end
end end

View File

@ -21,7 +21,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
accepts == [] -> {:ok, object} accepts == [] -> {:ok, object}
actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object} actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
MRF.subdomain_match?(accepts, actor_host) -> {:ok, object} MRF.subdomain_match?(accepts, actor_host) -> {:ok, object}
true -> {:reject, nil} true -> {:reject, "[SimplePolicy] host not in accept list"}
end end
end end
@ -31,7 +31,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
if MRF.subdomain_match?(rejects, actor_host) do if MRF.subdomain_match?(rejects, actor_host) do
{:reject, nil} {:reject, "[SimplePolicy] host in reject list"}
else else
{:ok, object} {:ok, object}
end end
@ -114,7 +114,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
if MRF.subdomain_match?(report_removal, actor_host) do if MRF.subdomain_match?(report_removal, actor_host) do
{:reject, nil} {:reject, "[SimplePolicy] host in report_removal list"}
else else
{:ok, object} {:ok, object}
end end
@ -159,7 +159,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
if MRF.subdomain_match?(reject_deletes, actor_host) do if MRF.subdomain_match?(reject_deletes, actor_host) do
{:reject, nil} {:reject, "[SimplePolicy] host in reject_deletes list"}
else else
{:ok, object} {:ok, object}
end end
@ -177,7 +177,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
{:ok, object} <- check_report_removal(actor_info, object) do {:ok, object} <- check_report_removal(actor_info, object) do
{:ok, object} {:ok, object}
else else
_e -> {:reject, nil} {:reject, nil} -> {:reject, "[SimplePolicy]"}
{:reject, _} = e -> e
_ -> {:reject, "[SimplePolicy]"}
end end
end end
@ -191,7 +193,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
{:ok, object} <- check_banner_removal(actor_info, object) do {:ok, object} <- check_banner_removal(actor_info, object) do
{:ok, object} {:ok, object}
else else
_e -> {:reject, nil} {:reject, nil} -> {:reject, "[SimplePolicy]"}
{:reject, _} = e -> e
_ -> {:reject, "[SimplePolicy]"}
end end
end end

View File

@ -134,12 +134,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
if user.local == true do if user.local == true do
{:ok, message} {:ok, message}
else else
{:reject, nil} {:reject,
"[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-remote-subscription"}
end end
end end
defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow"}), defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow", "actor" => actor}),
do: {:reject, nil} do: {:reject, "[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-any-subscription"}
defp process_tag(_, message), do: {:ok, message} defp process_tag(_, message), do: {:ok, message}

View File

@ -14,7 +14,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
if actor in allow_list do if actor in allow_list do
{:ok, object} {:ok, object}
else else
{:reject, nil} {:reject, "[UserAllowListPolicy] #{actor} not in the list"}
end end
end end

View File

@ -11,22 +11,26 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
with {:ok, _} <- filter(child_message) do with {:ok, _} <- filter(child_message) do
{:ok, message} {:ok, message}
else else
{:reject, nil} -> {:reject, _} = e -> e
{:reject, nil}
end end
end end
def filter(%{"type" => message_type} = message) do def filter(%{"type" => message_type} = message) do
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]), with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]), rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
true <- {_, true} <-
Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type), {:accepted,
false <- Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type)},
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type), {_, false} <-
{:rejected,
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type)},
{:ok, _} <- filter(message["object"]) do {:ok, _} <- filter(message["object"]) do
{:ok, message} {:ok, message}
else else
_ -> {:reject, nil} {:reject, _} = e -> e
{:accepted, _} -> {:reject, "[VocabularyPolicy] #{message_type} not in accept list"}
{:rejected, _} -> {:reject, "[VocabularyPolicy] #{message_type} in reject list"}
_ -> {:reject, "[VocabularyPolicy]"}
end end
end end

View File

@ -49,7 +49,8 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
""" """
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
Logger.debug("Federating #{id} to #{inbox}") Logger.debug("Federating #{id} to #{inbox}")
%{host: host, path: path} = URI.parse(inbox)
uri = URI.parse(inbox)
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64()) digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
@ -57,8 +58,8 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
signature = signature =
Pleroma.Signature.sign(actor, %{ Pleroma.Signature.sign(actor, %{
"(request-target)": "post #{path}", "(request-target)": "post #{uri.path}",
host: host, host: signature_host(uri),
"content-length": byte_size(json), "content-length": byte_size(json),
digest: digest, digest: digest,
date: date date: date
@ -76,8 +77,9 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
{"digest", digest} {"digest", digest}
] ]
) do ) do
if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since], if not Map.has_key?(params, :unreachable_since) || params[:unreachable_since] do
do: Instances.set_reachable(inbox) Instances.set_reachable(inbox)
end
result result
else else
@ -96,6 +98,14 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|> publish_one() |> publish_one()
end end
defp signature_host(%URI{port: port, scheme: scheme, host: host}) do
if port == URI.default_port(scheme) do
host
else
"#{host}:#{port}"
end
end
defp should_federate?(inbox, public) do defp should_federate?(inbox, public) do
if public do if public do
true true

View File

@ -62,15 +62,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_summary(object), do: Map.put(object, "summary", "") def fix_summary(object), do: Map.put(object, "summary", "")
def fix_addressing_list(map, field) do def fix_addressing_list(map, field) do
cond do addrs = map[field]
is_binary(map[field]) ->
Map.put(map, field, [map[field]])
is_nil(map[field]) -> cond do
Map.put(map, field, []) is_list(addrs) ->
Map.put(map, field, Enum.filter(addrs, &is_binary/1))
is_binary(addrs) ->
Map.put(map, field, [addrs])
true -> true ->
map Map.put(map, field, [])
end end
end end

View File

@ -9,8 +9,6 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
alias Pleroma.ConfigDB alias Pleroma.ConfigDB
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
@descriptions Pleroma.Docs.JSON.compile()
plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :update) plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :update)
@ -25,7 +23,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ConfigOperation defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ConfigOperation
def descriptions(conn, _params) do def descriptions(conn, _params) do
descriptions = Enum.filter(@descriptions, &whitelisted_config?/1) descriptions = Enum.filter(Pleroma.Docs.JSON.compiled_descriptions(), &whitelisted_config?/1)
json(conn, descriptions) json(conn, descriptions)
end end

View File

@ -29,6 +29,10 @@ defmodule Pleroma.Web.ApiSpec.Helpers do
} }
end end
def admin_api_params do
[Operation.parameter(:admin_token, :query, :string, "Allows authorization via admin token.")]
end
def pagination_params do def pagination_params do
[ [
Operation.parameter(:max_id, :query, :string, "Return items older than this ID"), Operation.parameter(:max_id, :query, :string, "Return items older than this ID"),

View File

@ -159,6 +159,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
"Accounts which follow the given account, if network is not hidden by the account owner.", "Accounts which follow the given account, if network is not hidden by the account owner.",
parameters: [ parameters: [
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
Operation.parameter(:id, :query, :string, "ID of the resource owner"),
with_relationships_param() | pagination_params() with_relationships_param() | pagination_params()
], ],
responses: %{ responses: %{
@ -177,6 +178,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
"Accounts which the given account is following, if network is not hidden by the account owner.", "Accounts which the given account is following, if network is not hidden by the account owner.",
parameters: [ parameters: [
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
Operation.parameter(:id, :query, :string, "ID of the resource owner"),
with_relationships_param() | pagination_params() with_relationships_param() | pagination_params()
], ],
responses: %{200 => Operation.response("Accounts", "application/json", array_of_accounts())} responses: %{200 => Operation.response("Accounts", "application/json", array_of_accounts())}

View File

@ -26,6 +26,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do
%Schema{type: :boolean, default: false}, %Schema{type: :boolean, default: false},
"Get only saved in database settings" "Get only saved in database settings"
) )
| admin_api_params()
], ],
security: [%{"oAuth" => ["read"]}], security: [%{"oAuth" => ["read"]}],
responses: %{ responses: %{
@ -41,6 +42,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do
summary: "Update config settings", summary: "Update config settings",
operationId: "AdminAPI.ConfigController.update", operationId: "AdminAPI.ConfigController.update",
security: [%{"oAuth" => ["write"]}], security: [%{"oAuth" => ["write"]}],
parameters: admin_api_params(),
requestBody: requestBody:
request_body("Parameters", %Schema{ request_body("Parameters", %Schema{
type: :object, type: :object,
@ -73,6 +75,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do
summary: "Get JSON with config descriptions.", summary: "Get JSON with config descriptions.",
operationId: "AdminAPI.ConfigController.descriptions", operationId: "AdminAPI.ConfigController.descriptions",
security: [%{"oAuth" => ["read"]}], security: [%{"oAuth" => ["read"]}],
parameters: admin_api_params(),
responses: %{ responses: %{
200 => 200 =>
Operation.response("Config Descriptions", "application/json", %Schema{ Operation.response("Config Descriptions", "application/json", %Schema{

View File

@ -20,6 +20,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation do
summary: "Get a list of generated invites", summary: "Get a list of generated invites",
operationId: "AdminAPI.InviteController.index", operationId: "AdminAPI.InviteController.index",
security: [%{"oAuth" => ["read:invites"]}], security: [%{"oAuth" => ["read:invites"]}],
parameters: admin_api_params(),
responses: %{ responses: %{
200 => 200 =>
Operation.response("Invites", "application/json", %Schema{ Operation.response("Invites", "application/json", %Schema{
@ -51,6 +52,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation do
summary: "Create an account registration invite token", summary: "Create an account registration invite token",
operationId: "AdminAPI.InviteController.create", operationId: "AdminAPI.InviteController.create",
security: [%{"oAuth" => ["write:invites"]}], security: [%{"oAuth" => ["write:invites"]}],
parameters: admin_api_params(),
requestBody: requestBody:
request_body("Parameters", %Schema{ request_body("Parameters", %Schema{
type: :object, type: :object,
@ -71,6 +73,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation do
summary: "Revoke invite by token", summary: "Revoke invite by token",
operationId: "AdminAPI.InviteController.revoke", operationId: "AdminAPI.InviteController.revoke",
security: [%{"oAuth" => ["write:invites"]}], security: [%{"oAuth" => ["write:invites"]}],
parameters: admin_api_params(),
requestBody: requestBody:
request_body( request_body(
"Parameters", "Parameters",
@ -97,6 +100,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation do
summary: "Sends registration invite via email", summary: "Sends registration invite via email",
operationId: "AdminAPI.InviteController.email", operationId: "AdminAPI.InviteController.email",
security: [%{"oAuth" => ["write:invites"]}], security: [%{"oAuth" => ["write:invites"]}],
parameters: admin_api_params(),
requestBody: requestBody:
request_body( request_body(
"Parameters", "Parameters",

View File

@ -33,6 +33,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation do
%Schema{type: :integer, default: 50}, %Schema{type: :integer, default: 50},
"Number of statuses to return" "Number of statuses to return"
) )
| admin_api_params()
], ],
responses: %{ responses: %{
200 => success_response() 200 => success_response()
@ -46,6 +47,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation do
summary: "Remove a banned MediaProxy URL from Cachex", summary: "Remove a banned MediaProxy URL from Cachex",
operationId: "AdminAPI.MediaProxyCacheController.delete", operationId: "AdminAPI.MediaProxyCacheController.delete",
security: [%{"oAuth" => ["write:media_proxy_caches"]}], security: [%{"oAuth" => ["write:media_proxy_caches"]}],
parameters: admin_api_params(),
requestBody: requestBody:
request_body( request_body(
"Parameters", "Parameters",
@ -71,6 +73,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation do
summary: "Purge and optionally ban a MediaProxy URL", summary: "Purge and optionally ban a MediaProxy URL",
operationId: "AdminAPI.MediaProxyCacheController.purge", operationId: "AdminAPI.MediaProxyCacheController.purge",
security: [%{"oAuth" => ["write:media_proxy_caches"]}], security: [%{"oAuth" => ["write:media_proxy_caches"]}],
parameters: admin_api_params(),
requestBody: requestBody:
request_body( request_body(
"Parameters", "Parameters",

View File

@ -36,6 +36,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do
%Schema{type: :integer, default: 50}, %Schema{type: :integer, default: 50},
"Number of apps to return" "Number of apps to return"
) )
| admin_api_params()
], ],
responses: %{ responses: %{
200 => 200 =>
@ -72,6 +73,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do
summary: "Create OAuth App", summary: "Create OAuth App",
operationId: "AdminAPI.OAuthAppController.create", operationId: "AdminAPI.OAuthAppController.create",
requestBody: request_body("Parameters", create_request()), requestBody: request_body("Parameters", create_request()),
parameters: admin_api_params(),
security: [%{"oAuth" => ["write"]}], security: [%{"oAuth" => ["write"]}],
responses: %{ responses: %{
200 => Operation.response("App", "application/json", oauth_app()), 200 => Operation.response("App", "application/json", oauth_app()),
@ -85,7 +87,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do
tags: ["Admin", "oAuth Apps"], tags: ["Admin", "oAuth Apps"],
summary: "Update OAuth App", summary: "Update OAuth App",
operationId: "AdminAPI.OAuthAppController.update", operationId: "AdminAPI.OAuthAppController.update",
parameters: [id_param()], parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["write"]}], security: [%{"oAuth" => ["write"]}],
requestBody: request_body("Parameters", update_request()), requestBody: request_body("Parameters", update_request()),
responses: %{ responses: %{
@ -103,7 +105,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do
tags: ["Admin", "oAuth Apps"], tags: ["Admin", "oAuth Apps"],
summary: "Delete OAuth App", summary: "Delete OAuth App",
operationId: "AdminAPI.OAuthAppController.delete", operationId: "AdminAPI.OAuthAppController.delete",
parameters: [id_param()], parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["write"]}], security: [%{"oAuth" => ["write"]}],
responses: %{ responses: %{
204 => no_content_response(), 204 => no_content_response(),

View File

@ -19,6 +19,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
summary: "List Relays", summary: "List Relays",
operationId: "AdminAPI.RelayController.index", operationId: "AdminAPI.RelayController.index",
security: [%{"oAuth" => ["read"]}], security: [%{"oAuth" => ["read"]}],
parameters: admin_api_params(),
responses: %{ responses: %{
200 => 200 =>
Operation.response("Response", "application/json", %Schema{ Operation.response("Response", "application/json", %Schema{
@ -41,6 +42,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
summary: "Follow a Relay", summary: "Follow a Relay",
operationId: "AdminAPI.RelayController.follow", operationId: "AdminAPI.RelayController.follow",
security: [%{"oAuth" => ["write:follows"]}], security: [%{"oAuth" => ["write:follows"]}],
parameters: admin_api_params(),
requestBody: requestBody:
request_body("Parameters", %Schema{ request_body("Parameters", %Schema{
type: :object, type: :object,
@ -64,6 +66,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
summary: "Unfollow a Relay", summary: "Unfollow a Relay",
operationId: "AdminAPI.RelayController.unfollow", operationId: "AdminAPI.RelayController.unfollow",
security: [%{"oAuth" => ["write:follows"]}], security: [%{"oAuth" => ["write:follows"]}],
parameters: admin_api_params(),
requestBody: requestBody:
request_body("Parameters", %Schema{ request_body("Parameters", %Schema{
type: :object, type: :object,

View File

@ -48,6 +48,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
%Schema{type: :integer, default: 50}, %Schema{type: :integer, default: 50},
"Number number of log entries per page" "Number number of log entries per page"
) )
| admin_api_params()
], ],
responses: %{ responses: %{
200 => 200 =>
@ -71,7 +72,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
tags: ["Admin", "Reports"], tags: ["Admin", "Reports"],
summary: "Get an individual report", summary: "Get an individual report",
operationId: "AdminAPI.ReportController.show", operationId: "AdminAPI.ReportController.show",
parameters: [id_param()], parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["read:reports"]}], security: [%{"oAuth" => ["read:reports"]}],
responses: %{ responses: %{
200 => Operation.response("Report", "application/json", report()), 200 => Operation.response("Report", "application/json", report()),
@ -86,6 +87,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
summary: "Change the state of one or multiple reports", summary: "Change the state of one or multiple reports",
operationId: "AdminAPI.ReportController.update", operationId: "AdminAPI.ReportController.update",
security: [%{"oAuth" => ["write:reports"]}], security: [%{"oAuth" => ["write:reports"]}],
parameters: admin_api_params(),
requestBody: request_body("Parameters", update_request(), required: true), requestBody: request_body("Parameters", update_request(), required: true),
responses: %{ responses: %{
204 => no_content_response(), 204 => no_content_response(),
@ -100,7 +102,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
tags: ["Admin", "Reports"], tags: ["Admin", "Reports"],
summary: "Create report note", summary: "Create report note",
operationId: "AdminAPI.ReportController.notes_create", operationId: "AdminAPI.ReportController.notes_create",
parameters: [id_param()], parameters: [id_param() | admin_api_params()],
requestBody: requestBody:
request_body("Parameters", %Schema{ request_body("Parameters", %Schema{
type: :object, type: :object,
@ -124,6 +126,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
parameters: [ parameters: [
Operation.parameter(:report_id, :path, :string, "Report ID"), Operation.parameter(:report_id, :path, :string, "Report ID"),
Operation.parameter(:id, :path, :string, "Note ID") Operation.parameter(:id, :path, :string, "Note ID")
| admin_api_params()
], ],
security: [%{"oAuth" => ["write:reports"]}], security: [%{"oAuth" => ["write:reports"]}],
responses: %{ responses: %{

View File

@ -55,6 +55,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
%Schema{type: :integer, default: 50}, %Schema{type: :integer, default: 50},
"Number of statuses to return" "Number of statuses to return"
) )
| admin_api_params()
], ],
responses: %{ responses: %{
200 => 200 =>
@ -71,7 +72,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
tags: ["Admin", "Statuses"], tags: ["Admin", "Statuses"],
summary: "Show Status", summary: "Show Status",
operationId: "AdminAPI.StatusController.show", operationId: "AdminAPI.StatusController.show",
parameters: [id_param()], parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["read:statuses"]}], security: [%{"oAuth" => ["read:statuses"]}],
responses: %{ responses: %{
200 => Operation.response("Status", "application/json", status()), 200 => Operation.response("Status", "application/json", status()),
@ -85,7 +86,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
tags: ["Admin", "Statuses"], tags: ["Admin", "Statuses"],
summary: "Change the scope of an individual reported status", summary: "Change the scope of an individual reported status",
operationId: "AdminAPI.StatusController.update", operationId: "AdminAPI.StatusController.update",
parameters: [id_param()], parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["write:statuses"]}], security: [%{"oAuth" => ["write:statuses"]}],
requestBody: request_body("Parameters", update_request(), required: true), requestBody: request_body("Parameters", update_request(), required: true),
responses: %{ responses: %{
@ -100,7 +101,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
tags: ["Admin", "Statuses"], tags: ["Admin", "Statuses"],
summary: "Delete an individual reported status", summary: "Delete an individual reported status",
operationId: "AdminAPI.StatusController.delete", operationId: "AdminAPI.StatusController.delete",
parameters: [id_param()], parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["write:statuses"]}], security: [%{"oAuth" => ["write:statuses"]}],
responses: %{ responses: %{
200 => empty_object_response(), 200 => empty_object_response(),

View File

@ -90,11 +90,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
notification_settings: %Schema{ notification_settings: %Schema{
type: :object, type: :object,
properties: %{ properties: %{
followers: %Schema{type: :boolean}, block_from_strangers: %Schema{type: :boolean},
follows: %Schema{type: :boolean}, hide_notification_contents: %Schema{type: :boolean}
non_followers: %Schema{type: :boolean},
non_follows: %Schema{type: :boolean},
privacy_option: %Schema{type: :boolean}
} }
}, },
relationship: AccountRelationship, relationship: AccountRelationship,
@ -182,11 +179,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
"unread_conversation_count" => 0, "unread_conversation_count" => 0,
"tags" => [], "tags" => [],
"notification_settings" => %{ "notification_settings" => %{
"followers" => true, "block_from_strangers" => false,
"follows" => true, "hide_notification_contents" => false
"non_followers" => true,
"non_follows" => true,
"privacy_option" => false
}, },
"relationship" => %{ "relationship" => %{
"blocked_by" => false, "blocked_by" => false,

View File

@ -172,6 +172,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
with_direct_conversation_id: true with_direct_conversation_id: true
) )
else else
{:error, {:reject, message}} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{error: message})
{:error, message} -> {:error, message} ->
conn conn
|> put_status(:unprocessable_entity) |> put_status(:unprocessable_entity)

View File

@ -41,7 +41,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
account_activation_required: Keyword.get(instance, :account_activation_required), account_activation_required: Keyword.get(instance, :account_activation_required),
features: features(), features: features(),
federation: federation(), federation: federation(),
fields_limits: fields_limits() fields_limits: fields_limits(),
post_formats: Config.get([:instance, :allowed_post_formats])
}, },
vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
} }

View File

@ -60,22 +60,28 @@ defmodule Pleroma.Web.MediaProxy do
defp whitelisted?(url) do defp whitelisted?(url) do
%{host: domain} = URI.parse(url) %{host: domain} = URI.parse(url)
mediaproxy_whitelist = Config.get([:media_proxy, :whitelist]) mediaproxy_whitelist_domains =
[:media_proxy, :whitelist]
|> Config.get()
|> Enum.map(&maybe_get_domain_from_url/1)
upload_base_url_domain = whitelist_domains =
if !is_nil(Config.get([Upload, :base_url])) do if base_url = Config.get([Upload, :base_url]) do
[URI.parse(Config.get([Upload, :base_url])).host] %{host: base_domain} = URI.parse(base_url)
[base_domain | mediaproxy_whitelist_domains]
else else
[] mediaproxy_whitelist_domains
end end
whitelist = mediaproxy_whitelist ++ upload_base_url_domain domain in whitelist_domains
Enum.any?(whitelist, fn pattern ->
String.equivalent?(domain, pattern)
end)
end end
defp maybe_get_domain_from_url("http" <> _ = url) do
URI.parse(url).host
end
defp maybe_get_domain_from_url(domain), do: domain
def encode_url(url) do def encode_url(url) do
base64 = Base.url_encode64(url, @base64_opts) base64 = Base.url_encode64(url, @base64_opts)

View File

@ -104,7 +104,7 @@ defmodule Pleroma.Web.Push.Impl do
def build_content( def build_content(
%{ %{
user: %{notification_settings: %{privacy_option: true}} user: %{notification_settings: %{hide_notification_contents: true}}
} = notification, } = notification,
_actor, _actor,
_object, _object,

View File

@ -10,7 +10,7 @@
<%= form_for @conn, mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %> <%= form_for @conn, mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
<div class="input"> <div class="input">
<%= label f, :code, "Recovery code" %> <%= label f, :code, "Recovery code" %>
<%= text_input f, :code %> <%= text_input f, :code, [autocomplete: false, autocorrect: "off", autocapitalize: "off", autofocus: true, spellcheck: false] %>
<%= hidden_input f, :mfa_token, value: @mfa_token %> <%= hidden_input f, :mfa_token, value: @mfa_token %>
<%= hidden_input f, :state, value: @state %> <%= hidden_input f, :state, value: @state %>
<%= hidden_input f, :redirect_uri, value: @redirect_uri %> <%= hidden_input f, :redirect_uri, value: @redirect_uri %>

View File

@ -10,7 +10,7 @@
<%= form_for @conn, mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %> <%= form_for @conn, mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
<div class="input"> <div class="input">
<%= label f, :code, "Authentication code" %> <%= label f, :code, "Authentication code" %>
<%= text_input f, :code %> <%= text_input f, :code, [autocomplete: false, autocorrect: "off", autocapitalize: "off", autofocus: true, pattern: "[0-9]*", spellcheck: false] %>
<%= hidden_input f, :mfa_token, value: @mfa_token %> <%= hidden_input f, :mfa_token, value: @mfa_token %>
<%= hidden_input f, :state, value: @state %> <%= hidden_input f, :state, value: @state %>
<%= hidden_input f, :redirect_uri, value: @redirect_uri %> <%= hidden_input f, :redirect_uri, value: @redirect_uri %>

11
mix.exs
View File

@ -90,8 +90,6 @@ defmodule Pleroma.Mixfile do
defp elixirc_paths(_), do: ["lib"] defp elixirc_paths(_), do: ["lib"]
defp warnings_as_errors(:prod), do: false defp warnings_as_errors(:prod), do: false
# Uncomment this if you need testing configurable_from_database logic
# defp warnings_as_errors(:dev), do: false
defp warnings_as_errors(_), do: true defp warnings_as_errors(_), do: true
# Specifies OAuth dependencies. # Specifies OAuth dependencies.
@ -137,13 +135,11 @@ defmodule Pleroma.Mixfile do
{:poison, "~> 3.0", override: true}, {:poison, "~> 3.0", override: true},
# {:tesla, "~> 1.3", override: true}, # {:tesla, "~> 1.3", override: true},
{:tesla, {:tesla,
git: "https://git.pleroma.social/pleroma/elixir-libraries/tesla.git", github: "teamon/tesla", ref: "af3707078b10793f6a534938e56b963aff82fe3c", override: true},
ref: "61b7503cef33f00834f78ddfafe0d5d9dec2270b",
override: true},
{:castore, "~> 0.1"}, {:castore, "~> 0.1"},
{:cowlib, "~> 2.8", override: true}, {:cowlib, "~> 2.8", override: true},
{:gun, {:gun,
github: "ninenines/gun", ref: "e1a69b36b180a574c0ac314ced9613fdd52312cc", override: true}, github: "ninenines/gun", ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", override: true},
{:jason, "~> 1.0"}, {:jason, "~> 1.0"},
{:mogrify, "~> 0.6.1"}, {:mogrify, "~> 0.6.1"},
{:ex_aws, "~> 2.1"}, {:ex_aws, "~> 2.1"},
@ -193,6 +189,9 @@ defmodule Pleroma.Mixfile do
{:plug_static_index_html, "~> 1.0.0"}, {:plug_static_index_html, "~> 1.0.0"},
{:excoveralls, "~> 0.12.1", only: :test}, {:excoveralls, "~> 0.12.1", only: :test},
{:flake_id, "~> 0.1.0"}, {:flake_id, "~> 0.1.0"},
{:concurrent_limiter,
git: "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter",
ref: "8eee96c6ba39b9286ec44c51c52d9f2758951365"},
{:remote_ip, {:remote_ip,
git: "https://git.pleroma.social/pleroma/remote_ip.git", git: "https://git.pleroma.social/pleroma/remote_ip.git",
ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"}, ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"},

View File

@ -15,6 +15,7 @@
"certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"}, "certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"}, "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"},
"concurrent_limiter": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter", "8eee96c6ba39b9286ec44c51c52d9f2758951365", [ref: "8eee96c6ba39b9286ec44c51c52d9f2758951365"]},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
"cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9af027d20dc12dd0c4345a6b87247e0c62965871feea0bfecf9764648b02cc69"}, "cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9af027d20dc12dd0c4345a6b87247e0c62965871feea0bfecf9764648b02cc69"},
"cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"}, "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"},
@ -49,7 +50,7 @@
"gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"}, "gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"},
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"}, "gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"},
"gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm", "3c75b5ea8288e2ee7ea503ff9e30dfe4d07ad3c054576a6e60040e79a801e14d"}, "gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm", "3c75b5ea8288e2ee7ea503ff9e30dfe4d07ad3c054576a6e60040e79a801e14d"},
"gun": {:git, "https://github.com/ninenines/gun.git", "e1a69b36b180a574c0ac314ced9613fdd52312cc", [ref: "e1a69b36b180a574c0ac314ced9613fdd52312cc"]}, "gun": {:git, "https://github.com/ninenines/gun.git", "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", [ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327"]},
"hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"}, "hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"},
"html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"}, "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"}, "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
@ -107,7 +108,7 @@
"swoosh": {:git, "https://github.com/swoosh/swoosh", "c96e0ca8a00d8f211ec1f042a4626b09f249caa5", [ref: "c96e0ca8a00d8f211ec1f042a4626b09f249caa5"]}, "swoosh": {:git, "https://github.com/swoosh/swoosh", "c96e0ca8a00d8f211ec1f042a4626b09f249caa5", [ref: "c96e0ca8a00d8f211ec1f042a4626b09f249caa5"]},
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
"tesla": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/tesla.git", "61b7503cef33f00834f78ddfafe0d5d9dec2270b", [ref: "61b7503cef33f00834f78ddfafe0d5d9dec2270b"]}, "tesla": {:git, "https://github.com/teamon/tesla.git", "af3707078b10793f6a534938e56b963aff82fe3c", [ref: "af3707078b10793f6a534938e56b963aff82fe3c"]},
"timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"}, "timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"}, "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
"tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"}, "tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"},

View File

@ -90,110 +90,100 @@ msgid "must be equal to %{number}"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:421 #: lib/pleroma/web/common_api/common_api.ex:505
msgid "Account not found" msgid "Account not found"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:249 #: lib/pleroma/web/common_api/common_api.ex:339
msgid "Already voted" msgid "Already voted"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:360 #: lib/pleroma/web/oauth/oauth_controller.ex:359
msgid "Bad request" msgid "Bad request"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:426
msgid "Can't delete object" msgid "Can't delete object"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:196 #: lib/pleroma/web/controller_helper.ex:105
msgid "Can't delete this post" #: lib/pleroma/web/controller_helper.ex:111
msgstr ""
#, elixir-format
#: lib/pleroma/web/controller_helper.ex:95
#: lib/pleroma/web/controller_helper.ex:101
msgid "Can't display this activity" msgid "Can't display this activity"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:227 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:285
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:254
msgid "Can't find user" msgid "Can't find user"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:114 #: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61
msgid "Can't get favorites" msgid "Can't get favorites"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:437 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:438
msgid "Can't like object" msgid "Can't like object"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/utils.ex:556 #: lib/pleroma/web/common_api/utils.ex:563
msgid "Cannot post an empty status without attachments" msgid "Cannot post an empty status without attachments"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/utils.ex:504 #: lib/pleroma/web/common_api/utils.ex:511
msgid "Comment must be up to %{max_size} characters" msgid "Comment must be up to %{max_size} characters"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/config/config_db.ex:222 #: lib/pleroma/config/config_db.ex:191
msgid "Config with params %{params} not found" msgid "Config with params %{params} not found"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:95 #: lib/pleroma/web/common_api/common_api.ex:181
#: lib/pleroma/web/common_api/common_api.ex:185
msgid "Could not delete" msgid "Could not delete"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:141 #: lib/pleroma/web/common_api/common_api.ex:231
msgid "Could not favorite" msgid "Could not favorite"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:370 #: lib/pleroma/web/common_api/common_api.ex:453
msgid "Could not pin" msgid "Could not pin"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:112 #: lib/pleroma/web/common_api/common_api.ex:278
msgid "Could not repeat"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:188
msgid "Could not unfavorite" msgid "Could not unfavorite"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:380 #: lib/pleroma/web/common_api/common_api.ex:463
msgid "Could not unpin" msgid "Could not unpin"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:126 #: lib/pleroma/web/common_api/common_api.ex:216
msgid "Could not unrepeat" msgid "Could not unrepeat"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:428 #: lib/pleroma/web/common_api/common_api.ex:512
#: lib/pleroma/web/common_api/common_api.ex:437 #: lib/pleroma/web/common_api/common_api.ex:521
msgid "Could not update state" msgid "Could not update state"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202 #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:207
msgid "Error." msgid "Error."
msgstr "" msgstr ""
@ -203,8 +193,8 @@ msgid "Invalid CAPTCHA"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:117 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:116
#: lib/pleroma/web/oauth/oauth_controller.ex:569 #: lib/pleroma/web/oauth/oauth_controller.ex:568
msgid "Invalid credentials" msgid "Invalid credentials"
msgstr "" msgstr ""
@ -214,22 +204,22 @@ msgid "Invalid credentials."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:265 #: lib/pleroma/web/common_api/common_api.ex:355
msgid "Invalid indices" msgid "Invalid indices"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1147 #: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29
msgid "Invalid parameters" msgid "Invalid parameters"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/utils.ex:411 #: lib/pleroma/web/common_api/utils.ex:414
msgid "Invalid password." msgid "Invalid password."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:187 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:220
msgid "Invalid request" msgid "Invalid request"
msgstr "" msgstr ""
@ -239,44 +229,44 @@ msgid "Kocaptcha service unavailable"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:113 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:112
msgid "Missing parameters" msgid "Missing parameters"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/utils.ex:540 #: lib/pleroma/web/common_api/utils.ex:547
msgid "No such conversation" msgid "No such conversation"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:439 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388
#: lib/pleroma/web/admin_api/admin_api_controller.ex:465 lib/pleroma/web/admin_api/admin_api_controller.ex:507 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456
msgid "No such permission_group" msgid "No such permission_group"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:74 #: lib/pleroma/plugs/uploaded_media.ex:84
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:485 lib/pleroma/web/admin_api/admin_api_controller.ex:1135 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11
#: lib/pleroma/web/feed/user_controller.ex:73 lib/pleroma/web/ostatus/ostatus_controller.ex:143 #: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143
msgid "Not found" msgid "Not found"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:241 #: lib/pleroma/web/common_api/common_api.ex:331
msgid "Poll's author can't vote" msgid "Poll's author can't vote"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20 #: 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: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/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:306
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
msgid "Record not found" msgid "Record not found"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1153 #: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35
#: lib/pleroma/web/feed/user_controller.ex:79 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:32 #: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36
#: lib/pleroma/web/ostatus/ostatus_controller.ex:149 #: lib/pleroma/web/ostatus/ostatus_controller.ex:149
msgid "Something went wrong" msgid "Something went wrong"
msgstr "" msgstr ""
@ -287,7 +277,7 @@ msgid "The message visibility must be direct"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/utils.ex:566 #: lib/pleroma/web/common_api/utils.ex:573
msgid "The status is over the character limit" msgid "The status is over the character limit"
msgstr "" msgstr ""
@ -302,65 +292,65 @@ msgid "Throttled"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:266 #: lib/pleroma/web/common_api/common_api.ex:356
msgid "Too many choices" msgid "Too many choices"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:442 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443
msgid "Unhandled activity type" msgid "Unhandled activity type"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:536 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485
msgid "You can't revoke your own admin status." msgid "You can't revoke your own admin status."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:218 #: lib/pleroma/web/oauth/oauth_controller.ex:221
#: lib/pleroma/web/oauth/oauth_controller.ex:309 #: lib/pleroma/web/oauth/oauth_controller.ex:308
msgid "Your account is currently disabled" msgid "Your account is currently disabled"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:180 #: lib/pleroma/web/oauth/oauth_controller.ex:183
#: lib/pleroma/web/oauth/oauth_controller.ex:332 #: lib/pleroma/web/oauth/oauth_controller.ex:331
msgid "Your login is missing a confirmed e-mail address" msgid "Your login is missing a confirmed e-mail address"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390
msgid "can't read inbox of %{nickname} as %{as_nickname}" msgid "can't read inbox of %{nickname} as %{as_nickname}"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473
msgid "can't update outbox of %{nickname} as %{as_nickname}" msgid "can't update outbox of %{nickname} as %{as_nickname}"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:388 #: lib/pleroma/web/common_api/common_api.ex:471
msgid "conversation is already muted" msgid "conversation is already muted"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:316 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492
msgid "error" msgid "error"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:29 #: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32
msgid "mascots can only be images" msgid "mascots can only be images"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:60 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62
msgid "not found" msgid "not found"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:395 #: lib/pleroma/web/oauth/oauth_controller.ex:394
msgid "Bad OAuth request." msgid "Bad OAuth request."
msgstr "" msgstr ""
@ -375,17 +365,17 @@ msgid "CAPTCHA expired"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:55 #: lib/pleroma/plugs/uploaded_media.ex:57
msgid "Failed" msgid "Failed"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:411 #: lib/pleroma/web/oauth/oauth_controller.ex:410
msgid "Failed to authenticate: %{message}." msgid "Failed to authenticate: %{message}."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:442 #: lib/pleroma/web/oauth/oauth_controller.ex:441
msgid "Failed to set up user account." msgid "Failed to set up user account."
msgstr "" msgstr ""
@ -395,7 +385,7 @@ msgid "Insufficient permissions: %{permissions}."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:94 #: lib/pleroma/plugs/uploaded_media.ex:104
msgid "Internal Error" msgid "Internal Error"
msgstr "" msgstr ""
@ -411,12 +401,12 @@ msgid "Invalid answer data"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:128 #: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33
msgid "Nodeinfo schema version not handled" msgid "Nodeinfo schema version not handled"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:169 #: lib/pleroma/web/oauth/oauth_controller.ex:172
msgid "This action is outside the authorized scopes" msgid "This action is outside the authorized scopes"
msgstr "" msgstr ""
@ -426,13 +416,13 @@ msgid "Unknown error, please check the details and try again."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:116 #: lib/pleroma/web/oauth/oauth_controller.ex:119
#: lib/pleroma/web/oauth/oauth_controller.ex:155 #: lib/pleroma/web/oauth/oauth_controller.ex:158
msgid "Unlisted redirect_uri." msgid "Unlisted redirect_uri."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:391 #: lib/pleroma/web/oauth/oauth_controller.ex:390
msgid "Unsupported OAuth provider: %{provider}." msgid "Unsupported OAuth provider: %{provider}."
msgstr "" msgstr ""
@ -452,12 +442,12 @@ msgid "CAPTCHA Error"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:200 #: lib/pleroma/web/common_api/common_api.ex:290
msgid "Could not add reaction emoji" msgid "Could not add reaction emoji"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:211 #: lib/pleroma/web/common_api/common_api.ex:301
msgid "Could not remove reaction emoji" msgid "Could not remove reaction emoji"
msgstr "" msgstr ""
@ -472,39 +462,45 @@ msgid "List not found"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:124 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123
msgid "Missing parameter: %{name}" msgid "Missing parameter: %{name}"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:207 #: lib/pleroma/web/oauth/oauth_controller.ex:210
#: lib/pleroma/web/oauth/oauth_controller.ex:322 #: lib/pleroma/web/oauth/oauth_controller.ex:321
msgid "Password reset is required" msgid "Password reset is required"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/tests/auth_test_controller.ex:9 #: 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/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6
#: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/fallback_redirect_controller.ex:6 #: lib/pleroma/web/admin_api/controllers/config_controller.ex:6 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6
#: lib/pleroma/web/feed/tag_controller.ex:6 lib/pleroma/web/feed/user_controller.ex:6 #: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6 lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6
#: lib/pleroma/web/mailer/subscription_controller.ex:2 lib/pleroma/web/masto_fe_controller.ex:6 #: lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex:6 lib/pleroma/web/admin_api/controllers/relay_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/admin_api/controllers/report_controller.ex:6 lib/pleroma/web/admin_api/controllers/status_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/controller_helper.ex:6 lib/pleroma/web/embed_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/fallback_redirect_controller.ex:6 lib/pleroma/web/feed/tag_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/feed/user_controller.ex:6 lib/pleroma/web/mailer/subscription_controller.ex:2
#: lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6 #: lib/pleroma/web/masto_fe_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/account_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/app_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/auth_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/conversation_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/custom_emoji_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/domain_block_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/fallback_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/filter_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/follow_request_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/instance_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/list_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/marker_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6 lib/pleroma/web/media_proxy/media_proxy_controller.ex:6 #: lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6
#: lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6 lib/pleroma/web/nodeinfo/nodeinfo_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/oauth/fallback_controller.ex:6 lib/pleroma/web/oauth/mfa_controller.ex:10 #: lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6
#: lib/pleroma/web/oauth/oauth_controller.ex:6 lib/pleroma/web/ostatus/ostatus_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/pleroma_api/controllers/account_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:2 #: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/pleroma_api_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/chat_controller.ex:5 lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:2 lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/scrobble_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/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/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6
@ -519,46 +515,56 @@ msgid "Two-factor authentication enabled, you must use a access token."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:210 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210
msgid "Unexpected error occurred while adding file to pack." msgid "Unexpected error occurred while adding file to pack."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:138 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138
msgid "Unexpected error occurred while creating pack." msgid "Unexpected error occurred while creating pack."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:278 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278
msgid "Unexpected error occurred while removing file from pack." msgid "Unexpected error occurred while removing file from pack."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:250 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250
msgid "Unexpected error occurred while updating file in pack." msgid "Unexpected error occurred while updating file in pack."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:179 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179
msgid "Unexpected error occurred while updating pack metadata." msgid "Unexpected error occurred while updating pack metadata."
msgstr "" msgstr ""
#, elixir-format
#: lib/pleroma/plugs/user_is_admin_plug.ex:40
msgid "User is not an admin or OAuth admin scope is not granted."
msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
msgid "Web push subscription is disabled on this Pleroma instance" msgid "Web push subscription is disabled on this Pleroma instance"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:502 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451
msgid "You can't revoke your own admin/moderator status." msgid "You can't revoke your own admin/moderator status."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:105 #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126
msgid "authorization required for timeline view" msgid "authorization required for timeline view"
msgstr "" msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24
msgid "Access denied"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282
msgid "This API requires an authenticated user"
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/user_is_admin_plug.ex:21
msgid "User is not an admin."
msgstr ""

View File

@ -562,11 +562,11 @@ msgstr "Errore inaspettato durante l'aggiornamento del file nel pacchetto."
msgid "Unexpected error occurred while updating pack metadata." msgid "Unexpected error occurred while updating pack metadata."
msgstr "Errore inaspettato durante l'aggiornamento dei metadati del pacchetto." msgstr "Errore inaspettato durante l'aggiornamento dei metadati del pacchetto."
#: lib/pleroma/plugs/user_is_admin_plug.ex:40 #: lib/pleroma/plugs/user_is_admin_plug.ex:21
#, elixir-format #, elixir-format
msgid "User is not an admin or OAuth admin scope is not granted." msgid "User is not an admin."
msgstr "" msgstr ""
"L'utente non è un amministratore o non ha ricevuto questa autorizzazione " "L'utente non è un amministratore."
"OAuth." "OAuth."
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61

View File

@ -559,9 +559,9 @@ msgstr ""
msgid "Unexpected error occurred while updating pack metadata." msgid "Unexpected error occurred while updating pack metadata."
msgstr "" msgstr ""
#: lib/pleroma/plugs/user_is_admin_plug.ex:40 #: lib/pleroma/plugs/user_is_admin_plug.ex:21
#, elixir-format #, elixir-format
msgid "User is not an admin or OAuth admin scope is not granted." msgid "User is not an admin."
msgstr "" msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61

View File

@ -566,9 +566,9 @@ msgstr "Nieoczekiwany błąd podczas zmieniania pliku w paczce."
msgid "Unexpected error occurred while updating pack metadata." msgid "Unexpected error occurred while updating pack metadata."
msgstr "Nieoczekiwany błąd podczas zmieniania metadanych paczki." msgstr "Nieoczekiwany błąd podczas zmieniania metadanych paczki."
#: lib/pleroma/plugs/user_is_admin_plug.ex:40 #: lib/pleroma/plugs/user_is_admin_plug.ex:21
#, elixir-format #, elixir-format
msgid "User is not an admin or OAuth admin scope is not granted." msgid "User is not an admin."
msgstr "" msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61

View File

@ -0,0 +1,19 @@
defmodule Pleroma.Repo.Migrations.RenameNotificationPrivacyOption do
use Ecto.Migration
def up do
execute(
"UPDATE users SET notification_settings = notification_settings - 'privacy_option' || jsonb_build_object('hide_notification_contents', notification_settings->'privacy_option')
where notification_settings ? 'privacy_option'
and local"
)
end
def down do
execute(
"UPDATE users SET notification_settings = notification_settings - 'hide_notification_contents' || jsonb_build_object('privacy_option', notification_settings->'hide_notification_contents')
where notification_settings ? 'hide_notification_contents'
and local"
)
end
end

View File

@ -0,0 +1,27 @@
defmodule Elixir.Pleroma.Repo.Migrations.Oban20ConfigChanges do
use Ecto.Migration
import Ecto.Query
alias Pleroma.ConfigDB
alias Pleroma.Repo
def change do
config_entry =
from(c in ConfigDB, where: c.group == ^":pleroma" and c.key == ^"Oban")
|> select([c], struct(c, [:value, :id]))
|> Repo.one()
if config_entry do
%{value: value} = config_entry
value =
case Keyword.fetch(value, :verbose) do
{:ok, log} -> Keyword.put_new(value, :log, log)
_ -> value
end
|> Keyword.drop([:verbose, :prune])
Ecto.Changeset.change(config_entry, %{value: value})
|> Repo.update()
end
end
end

View File

@ -1 +1 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/app.77b1644622e3bae24b6b.css rel=stylesheet><link href=/static/fontello.1594374054351.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.247dc52c7abe6a0dab87.js></script><script type=text/javascript src=/static/js/app.1e68e208590653dab5aa.js></script></body></html> <!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/app.6dbc7dea4fc148c85860.css rel=stylesheet><link href=/static/fontello.1594823398494.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.9e24ed238da5a8538f50.js></script><script type=text/javascript src=/static/js/app.31bba9f1e242ff273dcb.js></script></body></html>

View File

@ -243,4 +243,4 @@
cursor: pointer; cursor: pointer;
} }
/*# sourceMappingURL=app.77b1644622e3bae24b6b.css.map*/ /*# sourceMappingURL=app.6dbc7dea4fc148c85860.css.map*/

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

View File

@ -1,144 +0,0 @@
@font-face {
font-family: "Icons";
src: url("./font/fontello.1589385935077.eot");
src: url("./font/fontello.1589385935077.eot") format("embedded-opentype"),
url("./font/fontello.1589385935077.woff2") format("woff2"),
url("./font/fontello.1589385935077.woff") format("woff"),
url("./font/fontello.1589385935077.ttf") format("truetype"),
url("./font/fontello.1589385935077.svg") format("svg");
font-weight: normal;
font-style: normal;
}
[class^="icon-"]::before,
[class*=" icon-"]::before {
font-family: "Icons";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
font-variant: normal;
text-transform: none;
line-height: 1em;
margin-left: .2em;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-spin4::before { content: "\e834"; }
.icon-cancel::before { content: "\e800"; }
.icon-upload::before { content: "\e801"; }
.icon-spin3::before { content: "\e832"; }
.icon-reply::before { content: "\f112"; }
.icon-star::before { content: "\e802"; }
.icon-star-empty::before { content: "\e803"; }
.icon-retweet::before { content: "\e804"; }
.icon-eye-off::before { content: "\e805"; }
.icon-binoculars::before { content: "\f1e5"; }
.icon-cog::before { content: "\e807"; }
.icon-user-plus::before { content: "\f234"; }
.icon-menu::before { content: "\f0c9"; }
.icon-logout::before { content: "\e808"; }
.icon-down-open::before { content: "\e809"; }
.icon-attach::before { content: "\e80a"; }
.icon-link-ext::before { content: "\f08e"; }
.icon-link-ext-alt::before { content: "\f08f"; }
.icon-picture::before { content: "\e80b"; }
.icon-video::before { content: "\e80c"; }
.icon-right-open::before { content: "\e80d"; }
.icon-left-open::before { content: "\e80e"; }
.icon-up-open::before { content: "\e80f"; }
.icon-comment-empty::before { content: "\f0e5"; }
.icon-mail-alt::before { content: "\f0e0"; }
.icon-lock::before { content: "\e811"; }
.icon-lock-open-alt::before { content: "\f13e"; }
.icon-globe::before { content: "\e812"; }
.icon-brush::before { content: "\e813"; }
.icon-search::before { content: "\e806"; }
.icon-adjust::before { content: "\e816"; }
.icon-thumbs-up-alt::before { content: "\f164"; }
.icon-attention::before { content: "\e814"; }
.icon-plus-squared::before { content: "\f0fe"; }
.icon-plus::before { content: "\e815"; }
.icon-edit::before { content: "\e817"; }
.icon-play-circled::before { content: "\f144"; }
.icon-pencil::before { content: "\e818"; }
.icon-chart-bar::before { content: "\e81b"; }
.icon-smile::before { content: "\f118"; }
.icon-bell-alt::before { content: "\f0f3"; }
.icon-wrench::before { content: "\e81a"; }
.icon-pin::before { content: "\e819"; }
.icon-ellipsis::before { content: "\f141"; }
.icon-bell-ringing-o::before { content: "\e810"; }
.icon-zoom-in::before { content: "\e81c"; }
.icon-gauge::before { content: "\f0e4"; }
.icon-users::before { content: "\e81d"; }
.icon-info-circled::before { content: "\e81f"; }
.icon-home-2::before { content: "\e821"; }
.icon-chat::before { content: "\e81e"; }
.icon-login::before { content: "\e820"; }
.icon-arrow-curved::before { content: "\e822"; }
.icon-link::before { content: "\e823"; }
.icon-share::before { content: "\f1e0"; }
.icon-user::before { content: "\e824"; }
.icon-ok::before { content: "\e827"; }

View File

@ -1,152 +0,0 @@
@font-face {
font-family: "Icons";
src: url("./font/fontello.1594030805019.eot");
src: url("./font/fontello.1594030805019.eot") format("embedded-opentype"),
url("./font/fontello.1594030805019.woff2") format("woff2"),
url("./font/fontello.1594030805019.woff") format("woff"),
url("./font/fontello.1594030805019.ttf") format("truetype"),
url("./font/fontello.1594030805019.svg") format("svg");
font-weight: normal;
font-style: normal;
}
[class^="icon-"]::before,
[class*=" icon-"]::before {
font-family: "Icons";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
font-variant: normal;
text-transform: none;
line-height: 1em;
margin-left: .2em;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-spin4::before { content: "\e834"; }
.icon-cancel::before { content: "\e800"; }
.icon-upload::before { content: "\e801"; }
.icon-spin3::before { content: "\e832"; }
.icon-reply::before { content: "\f112"; }
.icon-star::before { content: "\e802"; }
.icon-star-empty::before { content: "\e803"; }
.icon-retweet::before { content: "\e804"; }
.icon-eye-off::before { content: "\e805"; }
.icon-binoculars::before { content: "\f1e5"; }
.icon-cog::before { content: "\e807"; }
.icon-user-plus::before { content: "\f234"; }
.icon-menu::before { content: "\f0c9"; }
.icon-logout::before { content: "\e808"; }
.icon-down-open::before { content: "\e809"; }
.icon-attach::before { content: "\e80a"; }
.icon-link-ext::before { content: "\f08e"; }
.icon-link-ext-alt::before { content: "\f08f"; }
.icon-picture::before { content: "\e80b"; }
.icon-video::before { content: "\e80c"; }
.icon-right-open::before { content: "\e80d"; }
.icon-left-open::before { content: "\e80e"; }
.icon-up-open::before { content: "\e80f"; }
.icon-comment-empty::before { content: "\f0e5"; }
.icon-mail-alt::before { content: "\f0e0"; }
.icon-lock::before { content: "\e811"; }
.icon-lock-open-alt::before { content: "\f13e"; }
.icon-globe::before { content: "\e812"; }
.icon-brush::before { content: "\e813"; }
.icon-search::before { content: "\e806"; }
.icon-adjust::before { content: "\e816"; }
.icon-thumbs-up-alt::before { content: "\f164"; }
.icon-attention::before { content: "\e814"; }
.icon-plus-squared::before { content: "\f0fe"; }
.icon-plus::before { content: "\e815"; }
.icon-edit::before { content: "\e817"; }
.icon-play-circled::before { content: "\f144"; }
.icon-pencil::before { content: "\e818"; }
.icon-chart-bar::before { content: "\e81b"; }
.icon-smile::before { content: "\f118"; }
.icon-bell-alt::before { content: "\f0f3"; }
.icon-wrench::before { content: "\e81a"; }
.icon-pin::before { content: "\e819"; }
.icon-ellipsis::before { content: "\f141"; }
.icon-bell-ringing-o::before { content: "\e810"; }
.icon-zoom-in::before { content: "\e81c"; }
.icon-gauge::before { content: "\f0e4"; }
.icon-users::before { content: "\e81d"; }
.icon-info-circled::before { content: "\e81f"; }
.icon-home-2::before { content: "\e821"; }
.icon-chat::before { content: "\e81e"; }
.icon-login::before { content: "\e820"; }
.icon-arrow-curved::before { content: "\e822"; }
.icon-link::before { content: "\e823"; }
.icon-share::before { content: "\f1e0"; }
.icon-user::before { content: "\e824"; }
.icon-ok::before { content: "\e827"; }
.icon-filter::before { content: "\f0b0"; }
.icon-download::before { content: "\e825"; }
.icon-bookmark::before { content: "\e826"; }
.icon-bookmark-empty::before { content: "\f097"; }

View File

@ -1,156 +0,0 @@
@font-face {
font-family: "Icons";
src: url("./font/fontello.1594134783339.eot");
src: url("./font/fontello.1594134783339.eot") format("embedded-opentype"),
url("./font/fontello.1594134783339.woff2") format("woff2"),
url("./font/fontello.1594134783339.woff") format("woff"),
url("./font/fontello.1594134783339.ttf") format("truetype"),
url("./font/fontello.1594134783339.svg") format("svg");
font-weight: normal;
font-style: normal;
}
[class^="icon-"]::before,
[class*=" icon-"]::before {
font-family: "Icons";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
font-variant: normal;
text-transform: none;
line-height: 1em;
margin-left: .2em;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-spin4::before { content: "\e834"; }
.icon-cancel::before { content: "\e800"; }
.icon-upload::before { content: "\e801"; }
.icon-spin3::before { content: "\e832"; }
.icon-reply::before { content: "\f112"; }
.icon-star::before { content: "\e802"; }
.icon-star-empty::before { content: "\e803"; }
.icon-retweet::before { content: "\e804"; }
.icon-eye-off::before { content: "\e805"; }
.icon-binoculars::before { content: "\f1e5"; }
.icon-cog::before { content: "\e807"; }
.icon-user-plus::before { content: "\f234"; }
.icon-menu::before { content: "\f0c9"; }
.icon-logout::before { content: "\e808"; }
.icon-down-open::before { content: "\e809"; }
.icon-attach::before { content: "\e80a"; }
.icon-link-ext::before { content: "\f08e"; }
.icon-link-ext-alt::before { content: "\f08f"; }
.icon-picture::before { content: "\e80b"; }
.icon-video::before { content: "\e80c"; }
.icon-right-open::before { content: "\e80d"; }
.icon-left-open::before { content: "\e80e"; }
.icon-up-open::before { content: "\e80f"; }
.icon-comment-empty::before { content: "\f0e5"; }
.icon-mail-alt::before { content: "\f0e0"; }
.icon-lock::before { content: "\e811"; }
.icon-lock-open-alt::before { content: "\f13e"; }
.icon-globe::before { content: "\e812"; }
.icon-brush::before { content: "\e813"; }
.icon-search::before { content: "\e806"; }
.icon-adjust::before { content: "\e816"; }
.icon-thumbs-up-alt::before { content: "\f164"; }
.icon-attention::before { content: "\e814"; }
.icon-plus-squared::before { content: "\f0fe"; }
.icon-plus::before { content: "\e815"; }
.icon-edit::before { content: "\e817"; }
.icon-play-circled::before { content: "\f144"; }
.icon-pencil::before { content: "\e818"; }
.icon-chart-bar::before { content: "\e81b"; }
.icon-smile::before { content: "\f118"; }
.icon-bell-alt::before { content: "\f0f3"; }
.icon-wrench::before { content: "\e81a"; }
.icon-pin::before { content: "\e819"; }
.icon-ellipsis::before { content: "\f141"; }
.icon-bell-ringing-o::before { content: "\e810"; }
.icon-zoom-in::before { content: "\e81c"; }
.icon-gauge::before { content: "\f0e4"; }
.icon-users::before { content: "\e81d"; }
.icon-info-circled::before { content: "\e81f"; }
.icon-home-2::before { content: "\e821"; }
.icon-chat::before { content: "\e81e"; }
.icon-login::before { content: "\e820"; }
.icon-arrow-curved::before { content: "\e822"; }
.icon-link::before { content: "\e823"; }
.icon-share::before { content: "\f1e0"; }
.icon-user::before { content: "\e824"; }
.icon-ok::before { content: "\e827"; }
.icon-filter::before { content: "\f0b0"; }
.icon-download::before { content: "\e825"; }
.icon-bookmark::before { content: "\e826"; }
.icon-bookmark-empty::before { content: "\f097"; }
.icon-music::before { content: "\e828"; }
.icon-doc::before { content: "\e829"; }

View File

@ -1,11 +1,11 @@
@font-face { @font-face {
font-family: "Icons"; font-family: "Icons";
src: url("./font/fontello.1594374054351.eot"); src: url("./font/fontello.1594823398494.eot");
src: url("./font/fontello.1594374054351.eot") format("embedded-opentype"), src: url("./font/fontello.1594823398494.eot") format("embedded-opentype"),
url("./font/fontello.1594374054351.woff2") format("woff2"), url("./font/fontello.1594823398494.woff2") format("woff2"),
url("./font/fontello.1594374054351.woff") format("woff"), url("./font/fontello.1594823398494.woff") format("woff"),
url("./font/fontello.1594374054351.ttf") format("truetype"), url("./font/fontello.1594823398494.ttf") format("truetype"),
url("./font/fontello.1594374054351.svg") format("svg"); url("./font/fontello.1594823398494.svg") format("svg");
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }

0
priv/static/static/fontello.json Executable file → Normal file
View File

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"static/js/10.2823375ec309b971aaea.js","sourceRoot":""} {"version":3,"sources":[],"names":[],"mappings":"","file":"static/js/10.5ef4671883649cf93524.js","sourceRoot":""}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"static/js/11.2cb4b0f72a4654070a58.js","sourceRoot":""} {"version":3,"sources":[],"names":[],"mappings":"","file":"static/js/11.c5b938b4349f87567338.js","sourceRoot":""}

File diff suppressed because one or more lines are too long

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