diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md index 9ce9b6918..dd0d6eb24 100644 --- a/.gitlab/issue_templates/Bug.md +++ b/.gitlab/issue_templates/Bug.md @@ -8,9 +8,7 @@ ### Environment -* Installation type: - - [ ] OTP - - [ ] From source +* Installation type (OTP or From Source): * 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): * Operating system: diff --git a/CHANGELOG.md b/CHANGELOG.md index a3689fc6b..c52d2eef5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - **Breaking:** Elixir >=1.9 is now required (was >= 1.8) +- **Breaking:** Configuration: `:auto_linker, :opts` moved to `:pleroma, Pleroma.Formatter`. Old config namespace is deprecated. - In Conversations, return only direct messages as `last_status` - Using the `only_media` filter on timelines will now exclude reblog media - MFR policy to set global expiration for all local Create activities @@ -25,6 +26,12 @@ 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: On deletion, returns the original post text. - 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 +- Mastodon API (legacy): Allow query parameters for `/api/v1/domain_blocks`, e.g. `/api/v1/domain_blocks?domain=badposters.zone`
@@ -63,7 +70,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
API Changes -- Mastodon API: Add pleroma.parents_visible field to statuses. + +- Mastodon API: Add pleroma.parent_visible field to statuses. - Mastodon API: Extended `/api/v1/instance`. - Mastodon API: Support for `include_types` in `/api/v1/notifications`. - Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint. @@ -88,6 +96,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Admin API: fix `GET /api/pleroma/admin/users/:nickname/credentials` returning 404 when getting the credentials of a remote user while `:instance, :limit_to_local_content` is set to `:unauthenticated` - Fix CSP policy generation to include remote Captcha services - Fix edge case where MediaProxy truncates media, usually caused when Caddy is serving content for the other Federated instance. +- Emoji Packs could not be listed when instance was set to `public: false` ## [Unreleased (patch)] @@ -117,6 +126,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Follow request notifications
API Changes + - Admin API: `GET /api/pleroma/admin/need_reboot`.
@@ -184,6 +194,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - **Breaking**: Using third party engines for user recommendation
API Changes + - **Breaking**: AdminAPI: migrate_from_db endpoint
diff --git a/config/config.exs b/config/config.exs index 791740663..c5465ae2c 100644 --- a/config/config.exs +++ b/config/config.exs @@ -172,7 +172,7 @@ "application/ld+json" => ["activity+json"] } -config :tesla, adapter: Tesla.Adapter.Hackney +config :tesla, adapter: Tesla.Adapter.Gun # Configures http settings, upstream proxy etc. config :pleroma, :http, @@ -513,6 +513,7 @@ attachments_cleanup: 5, new_users_digest: 1 ], + plugins: [Oban.Plugins.Pruner], crontab: [ {"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker}, {"0 * * * *", Pleroma.Workers.Cron.StatsWorker}, @@ -527,16 +528,14 @@ federator_outgoing: 5 ] -config :auto_linker, - opts: [ - extra: true, - # TODO: Set to :no_scheme when it works properly - validate_tld: true, - class: false, - strip_prefix: false, - new_window: false, - rel: "ugc" - ] +config :pleroma, Pleroma.Formatter, + class: false, + rel: "ugc", + new_window: false, + truncate: false, + strip_prefix: false, + extra: true, + validate_tld: :no_scheme config :pleroma, :ldap, enabled: System.get_env("LDAP_ENABLED") == "true", @@ -648,32 +647,30 @@ prepare: :unnamed config :pleroma, :connections_pool, - checkin_timeout: 250, + reclaim_multiplier: 0.1, + connection_acquisition_wait: 250, + connection_acquisition_retries: 5, max_connections: 250, - retry: 1, - retry_timeout: 1000, + max_idle_time: 30_000, + retry: 0, await_up_timeout: 5_000 config :pleroma, :pools, federation: [ size: 50, - max_overflow: 10, - timeout: 150_000 + max_waiting: 10 ], media: [ size: 50, - max_overflow: 10, - timeout: 150_000 + max_waiting: 10 ], upload: [ size: 25, - max_overflow: 5, - timeout: 300_000 + max_waiting: 5 ], default: [ size: 10, - max_overflow: 2, - timeout: 10_000 + max_waiting: 2 ] config :pleroma, :hackney_pools, diff --git a/config/description.exs b/config/description.exs index 9aca26fbf..f506affd4 100644 --- a/config/description.exs +++ b/config/description.exs @@ -1431,6 +1431,7 @@ group: :pleroma, key: :mrf_simple, tab: :mrf, + related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy", label: "MRF Simple", type: :group, description: "Simple ingress policies", @@ -1497,6 +1498,7 @@ group: :pleroma, key: :mrf_activity_expiration, tab: :mrf, + related_policy: "Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy", label: "MRF Activity Expiration Policy", type: :group, description: "Adds automatic expiration to all local activities", @@ -1513,6 +1515,7 @@ group: :pleroma, key: :mrf_subchain, tab: :mrf, + related_policy: "Pleroma.Web.ActivityPub.MRF.SubchainPolicy", label: "MRF Subchain", type: :group, description: @@ -1535,6 +1538,7 @@ group: :pleroma, key: :mrf_rejectnonpublic, tab: :mrf, + related_policy: "Pleroma.Web.ActivityPub.MRF.RejectNonPublic", description: "RejectNonPublic drops posts with non-public visibility settings.", label: "MRF Reject Non Public", type: :group, @@ -1556,6 +1560,7 @@ group: :pleroma, key: :mrf_hellthread, tab: :mrf, + related_policy: "Pleroma.Web.ActivityPub.MRF.HellthreadPolicy", label: "MRF Hellthread", type: :group, description: "Block messages with excessive user mentions", @@ -1581,6 +1586,7 @@ group: :pleroma, key: :mrf_keyword, tab: :mrf, + related_policy: "Pleroma.Web.ActivityPub.MRF.KeywordPolicy", label: "MRF Keyword", type: :group, description: "Reject or Word-Replace messages with a keyword or regex", @@ -1612,6 +1618,7 @@ group: :pleroma, key: :mrf_mention, tab: :mrf, + related_policy: "Pleroma.Web.ActivityPub.MRF.MentionPolicy", label: "MRF Mention", type: :group, description: "Block messages which mention a specific user", @@ -1628,6 +1635,7 @@ group: :pleroma, key: :mrf_vocabulary, tab: :mrf, + related_policy: "Pleroma.Web.ActivityPub.MRF.VocabularyPolicy", label: "MRF Vocabulary", type: :group, description: "Filter messages which belong to certain activity vocabularies", @@ -1651,6 +1659,8 @@ # %{ # group: :pleroma, # key: :mrf_user_allowlist, + # tab: :mrf, + # related_policy: "Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy", # type: :map, # description: # "The keys in this section are the domain names that the policy should apply to." <> @@ -2221,11 +2231,12 @@ ] }, %{ - group: :auto_linker, - key: :opts, + group: :pleroma, + key: Pleroma.Formatter, label: "Auto Linker", type: :group, - description: "Configuration for the auto_linker library", + description: + "Configuration for Pleroma's link formatter which parses mentions, hashtags, and URLs.", children: [ %{ key: :class, @@ -2242,24 +2253,31 @@ %{ key: :new_window, type: :boolean, - description: "Link URLs will open in new window/tab" + description: "Link URLs will open in a new window/tab." }, %{ key: :truncate, type: [:integer, false], description: - "Set to a number to truncate URLs longer then the number. Truncated URLs will end in `..`", + "Set to a number to truncate URLs longer than the number. Truncated URLs will end in `...`", suggestions: [15, false] }, %{ key: :strip_prefix, type: :boolean, - description: "Strip the scheme prefix" + description: "Strip the scheme prefix." }, %{ key: :extra, type: :boolean, description: "Link URLs with rarely used schemes (magnet, ipfs, irc, etc.)" + }, + %{ + key: :validate_tld, + type: [:atom, :boolean], + description: + "Set to false to disable TLD validation for URLs/emails. Can be set to :no_scheme to validate TLDs only for URLs without a scheme (e.g `example.com` will be validated, but `http://example.loki` won't)", + suggestions: [:no_scheme, true] } ] }, @@ -2907,8 +2925,9 @@ }, %{ group: :pleroma, - tab: :mrf, key: :mrf_normalize_markup, + tab: :mrf, + related_policy: "Pleroma.Web.ActivityPub.MRF.NormalizeMarkup", label: "MRF Normalize Markup", description: "MRF NormalizeMarkup settings. Scrub configured hypertext markup.", type: :group, @@ -3103,8 +3122,9 @@ %{ group: :pleroma, key: :mrf_object_age, - label: "MRF Object Age", tab: :mrf, + related_policy: "Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy", + label: "MRF Object Age", type: :group, description: "Rejects or delists posts based on their timestamp deviance from your server's clock.", @@ -3166,36 +3186,37 @@ description: "Advanced settings for `gun` connections pool", children: [ %{ - key: :checkin_timeout, + key: :connection_acquisition_wait, 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] }, + %{ + 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, type: :integer, description: "Maximum number of connections in the pool. Default: 250 connections.", 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, type: :integer, description: "Timeout while `gun` will wait until connection is up. Default: 5000ms.", 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] } ] }, @@ -3204,108 +3225,29 @@ key: :pools, type: :group, description: "Advanced settings for `gun` workers pools", - children: [ - %{ - key: :federation, - type: :keyword, - description: "Settings for federation 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: :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] - } - ] - } - ] + children: + Enum.map([:federation, :media, :upload, :default], fn pool_name -> + %{ + key: pool_name, + type: :keyword, + description: "Settings for #{pool_name} pool.", + children: [ + %{ + key: :size, + type: :integer, + description: "Maximum number of concurrent requests in the pool.", + suggestions: [50] + }, + %{ + key: :max_waiting, + type: :integer, + 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] + } + ] + } + end) }, %{ group: :pleroma, diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md index c4a9c6dad..38865dc68 100644 --- a/docs/API/differences_in_mastoapi_responses.md +++ b/docs/API/differences_in_mastoapi_responses.md @@ -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.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.post_formats`: A list of the allowed post format types - `vapid_public_key`: The public key needed for push messages ## Markers diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md index b7eee5192..5bd38ad36 100644 --- a/docs/API/pleroma_api.md +++ b/docs/API/pleroma_api.md @@ -287,11 +287,8 @@ See [Admin-API](admin_api.md) * Method `PUT` * Authentication: required * Params: - * `followers`: BOOLEAN field, receives notifications from followers - * `follows`: BOOLEAN field, receives notifications from people the user follows - * `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. + * `block_from_strangers`: BOOLEAN field, blocks notifications from accounts you do not follow + * `hide_notification_contents`: 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"}` ## `/api/pleroma/healthcheck` diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 78270133f..32563a288 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -449,36 +449,32 @@ For each pool, the options are: *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. -It will increase memory usage, but federation would work faster. - -* `:checkin_timeout` - timeout to checkin connection from pool. Default: 250ms. -* `:max_connections` - maximum number of connections in the pool. Default: 250 connections. -* `: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. +* `:connection_acquisition_wait` - Timeout to acquire a connection from pool.The total max time is this value multiplied by the number of retries. +* `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. +* `:await_up_timeout` - Timeout to connect to the host. +* `: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. ### :pools *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: -* `:federation` for the federation 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 -* `:upload` for uploaded media (if using a remote uploader and `proxy_remote: true`) -* `:default` for other requests +* `: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. +* `:media` - for rich media, media proxy. +* `:upload` - for proxying media when a remote uploader is used and `proxy_remote: true`. +* `:default` - for other requests. 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 -* `: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 @@ -939,30 +935,29 @@ Configure OAuth 2 provider capabilities: ### :uri_schemes * `valid_schemes`: List of the scheme part that is considered valid to be an URL. -### :auto_linker +### Pleroma.Formatter -Configuration for the `auto_linker` library: +Configuration for Pleroma's link formatter which parses mentions, hashtags, and URLs. -* `class: "auto-linker"` - specify the class to be added to the generated link. false to clear. -* `rel: "noopener noreferrer"` - override the rel attribute. false to clear. -* `new_window: true` - set to false to remove `target='_blank'` attribute. -* `scheme: false` - Set to true to link urls with schema `http://google.com`. -* `truncate: false` - Set to a number to truncate urls longer then the number. Truncated urls will end in `..`. -* `strip_prefix: true` - Strip the scheme prefix. -* `extra: false` - link urls with rarely used schemes (magnet, ipfs, irc, etc.). +* `class` - specify the class to be added to the generated link (default: `false`) +* `rel` - specify the rel attribute (default: `ugc`) +* `new_window` - adds `target="_blank"` attribute (default: `false`) +* `truncate` - Set to a number to truncate URLs longer then the number. Truncated URLs will end in `...` (default: `false`) +* `strip_prefix` - Strip the scheme prefix (default: `false`) +* `extra` - link URLs with rarely used schemes (magnet, ipfs, irc, etc.) (default: `true`) +* `validate_tld` - Set to false to disable TLD validation for URLs/emails. Can be set to :no_scheme to validate TLDs only for urls without a scheme (e.g `example.com` will be validated, but `http://example.loki` won't) (default: `:no_scheme`) Example: ```elixir -config :auto_linker, - opts: [ - scheme: true, - extra: true, - class: false, - strip_prefix: false, - new_window: false, - rel: "ugc" - ] +config :pleroma, Pleroma.Formatter, + class: false, + rel: "ugc", + new_window: false, + truncate: false, + strip_prefix: false, + extra: true, + validate_tld: :no_scheme ``` ## Custom Runtime Modules (`:modules`) diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex index 9f0bf6ecb..074492a46 100644 --- a/lib/mix/pleroma.ex +++ b/lib/mix/pleroma.ex @@ -24,8 +24,10 @@ def start_pleroma do Application.put_env(:logger, :console, level: :debug) end + adapter = Application.get_env(:tesla, :adapter) + apps = - if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Gun do + if adapter == Tesla.Adapter.Gun do [:gun | @apps] else [:hackney | @apps] @@ -33,11 +35,14 @@ def start_pleroma do Enum.each(apps, &Application.ensure_all_started/1) - children = [ - Pleroma.Repo, - {Pleroma.Config.TransferTask, false}, - Pleroma.Web.Endpoint - ] + children = + [ + Pleroma.Repo, + {Pleroma.Config.TransferTask, false}, + Pleroma.Web.Endpoint, + {Oban, Pleroma.Config.get(Oban)} + ] ++ + http_children(adapter) cachex_children = Enum.map(@cachex_children, &Pleroma.Application.build_cachex(&1, [])) @@ -115,4 +120,11 @@ def mix_shell?, do: :erlang.function_exported(Mix, :shell, 0) def escape_sh_path(path) do ~S(') <> String.replace(path, ~S('), ~S(\')) <> ~S(') end + + defp http_children(Tesla.Adapter.Gun) do + Pleroma.Gun.ConnectionPool.children() ++ + [{Task, &Pleroma.HTTP.AdapterHelper.Gun.limiter_setup/0}] + end + + defp http_children(_), do: [] end diff --git a/lib/mix/tasks/pleroma/notification_settings.ex b/lib/mix/tasks/pleroma/notification_settings.ex index 7d65f0587..00f5ba7bf 100644 --- a/lib/mix/tasks/pleroma/notification_settings.ex +++ b/lib/mix/tasks/pleroma/notification_settings.ex @@ -3,8 +3,8 @@ defmodule Mix.Tasks.Pleroma.NotificationSettings do @moduledoc """ Example: - > mix pleroma.notification_settings --privacy-option=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=false --nickname-users="parallel588" # set false only for parallel588 user + > mix pleroma.notification_settings --hide-notification-contents=true # set true for all users """ @@ -19,16 +19,16 @@ def run(args) do OptionParser.parse( args, strict: [ - privacy_option: :boolean, + hide_notification_contents: :boolean, email_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 - privacy_option + if not is_nil(hide_notification_contents) do + hide_notification_contents |> build_query(options) |> Pleroma.Repo.update_all([]) end @@ -36,15 +36,15 @@ def run(args) do shell_info("Done") end - defp build_query(privacy_option, options) do + defp build_query(hide_notification_contents, options) do query = from(u in Pleroma.User, update: [ set: [ notification_settings: fragment( - "jsonb_set(notification_settings, '{privacy_option}', ?)", - ^privacy_option + "jsonb_set(notification_settings, '{hide_notification_contents}', ?)", + ^hide_notification_contents ) ] ] diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 3282c6882..0ffb55358 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -39,6 +39,7 @@ def start(_type, _args) do # 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() Pleroma.HTML.compile_scrubbers() Config.DeprecationWarnings.warn() @@ -223,9 +224,7 @@ defp task_children(_) do # start hackney and gun pools in tests defp http_children(_, :test) do - hackney_options = Config.get([:hackney_pools, :federation]) - hackney_pool = :hackney_pool.child_spec(:federation, hackney_options) - [hackney_pool, Pleroma.Pool.Supervisor] + http_children(Tesla.Adapter.Hackney, nil) ++ http_children(Tesla.Adapter.Gun, nil) end defp http_children(Tesla.Adapter.Hackney, _) do @@ -244,7 +243,10 @@ defp http_children(Tesla.Adapter.Hackney, _) do 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: [] end diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex index 88575a498..ee88c3346 100644 --- a/lib/pleroma/application_requirements.ex +++ b/lib/pleroma/application_requirements.ex @@ -16,6 +16,7 @@ defmodule VerifyError, do: defexception([:message]) @spec verify!() :: :ok | VerifyError.t() def verify! do :ok + |> check_confirmation_accounts! |> check_migrations_applied!() |> check_rum!() |> handle_result() @@ -24,6 +25,24 @@ def verify! do defp handle_result(:ok), do: :ok defp handle_result({:error, message}), do: raise(VerifyError, message: message) + # Checks account confirmation email + # + def check_confirmation_accounts!(:ok) do + if Pleroma.Config.get([:instance, :account_activation_required]) && + not Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled]) do + Logger.error( + "Account activation enabled, but no Mailer settings enabled.\nPlease set config :pleroma, :instance, account_activation_required: false\nOtherwise setup and enable Mailer." + ) + + {:error, + "Account activation enabled, but Mailer is disabled. Cannot send confirmation emails."} + else + :ok + end + end + + def check_confirmation_accounts!(result), do: result + # Checks for pending migrations. # def check_migrations_applied!(:ok) do diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config/config_db.ex index 1a89d8895..e5b7811aa 100644 --- a/lib/pleroma/config/config_db.ex +++ b/lib/pleroma/config/config_db.ex @@ -156,7 +156,6 @@ defp only_full_update?(%ConfigDB{group: group, key: key}) do {:quack, :meta}, {:mime, :types}, {:cors_plug, [:max_age, :methods, :expose, :headers]}, - {:auto_linker, :opts}, {:swarm, :node_blacklist}, {:logger, :backends} ] diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 02a93a8dc..0c450eae4 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -10,11 +10,15 @@ defmodule Pleroma.Formatter do @link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui @markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/ - @auto_linker_config hashtag: true, - hashtag_handler: &Pleroma.Formatter.hashtag_handler/4, - mention: true, - mention_handler: &Pleroma.Formatter.mention_handler/4, - scheme: true + defp linkify_opts do + Pleroma.Config.get(Pleroma.Formatter) ++ + [ + hashtag: true, + hashtag_handler: &Pleroma.Formatter.hashtag_handler/4, + mention: true, + mention_handler: &Pleroma.Formatter.mention_handler/4 + ] + end def escape_mention_handler("@" <> nickname = mention, buffer, _, _) do case User.get_cached_by_nickname(nickname) do @@ -80,19 +84,19 @@ def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do @spec linkify(String.t(), keyword()) :: {String.t(), [{String.t(), User.t()}], [{String.t(), String.t()}]} def linkify(text, options \\ []) do - options = options ++ @auto_linker_config + options = linkify_opts() ++ options if options[:safe_mention] && Regex.named_captures(@safe_mention_regex, text) do %{"mentions" => mentions, "rest" => rest} = Regex.named_captures(@safe_mention_regex, text) acc = %{mentions: MapSet.new(), tags: MapSet.new()} - {text_mentions, %{mentions: mentions}} = AutoLinker.link_map(mentions, acc, options) - {text_rest, %{tags: tags}} = AutoLinker.link_map(rest, acc, options) + {text_mentions, %{mentions: mentions}} = Linkify.link_map(mentions, acc, options) + {text_rest, %{tags: tags}} = Linkify.link_map(rest, acc, options) {text_mentions <> text_rest, MapSet.to_list(mentions), MapSet.to_list(tags)} else acc = %{mentions: MapSet.new(), tags: MapSet.new()} - {text, %{mentions: mentions, tags: tags}} = AutoLinker.link_map(text, acc, options) + {text, %{mentions: mentions, tags: tags}} = Linkify.link_map(text, acc, options) {text, MapSet.to_list(mentions), MapSet.to_list(tags)} end @@ -111,9 +115,9 @@ def mentions_escape(text, options \\ []) do if options[:safe_mention] && Regex.named_captures(@safe_mention_regex, text) do %{"mentions" => mentions, "rest" => rest} = Regex.named_captures(@safe_mention_regex, text) - AutoLinker.link(mentions, options) <> AutoLinker.link(rest, options) + Linkify.link(mentions, options) <> Linkify.link(rest, options) else - AutoLinker.link(text, options) + Linkify.link(text, options) end end diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex index 3d56d50a9..e9f54c4c0 100644 --- a/lib/pleroma/gopher/server.ex +++ b/lib/pleroma/gopher/server.ex @@ -96,16 +96,18 @@ def response("") do def response("/main/public") do posts = - ActivityPub.fetch_public_activities(%{"type" => ["Create"], "local_only" => true}) - |> render_activities + %{type: ["Create"], local_only: true} + |> ActivityPub.fetch_public_activities() + |> render_activities() info("Welcome to the Public Timeline!") <> posts <> ".\r\n" end def response("/main/all") do posts = - ActivityPub.fetch_public_activities(%{"type" => ["Create"]}) - |> render_activities + %{type: ["Create"]} + |> ActivityPub.fetch_public_activities() + |> render_activities() info("Welcome to the Federated Timeline!") <> posts <> ".\r\n" end @@ -130,13 +132,14 @@ def response("/notices/" <> id) do def response("/users/" <> nickname) do with %User{} = user <- User.get_cached_by_nickname(nickname) do params = %{ - "type" => ["Create"], - "actor_id" => user.ap_id + type: ["Create"], + actor_id: user.ap_id } activities = - ActivityPub.fetch_public_activities(params) - |> render_activities + params + |> ActivityPub.fetch_public_activities() + |> render_activities() info("Posts by #{user.nickname}") <> activities <> ".\r\n" else diff --git a/lib/pleroma/gun/api.ex b/lib/pleroma/gun/api.ex index f51cd7db8..09be74392 100644 --- a/lib/pleroma/gun/api.ex +++ b/lib/pleroma/gun/api.ex @@ -19,7 +19,8 @@ defmodule Pleroma.Gun.API do :tls_opts, :tcp_opts, :socks_opts, - :ws_opts + :ws_opts, + :supervise ] @impl Gun diff --git a/lib/pleroma/gun/conn.ex b/lib/pleroma/gun/conn.ex index cd25a2e74..a3f75a4bb 100644 --- a/lib/pleroma/gun/conn.ex +++ b/lib/pleroma/gun/conn.ex @@ -3,85 +3,33 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Gun.Conn do - @moduledoc """ - Struct for gun connection data - """ alias Pleroma.Gun - alias Pleroma.Pool.Connections require Logger - @type gun_state :: :up | :down - @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 + def open(%URI{} = uri, opts) do pool_opts = Pleroma.Config.get([:connections_pool], []) opts = opts |> 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(:supervise, false) |> maybe_add_tls_opts(uri) - key = "#{uri.scheme}:#{uri.host}:#{uri.port}" - - 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 + do_open(uri, opts) end 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 = [ verify: :verify_peer, cacertfile: CAStore.file_path(), depth: 20, reuse_sessions: false, - verify_fun: - {&:ssl_verify_hostname.verify_fun/3, - [check_hostname: Pleroma.HTTP.Connection.format_host(host)]} + log_level: :warning, + customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)] ] tls_opts = @@ -105,7 +53,7 @@ defp do_open(uri, %{proxy: {proxy_host, proxy_port}} = opts) do {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]), stream <- Gun.connect(conn, connect_opts), {:response, :fin, 200, _} <- Gun.await(conn, stream) do - conn + {:ok, conn} else error -> Logger.warn( @@ -141,7 +89,7 @@ defp do_open(uri, %{proxy: {proxy_type, proxy_host, proxy_port}} = opts) do with {:ok, conn} <- Gun.open(proxy_host, proxy_port, opts), {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do - conn + {:ok, conn} else error -> Logger.warn( @@ -155,11 +103,11 @@ defp do_open(uri, %{proxy: {proxy_type, proxy_host, proxy_port}} = opts) do end 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), {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do - conn + {:ok, conn} else error -> Logger.warn( @@ -171,7 +119,7 @@ defp do_open(%URI{host: host, port: port} = uri, opts) do end 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} end @@ -181,17 +129,6 @@ defp add_http2_opts(opts, "https", tls_opts) do 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 "#{scheme}://#{host}#{path}" end diff --git a/lib/pleroma/gun/connection_pool.ex b/lib/pleroma/gun/connection_pool.ex new file mode 100644 index 000000000..8b41a668c --- /dev/null +++ b/lib/pleroma/gun/connection_pool.ex @@ -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 diff --git a/lib/pleroma/gun/connection_pool/reclaimer.ex b/lib/pleroma/gun/connection_pool/reclaimer.ex new file mode 100644 index 000000000..cea800882 --- /dev/null +++ b/lib/pleroma/gun/connection_pool/reclaimer.ex @@ -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 diff --git a/lib/pleroma/gun/connection_pool/worker.ex b/lib/pleroma/gun/connection_pool/worker.ex new file mode 100644 index 000000000..f33447cb6 --- /dev/null +++ b/lib/pleroma/gun/connection_pool/worker.ex @@ -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 diff --git a/lib/pleroma/gun/connection_pool/worker_supervisor.ex b/lib/pleroma/gun/connection_pool/worker_supervisor.ex new file mode 100644 index 000000000..39615c956 --- /dev/null +++ b/lib/pleroma/gun/connection_pool/worker_supervisor.ex @@ -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 diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex index 510722ff9..9ec3836b0 100644 --- a/lib/pleroma/http/adapter_helper.ex +++ b/lib/pleroma/http/adapter_helper.ex @@ -3,32 +3,30 @@ # SPDX-License-Identifier: AGPL-3.0-only 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 :: {Connection.host(), pos_integer()} | {Connection.proxy_type(), Connection.host(), pos_integer()} @callback options(keyword(), URI.t()) :: keyword() - @callback after_request(keyword()) :: :ok - - @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 + @callback get_conn(URI.t(), keyword()) :: {:ok, term()} | {:error, term()} @spec format_proxy(String.t() | tuple() | nil) :: proxy() | nil def format_proxy(nil), do: nil 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, type, host, port} -> {type, host, port} _ -> nil @@ -38,4 +36,105 @@ def format_proxy(proxy_url) do @spec maybe_add_proxy(keyword(), proxy() | nil) :: keyword() def maybe_add_proxy(opts, nil), do: opts 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 diff --git a/lib/pleroma/http/adapter_helper/default.ex b/lib/pleroma/http/adapter_helper/default.ex new file mode 100644 index 000000000..e13441316 --- /dev/null +++ b/lib/pleroma/http/adapter_helper/default.ex @@ -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 diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex index ead7cdc6b..b4ff8306c 100644 --- a/lib/pleroma/http/adapter_helper/gun.ex +++ b/lib/pleroma/http/adapter_helper/gun.ex @@ -5,8 +5,8 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do @behaviour Pleroma.HTTP.AdapterHelper + alias Pleroma.Gun.ConnectionPool alias Pleroma.HTTP.AdapterHelper - alias Pleroma.Pool.Connections require Logger @@ -14,7 +14,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do connect_timeout: 5_000, domain_lookup_timeout: 5_000, tls_handshake_timeout: 5_000, - retry: 1, + retry: 0, retry_timeout: 1000, await_up_timeout: 5_000 ] @@ -31,16 +31,7 @@ def options(incoming_opts \\ [], %URI{} = uri) do |> Keyword.merge(config_opts) |> add_scheme_opts(uri) |> AdapterHelper.maybe_add_proxy(proxy) - |> maybe_get_conn(uri, 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 + |> Keyword.merge(incoming_opts) end defp add_scheme_opts(opts, %{scheme: "http"}), do: opts @@ -48,30 +39,40 @@ defp add_scheme_opts(opts, %{scheme: "http"}), do: opts defp add_scheme_opts(opts, %{scheme: "https"}) do opts |> Keyword.put(:certificates_verification, true) - |> Keyword.put(:tls_opts, log_level: :warning) end - defp maybe_get_conn(adapter_opts, uri, incoming_opts) do - {receive_conn?, opts} = - adapter_opts - |> Keyword.merge(incoming_opts) - |> Keyword.pop(:receive_conn, true) - - if Connections.alive?(:gun_connections) and receive_conn? do - checkin_conn(uri, opts) - else - opts + @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} | {:error, atom()} + def get_conn(uri, opts) do + case ConnectionPool.get_conn(uri, opts) do + {:ok, conn_pid} -> {:ok, Keyword.merge(opts, conn: conn_pid, close_conn: false)} + err -> err end end - defp checkin_conn(uri, opts) do - case Connections.checkin(uri, :gun_connections) do - nil -> - Task.start(Pleroma.Gun.Conn, :open, [uri, :gun_connections, opts]) - opts + @prefix Pleroma.Gun.ConnectionPool + def limiter_setup do + wait = Pleroma.Config.get([:connections_pool, :connection_acquisition_wait]) + retries = Pleroma.Config.get([:connections_pool, :connection_acquisition_retries]) - conn when is_pid(conn) -> - Keyword.merge(opts, conn: conn, close_conn: false) - end + :pools + |> Pleroma.Config.get([]) + |> 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 diff --git a/lib/pleroma/http/adapter_helper/hackney.ex b/lib/pleroma/http/adapter_helper/hackney.ex index 3972a03a9..cd569422b 100644 --- a/lib/pleroma/http/adapter_helper/hackney.ex +++ b/lib/pleroma/http/adapter_helper/hackney.ex @@ -24,5 +24,6 @@ def options(connection_opts \\ [], %URI{} = uri) do 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 diff --git a/lib/pleroma/http/connection.ex b/lib/pleroma/http/connection.ex deleted file mode 100644 index ebacf7902..000000000 --- a/lib/pleroma/http/connection.ex +++ /dev/null @@ -1,124 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# 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 diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http/http.ex index 66ca75367..b37b3fa89 100644 --- a/lib/pleroma/http/http.ex +++ b/lib/pleroma/http/http.ex @@ -7,7 +7,7 @@ defmodule Pleroma.HTTP do Wrapper for `Tesla.request/2`. """ - alias Pleroma.HTTP.Connection + alias Pleroma.HTTP.AdapterHelper alias Pleroma.HTTP.Request alias Pleroma.HTTP.RequestBuilder, as: Builder alias Tesla.Client @@ -60,49 +60,30 @@ def post(url, body, headers \\ [], options \\ []), {:ok, Env.t()} | {:error, any()} def request(method, url, body, headers, options) when is_binary(url) do uri = URI.parse(url) - adapter_opts = Connection.options(uri, options[:adapter] || []) - options = put_in(options[:adapter], adapter_opts) - params = options[:params] || [] - request = build_request(method, headers, options, url, body, params) + adapter_opts = AdapterHelper.options(uri, options[:adapter] || []) - adapter = Application.get_env(:tesla, :adapter) - client = Tesla.client([Tesla.Middleware.FollowRedirects], adapter) + case AdapterHelper.get_conn(uri, adapter_opts) do + {: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? = - if adapter == Tesla.Adapter.Gun && pid do - Process.alive?(pid) - else - false - end + client = Tesla.client(adapter_middlewares(adapter), adapter) - request_opts = - adapter_opts - |> Enum.into(%{}) - |> Map.put(:env, Pleroma.Config.get([:env])) - |> Map.put(:pool_alive?, pool_alive?) + maybe_limit( + fn -> + request(client, request) + end, + adapter, + adapter_opts + ) - response = request(client, request, request_opts) - - Connection.after_request(adapter_opts) - - 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 - ) + # Connection release is handled in a custom FollowRedirects middleware + err -> + err + end end @spec request(Client.t(), keyword()) :: {:ok, Env.t()} | {:error, any()} @@ -118,4 +99,19 @@ defp build_request(method, headers, options, url, body, params) do |> Builder.add_param(:query, :query, params) |> Builder.convert_to_keyword() 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 diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 32bcfcaba..0b171563b 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -571,10 +571,7 @@ def skip?(%Activity{} = activity, %User{} = user) do [ :self, :invisible, - :followers, - :follows, - :non_followers, - :non_follows, + :block_from_strangers, :recently_followed, :filtered ] @@ -595,45 +592,15 @@ def skip?(:invisible, %Activity{} = activity, _) do end def skip?( - :followers, + :block_from_strangers, %Activity{} = activity, - %User{notification_settings: %{followers: false}} = 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 + %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?( - :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 def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity, %User{} = user) do actor = activity.data["actor"] diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 3e2949ee2..e74c87269 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -124,6 +124,10 @@ def fetch_object_from_id!(id, options \\ []) do {:error, "Object has been deleted"} -> nil + {:reject, reason} -> + Logger.info("Rejected #{id} while fetching: #{inspect(reason)}") + nil + e -> Logger.error("Error while fetching #{id}: #{inspect(e)}") nil diff --git a/lib/pleroma/pool/connections.ex b/lib/pleroma/pool/connections.ex deleted file mode 100644 index acafe1bea..000000000 --- a/lib/pleroma/pool/connections.ex +++ /dev/null @@ -1,283 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# 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 diff --git a/lib/pleroma/pool/pool.ex b/lib/pleroma/pool/pool.ex deleted file mode 100644 index 21a6fbbc5..000000000 --- a/lib/pleroma/pool/pool.ex +++ /dev/null @@ -1,22 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# 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 diff --git a/lib/pleroma/pool/request.ex b/lib/pleroma/pool/request.ex deleted file mode 100644 index 3fb930db7..000000000 --- a/lib/pleroma/pool/request.ex +++ /dev/null @@ -1,65 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# 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 diff --git a/lib/pleroma/pool/supervisor.ex b/lib/pleroma/pool/supervisor.ex deleted file mode 100644 index faf646cb2..000000000 --- a/lib/pleroma/pool/supervisor.ex +++ /dev/null @@ -1,42 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# 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 diff --git a/lib/pleroma/reverse_proxy/client/tesla.ex b/lib/pleroma/reverse_proxy/client/tesla.ex index e81ea8bde..65785445d 100644 --- a/lib/pleroma/reverse_proxy/client/tesla.ex +++ b/lib/pleroma/reverse_proxy/client/tesla.ex @@ -48,7 +48,7 @@ def stream_body(%{pid: pid, opts: opts, fin: true}) do # if there were redirects we need to checkout old 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 end diff --git a/lib/pleroma/telemetry/logger.ex b/lib/pleroma/telemetry/logger.ex new file mode 100644 index 000000000..4cacae02f --- /dev/null +++ b/lib/pleroma/telemetry/logger.ex @@ -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 diff --git a/lib/pleroma/tesla/middleware/follow_redirects.ex b/lib/pleroma/tesla/middleware/follow_redirects.ex new file mode 100644 index 000000000..5a7032215 --- /dev/null +++ b/lib/pleroma/tesla/middleware/follow_redirects.ex @@ -0,0 +1,110 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2015-2020 Tymon Tobolski +# Copyright © 2020 Pleroma Authors +# 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 diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 23288d434..0138775bc 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -736,21 +736,25 @@ def post_register_action(%User{} = user) do end end - def try_send_confirmation_email(%User{} = user) do - if user.confirmation_pending && - Config.get([:instance, :account_activation_required]) do - user - |> Pleroma.Emails.UserEmail.account_confirmation_email() - |> Pleroma.Emails.Mailer.deliver_async() - + @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop} + def try_send_confirmation_email(%User{confirmation_pending: true} = user) do + if Config.get([:instance, :account_activation_required]) do + send_confirmation_email(user) {:ok, :enqueued} else {:ok, :noop} end end - def try_send_confirmation_email(users) do - Enum.each(users, &try_send_confirmation_email/1) + def try_send_confirmation_email(_), do: {:ok, :noop} + + @spec send_confirmation_email(Uset.t()) :: User.t() + def send_confirmation_email(%User{} = user) do + user + |> Pleroma.Emails.UserEmail.account_confirmation_email() + |> Pleroma.Emails.Mailer.deliver_async() + + user end def needs_update?(%User{local: true}), do: false diff --git a/lib/pleroma/user/notification_setting.ex b/lib/pleroma/user/notification_setting.ex index 4bd55e139..7d9e8a000 100644 --- a/lib/pleroma/user/notification_setting.ex +++ b/lib/pleroma/user/notification_setting.ex @@ -10,21 +10,15 @@ defmodule Pleroma.User.NotificationSetting do @primary_key false embedded_schema do - field(:followers, :boolean, default: true) - field(:follows, :boolean, default: true) - field(:non_follows, :boolean, default: true) - field(:non_followers, :boolean, default: true) - field(:privacy_option, :boolean, default: false) + field(:block_from_strangers, :boolean, default: false) + field(:hide_notification_contents, :boolean, default: false) end def changeset(schema, params) do schema |> cast(prepare_attrs(params), [ - :followers, - :follows, - :non_follows, - :non_followers, - :privacy_option + :block_from_strangers, + :hide_notification_contents ]) end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index bc7b5d95a..a4db1d87c 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1370,6 +1370,10 @@ def fetch_and_prepare_user_from_ap_id(ap_id) do Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}") {:error, e} + {:error, {:reject, reason} = e} -> + Logger.info("Rejected user #{ap_id}: #{inspect(reason)}") + {:error, e} + {:error, e} -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}") {:error, e} diff --git a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex index 0270b96ae..b96388489 100644 --- a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex @@ -60,7 +60,7 @@ def filter(%{"type" => "Follow", "actor" => actor_id} = message) do if score < 0.8 do {:ok, message} else - {:reject, nil} + {:reject, "[AntiFollowbotPolicy] Scored #{actor_id} as #{score}"} end end diff --git a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex index a7e187b5e..b22464111 100644 --- a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex @@ -39,14 +39,13 @@ def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message {:ok, message} {:old_user, false} -> - {:reject, nil} + {:reject, "[AntiLinkSpamPolicy] User has no posts nor followers"} {:error, _} -> - {:reject, nil} + {:reject, "[AntiLinkSpamPolicy] Failed to get or fetch user by ap_id"} e -> - Logger.warn("[MRF anti-link-spam] WTF: unhandled error #{inspect(e)}") - {:reject, nil} + {:reject, "[AntiLinkSpamPolicy] Unhandled error #{inspect(e)}"} end end diff --git a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex index f6b2c4415..9ba07b4e3 100644 --- a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex @@ -43,7 +43,7 @@ defp delist_message(message, _threshold), do: {:ok, message} defp reject_message(message, threshold) when threshold > 0 do with {_, recipients} <- get_recipient_count(message) do if recipients > threshold do - {:reject, nil} + {:reject, "[HellthreadPolicy] #{recipients} recipients is over the limit of #{threshold}"} else {:ok, message} end @@ -87,7 +87,7 @@ def filter(%{"type" => "Create", "object" => %{"type" => object_type}} = message {:ok, message} <- delist_message(message, delist_threshold) do {:ok, message} else - _e -> {:reject, nil} + e -> e end end diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex index 88b0d2b39..15e09dcf0 100644 --- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex @@ -24,7 +24,7 @@ defp check_reject(%{"object" => %{"content" => content, "summary" => summary}} = if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern -> string_matches?(content, pattern) or string_matches?(summary, pattern) end) do - {:reject, nil} + {:reject, "[KeywordPolicy] Matches with rejected keyword"} else {:ok, message} end @@ -89,8 +89,9 @@ def filter(%{"type" => "Create", "object" => %{"content" => _content}} = message {:ok, message} <- check_replace(message) do {:ok, message} else - _e -> - {:reject, nil} + {:reject, nil} -> {:reject, "[KeywordPolicy] "} + {:reject, _} = e -> e + _e -> {:reject, "[KeywordPolicy] "} end end diff --git a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex index 06f003921..7910ca131 100644 --- a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex @@ -12,8 +12,9 @@ def filter(%{"type" => "Create"} = message) do reject_actors = Pleroma.Config.get([:mrf_mention, :actors], []) recipients = (message["to"] || []) ++ (message["cc"] || []) - if Enum.any?(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do - {:reject, nil} + if rejected_mention = + Enum.find(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do + {:reject, "[MentionPolicy] Rejected for mention of #{rejected_mention}"} else {:ok, message} end diff --git a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex index a62914135..5f111c72f 100644 --- a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex @@ -28,7 +28,7 @@ defp check_date(%{"object" => %{"published" => published}} = message) do defp check_reject(message, actions) do if :reject in actions do - {:reject, nil} + {:reject, "[ObjectAgePolicy]"} else {:ok, message} end @@ -47,9 +47,8 @@ defp check_delist(message, actions) do {:ok, message} else - # Unhandleable error: somebody is messing around, just drop the message. _e -> - {:reject, nil} + {:reject, "[ObjectAgePolicy] Unhandled error"} end else {:ok, message} @@ -69,9 +68,8 @@ defp check_strip_followers(message, actions) do {:ok, message} else - # Unhandleable error: somebody is messing around, just drop the message. _e -> - {:reject, nil} + {:reject, "[ObjectAgePolicy] Unhandled error"} end else {:ok, message} diff --git a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex index 4fd63106d..0b9ed2224 100644 --- a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex +++ b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex @@ -38,7 +38,7 @@ def filter(%{"type" => "Create"} = object) do {:ok, object} true -> - {:reject, nil} + {:reject, "[RejectNonPublic] visibility: #{visibility}"} end end diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index 70a2ca053..b77b8c7b4 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -21,7 +21,7 @@ defp check_accept(%{host: actor_host} = _actor_info, object) do accepts == [] -> {:ok, object} actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object} MRF.subdomain_match?(accepts, actor_host) -> {:ok, object} - true -> {:reject, nil} + true -> {:reject, "[SimplePolicy] host not in accept list"} end end @@ -31,7 +31,7 @@ defp check_reject(%{host: actor_host} = _actor_info, object) do |> MRF.subdomains_regex() if MRF.subdomain_match?(rejects, actor_host) do - {:reject, nil} + {:reject, "[SimplePolicy] host in reject list"} else {:ok, object} end @@ -114,7 +114,7 @@ defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} |> MRF.subdomains_regex() if MRF.subdomain_match?(report_removal, actor_host) do - {:reject, nil} + {:reject, "[SimplePolicy] host in report_removal list"} else {:ok, object} end @@ -159,7 +159,7 @@ def filter(%{"type" => "Delete", "actor" => actor} = object) do |> MRF.subdomains_regex() if MRF.subdomain_match?(reject_deletes, actor_host) do - {:reject, nil} + {:reject, "[SimplePolicy] host in reject_deletes list"} else {:ok, object} end @@ -177,7 +177,9 @@ def filter(%{"actor" => actor} = object) do {:ok, object} <- check_report_removal(actor_info, object) do {:ok, object} else - _e -> {:reject, nil} + {:reject, nil} -> {:reject, "[SimplePolicy]"} + {:reject, _} = e -> e + _ -> {:reject, "[SimplePolicy]"} end end @@ -191,7 +193,9 @@ def filter(%{"id" => actor, "type" => obj_type} = object) {:ok, object} <- check_banner_removal(actor_info, object) do {:ok, object} else - _e -> {:reject, nil} + {:reject, nil} -> {:reject, "[SimplePolicy]"} + {:reject, _} = e -> e + _ -> {:reject, "[SimplePolicy]"} end end diff --git a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex index c310462cb..febabda08 100644 --- a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex @@ -134,12 +134,13 @@ defp process_tag( if user.local == true do {:ok, message} else - {:reject, nil} + {:reject, + "[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-remote-subscription"} end end - defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow"}), - do: {:reject, nil} + defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow", "actor" => actor}), + do: {:reject, "[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-any-subscription"} defp process_tag(_, message), do: {:ok, message} diff --git a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex index 651aed70f..1a28f2ba2 100644 --- a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex @@ -14,7 +14,7 @@ defp filter_by_list(%{"actor" => actor} = object, allow_list) do if actor in allow_list do {:ok, object} else - {:reject, nil} + {:reject, "[UserAllowListPolicy] #{actor} not in the list"} end end diff --git a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex index 6167a74e2..a6c545570 100644 --- a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex @@ -11,22 +11,26 @@ def filter(%{"type" => "Undo", "object" => child_message} = message) do with {:ok, _} <- filter(child_message) do {:ok, message} else - {:reject, nil} -> - {:reject, nil} + {:reject, _} = e -> e end end def filter(%{"type" => message_type} = message) do with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]), rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]), - true <- - Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type), - false <- - length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type), + {_, true} <- + {:accepted, + Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type)}, + {_, false} <- + {:rejected, + length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type)}, {:ok, _} <- filter(message["object"]) do {:ok, message} 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 diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index b70cbd043..d88f7f3ee 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -49,7 +49,8 @@ def is_representable?(%Activity{} = activity) do """ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do 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()) @@ -57,8 +58,8 @@ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = pa signature = Pleroma.Signature.sign(actor, %{ - "(request-target)": "post #{path}", - host: host, + "(request-target)": "post #{uri.path}", + host: signature_host(uri), "content-length": byte_size(json), digest: digest, date: date @@ -76,8 +77,9 @@ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = pa {"digest", digest} ] ) do - if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since], - do: Instances.set_reachable(inbox) + if not Map.has_key?(params, :unreachable_since) || params[:unreachable_since] do + Instances.set_reachable(inbox) + end result else @@ -96,6 +98,14 @@ def publish_one(%{actor_id: actor_id} = params) do |> publish_one() 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 if public do true diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index f37bcab3e..35aa05eb5 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -178,7 +178,7 @@ def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options) |> Map.drop(["conversation"]) else e -> - Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}") + Logger.warn("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}") object end else diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index dfae602df..713b0ca1f 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -719,15 +719,18 @@ defp build_flag_object(act) when is_map(act) or is_binary(act) do case Activity.get_by_ap_id_with_object(id) do %Activity{} = activity -> + activity_actor = User.get_by_ap_id(activity.object.data["actor"]) + %{ "type" => "Note", "id" => activity.data["id"], "content" => activity.object.data["content"], "published" => activity.object.data["published"], "actor" => - AccountView.render("show.json", %{ - user: User.get_by_ap_id(activity.object.data["actor"]) - }) + AccountView.render( + "show.json", + %{user: activity_actor, skip_visibility_check: true} + ) } _ -> diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index 53f71fcbf..aa2af1ab5 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -361,7 +361,11 @@ def list_users(conn, params) do with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do json( conn, - AccountView.render("index.json", users: users, count: count, page_size: page_size) + AccountView.render("index.json", + users: users, + count: count, + page_size: page_size + ) ) end end @@ -632,29 +636,24 @@ def reload_emoji(conn, _params) do end def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do - users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) + users = Enum.map(nicknames, &User.get_cached_by_nickname/1) User.toggle_confirmation(users) - ModerationLog.insert_log(%{ - actor: admin, - subject: users, - action: "confirm_email" - }) + ModerationLog.insert_log(%{actor: admin, subject: users, action: "confirm_email"}) json(conn, "") end def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do - users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) + users = + Enum.map(nicknames, fn nickname -> + nickname + |> User.get_cached_by_nickname() + |> User.send_confirmation_email() + end) - User.try_send_confirmation_email(users) - - ModerationLog.insert_log(%{ - actor: admin, - subject: users, - action: "resend_confirmation_email" - }) + ModerationLog.insert_log(%{actor: admin, subject: users, action: "resend_confirmation_email"}) json(conn, "") end diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex index bdab04ad2..333e72e42 100644 --- a/lib/pleroma/web/admin_api/views/account_view.ex +++ b/lib/pleroma/web/admin_api/views/account_view.ex @@ -107,7 +107,7 @@ def render("create-error.json", %{changeset: %Ecto.Changeset{changes: changes, e end def merge_account_views(%User{} = user) do - MastodonAPI.AccountView.render("show.json", %{user: user}) + MastodonAPI.AccountView.render("show.json", %{user: user, skip_visibility_check: true}) |> Map.merge(AdminAPI.AccountView.render("show.json", %{user: user})) end diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 952d9347b..50c8e0242 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -159,6 +159,7 @@ def followers_operation do "Accounts which follow the given account, if network is not hidden by the account owner.", parameters: [ %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, + Operation.parameter(:id, :query, :string, "ID of the resource owner"), with_relationships_param() | pagination_params() ], responses: %{ @@ -177,6 +178,7 @@ def following_operation do "Accounts which the given account is following, if network is not hidden by the account owner.", parameters: [ %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, + Operation.parameter(:id, :query, :string, "ID of the resource owner"), with_relationships_param() | pagination_params() ], responses: %{200 => Operation.response("Accounts", "application/json", array_of_accounts())} diff --git a/lib/pleroma/web/api_spec/operations/chat_operation.ex b/lib/pleroma/web/api_spec/operations/chat_operation.ex index cf299bfc2..b1a0d26ab 100644 --- a/lib/pleroma/web/api_spec/operations/chat_operation.ex +++ b/lib/pleroma/web/api_spec/operations/chat_operation.ex @@ -300,11 +300,11 @@ def chat_messages_response do "content" => "Check this out :firefox:", "id" => "13", "chat_id" => "1", - "actor_id" => "someflakeid", + "account_id" => "someflakeid", "unread" => false }, %{ - "actor_id" => "someflakeid", + "account_id" => "someflakeid", "content" => "Whats' up?", "id" => "12", "chat_id" => "1", diff --git a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex index 049bcf931..1e0da8209 100644 --- a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex +++ b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex @@ -31,6 +31,7 @@ def index_operation do } end + # Supporting domain query parameter is deprecated in Mastodon API def create_operation do %Operation{ tags: ["domain_blocks"], @@ -45,11 +46,13 @@ def create_operation do """, operationId: "DomainBlockController.create", requestBody: domain_block_request(), + parameters: [Operation.parameter(:domain, :query, %Schema{type: :string}, "Domain name")], security: [%{"oAuth" => ["follow", "write:blocks"]}], responses: %{200 => empty_object_response()} } end + # Supporting domain query parameter is deprecated in Mastodon API def delete_operation do %Operation{ tags: ["domain_blocks"], @@ -57,6 +60,7 @@ def delete_operation do description: "Remove a domain block, if it exists in the user's array of blocked domains.", operationId: "DomainBlockController.delete", requestBody: domain_block_request(), + parameters: [Operation.parameter(:domain, :query, %Schema{type: :string}, "Domain name")], security: [%{"oAuth" => ["follow", "write:blocks"]}], responses: %{ 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}) @@ -71,10 +75,9 @@ defp domain_block_request do type: :object, properties: %{ domain: %Schema{type: :string} - }, - required: [:domain] + } }, - required: true, + required: false, example: %{ "domain" => "facebook.com" } diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index cf148bc9d..ca79f0747 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -90,11 +90,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do notification_settings: %Schema{ type: :object, properties: %{ - followers: %Schema{type: :boolean}, - follows: %Schema{type: :boolean}, - non_followers: %Schema{type: :boolean}, - non_follows: %Schema{type: :boolean}, - privacy_option: %Schema{type: :boolean} + block_from_strangers: %Schema{type: :boolean}, + hide_notification_contents: %Schema{type: :boolean} } }, relationship: AccountRelationship, @@ -182,11 +179,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do "unread_conversation_count" => 0, "tags" => [], "notification_settings" => %{ - "followers" => true, - "follows" => true, - "non_followers" => true, - "non_follows" => true, - "privacy_option" => false + "block_from_strangers" => false, + "hide_notification_contents" => false }, "relationship" => %{ "blocked_by" => false, diff --git a/lib/pleroma/web/chat_channel.ex b/lib/pleroma/web/chat_channel.ex index bce27897f..3b1469c19 100644 --- a/lib/pleroma/web/chat_channel.ex +++ b/lib/pleroma/web/chat_channel.ex @@ -4,8 +4,10 @@ defmodule Pleroma.Web.ChatChannel do use Phoenix.Channel + alias Pleroma.User alias Pleroma.Web.ChatChannel.ChatChannelState + alias Pleroma.Web.MastodonAPI.AccountView def join("chat:public", _message, socket) do send(self(), :after_join) @@ -22,9 +24,9 @@ def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}} if String.length(text) in 1..Pleroma.Config.get([:instance, :chat_limit]) do author = User.get_cached_by_nickname(user_name) - author = Pleroma.Web.MastodonAPI.AccountView.render("show.json", user: author) + author_json = AccountView.render("show.json", user: author, skip_visibility_check: true) - message = ChatChannelState.add_message(%{text: text, author: author}) + message = ChatChannelState.add_message(%{text: text, author: author_json}) broadcast!(socket, "new_msg", message) end diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex index 825b231ab..9c2d093cd 100644 --- a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex @@ -32,9 +32,19 @@ def create(%{assigns: %{user: blocker}, body_params: %{domain: domain}} = conn, json(conn, %{}) end + def create(%{assigns: %{user: blocker}} = conn, %{domain: domain}) do + User.block_domain(blocker, domain) + json(conn, %{}) + end + @doc "DELETE /api/v1/domain_blocks" def delete(%{assigns: %{user: blocker}, body_params: %{domain: domain}} = conn, _params) do User.unblock_domain(blocker, domain) json(conn, %{}) end + + def delete(%{assigns: %{user: blocker}} = conn, %{domain: domain}) do + User.unblock_domain(blocker, domain) + json(conn, %{}) + end end diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index 29affa7d5..5a983db39 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -93,7 +93,6 @@ defp resource_search(_, "accounts", query, options) do AccountView.render("index.json", users: accounts, for: options[:for_user], - as: :user, embed_relationships: options[:embed_relationships] ) end diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 12be530c9..9bb2ef117 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -172,6 +172,11 @@ def create(%{assigns: %{user: user}, body_params: %{status: _} = params} = conn, with_direct_conversation_id: true ) else + {:error, {:reject, message}} -> + conn + |> put_status(:unprocessable_entity) + |> json(%{error: message}) + {:error, message} -> conn |> put_status(:unprocessable_entity) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index bc9745044..864c0417f 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -27,21 +27,40 @@ def render("index.json", %{users: users} = opts) do UserRelationship.view_relationships_option(reading_user, users) end - opts = Map.put(opts, :relationships, relationships_opt) + opts = + opts + |> Map.merge(%{relationships: relationships_opt, as: :user}) + |> Map.delete(:users) users |> render_many(AccountView, "show.json", opts) |> Enum.filter(&Enum.any?/1) end - def render("show.json", %{user: user} = opts) do - if User.visible_for(user, opts[:for]) == :visible do + @doc """ + Renders specified user account. + :skip_visibility_check option skips visibility check and renders any user (local or remote) + regardless of [:pleroma, :restrict_unauthenticated] setting. + :for option specifies the requester and can be a User record or nil. + Only use `user: user, for: user` when `user` is the actual requester of own profile. + """ + def render("show.json", %{user: _user, skip_visibility_check: true} = opts) do + do_render("show.json", opts) + end + + def render("show.json", %{user: user, for: for_user_or_nil} = opts) do + if User.visible_for(user, for_user_or_nil) == :visible do do_render("show.json", opts) else %{} end end + def render("show.json", _) do + raise "In order to prevent account accessibility issues, " <> + ":skip_visibility_check or :for option is required." + end + def render("mention.json", %{user: user}) do %{ id: to_string(user.id), diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex index 06f0c1728..a91994915 100644 --- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex +++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex @@ -38,7 +38,7 @@ def render("participation.json", %{participation: participation, for: user}) do %{ id: participation.id |> to_string(), - accounts: render(AccountView, "index.json", users: users, as: :user), + accounts: render(AccountView, "index.json", users: users, for: user), unread: !participation.read, last_status: render(StatusView, "show.json", diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 5389d63cd..ea2d3aa9c 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -42,7 +42,8 @@ def render("show.json", _) do account_activation_required: Keyword.get(instance, :account_activation_required), features: features(), 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) } diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index fa9d695f3..91b41ef59 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -297,13 +297,17 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} emoji_reactions = with %{data: %{"reactions" => emoji_reactions}} <- object do - Enum.map(emoji_reactions, fn [emoji, users] -> - %{ - name: emoji, - count: length(users), - me: !!(opts[:for] && opts[:for].ap_id in users) - } + Enum.map(emoji_reactions, fn + [emoji, users] when is_list(users) -> + build_emoji_map(emoji, users, opts[:for]) + + {emoji, users} when is_list(users) -> + build_emoji_map(emoji, users, opts[:for]) + + _ -> + nil end) + |> Enum.reject(&is_nil/1) else _ -> [] end @@ -545,4 +549,12 @@ defp present?(_), do: true defp pinned?(%Activity{id: id}, %User{pinned_activities: pinned_activities}), do: id in pinned_activities + + defp build_emoji_map(emoji, users, current_user) do + %{ + name: emoji, + count: length(users), + me: !!(current_user && current_user.ap_id in users) + } + end end diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex index c8ef3d915..e8a1746d4 100644 --- a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex @@ -89,11 +89,11 @@ def post_chat_message( cm_ref <- MessageReference.for_chat_and_object(chat, message) do conn |> put_view(MessageReferenceView) - |> render("show.json", for: user, chat_message_reference: cm_ref) + |> render("show.json", chat_message_reference: cm_ref) end end - def mark_message_as_read(%{assigns: %{user: %{id: user_id} = user}} = conn, %{ + def mark_message_as_read(%{assigns: %{user: %{id: user_id}}} = conn, %{ id: chat_id, message_id: message_id }) do @@ -104,12 +104,15 @@ def mark_message_as_read(%{assigns: %{user: %{id: user_id} = user}} = conn, %{ {:ok, cm_ref} <- MessageReference.mark_as_read(cm_ref) do conn |> put_view(MessageReferenceView) - |> render("show.json", for: user, chat_message_reference: cm_ref) + |> render("show.json", chat_message_reference: cm_ref) end end def mark_as_read( - %{body_params: %{last_read_id: last_read_id}, assigns: %{user: %{id: user_id}}} = conn, + %{ + body_params: %{last_read_id: last_read_id}, + assigns: %{user: %{id: user_id}} + } = conn, %{id: id} ) do with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id), @@ -121,7 +124,7 @@ def mark_as_read( end end - def messages(%{assigns: %{user: %{id: user_id} = user}} = conn, %{id: id} = params) do + def messages(%{assigns: %{user: %{id: user_id}}} = conn, %{id: id} = params) do with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id) do cm_refs = chat @@ -130,7 +133,7 @@ def messages(%{assigns: %{user: %{id: user_id} = user}} = conn, %{id: id} = para conn |> put_view(MessageReferenceView) - |> render("index.json", for: user, chat_message_references: cm_refs) + |> render("index.json", chat_message_references: cm_refs) else _ -> conn diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex index 33ecd1f70..657f46324 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex @@ -21,8 +21,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do ] ) - @skip_plugs [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug] - plug(:skip_plug, @skip_plugs when action in [:archive, :show, :list]) + @skip_plugs [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug] + plug(:skip_plug, @skip_plugs when action in [:index, :show, :archive]) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation diff --git a/lib/pleroma/web/pleroma_api/views/chat_view.ex b/lib/pleroma/web/pleroma_api/views/chat_view.ex index 1c996da11..04dc20d51 100644 --- a/lib/pleroma/web/pleroma_api/views/chat_view.ex +++ b/lib/pleroma/web/pleroma_api/views/chat_view.ex @@ -15,10 +15,11 @@ defmodule Pleroma.Web.PleromaAPI.ChatView do def render("show.json", %{chat: %Chat{} = chat} = opts) do recipient = User.get_cached_by_ap_id(chat.recipient) last_message = opts[:last_message] || MessageReference.last_message_for_chat(chat) + account_view_opts = account_view_opts(opts, recipient) %{ id: chat.id |> to_string(), - account: AccountView.render("show.json", Map.put(opts, :user, recipient)), + account: AccountView.render("show.json", account_view_opts), unread: MessageReference.unread_count_for_chat(chat), last_message: last_message && @@ -27,7 +28,17 @@ def render("show.json", %{chat: %Chat{} = chat} = opts) do } end - def render("index.json", %{chats: chats}) do - render_many(chats, __MODULE__, "show.json") + def render("index.json", %{chats: chats} = opts) do + render_many(chats, __MODULE__, "show.json", Map.delete(opts, :chats)) + end + + defp account_view_opts(opts, recipient) do + account_view_opts = Map.put(opts, :user, recipient) + + if Map.has_key?(account_view_opts, :for) do + account_view_opts + else + Map.put(account_view_opts, :skip_visibility_check, true) + end end end diff --git a/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex index 84d2d303d..e0f98b50a 100644 --- a/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex +++ b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex @@ -17,7 +17,7 @@ def render("show.json", %{emoji_reaction: [emoji, user_ap_ids], user: user}) do %{ name: emoji, count: length(users), - accounts: render(AccountView, "index.json", users: users, for: user, as: :user), + accounts: render(AccountView, "index.json", users: users, for: user), me: !!(user && user.ap_id in user_ap_ids) } end diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index cdb827e76..16368485e 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -104,7 +104,7 @@ def build_content(notification, actor, object, mastodon_type \\ nil) def build_content( %{ - user: %{notification_settings: %{privacy_option: true}} + user: %{notification_settings: %{hide_notification_contents: true}} } = notification, _actor, _object, diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 1729141e9..747f2dc6b 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -11,10 +11,10 @@ defmodule Pleroma.Web.RichMedia.Helpers do @spec validate_page_url(URI.t() | binary()) :: :ok | :error defp validate_page_url(page_url) when is_binary(page_url) do - validate_tld = Application.get_env(:auto_linker, :opts)[:validate_tld] + validate_tld = Pleroma.Config.get([Pleroma.Formatter, :validate_tld]) page_url - |> AutoLinker.Parser.url?(scheme: true, validate_tld: validate_tld) + |> Linkify.Parser.url?(validate_tld: validate_tld) |> parse_uri(page_url) end diff --git a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex index 750f65386..5ab59b57b 100644 --- a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex +++ b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex @@ -10,7 +10,7 @@ <%= form_for @conn, mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
<%= 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, :state, value: @state %> <%= hidden_input f, :redirect_uri, value: @redirect_uri %> diff --git a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex index af6e546b0..af85777eb 100644 --- a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex +++ b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex @@ -10,7 +10,7 @@ <%= form_for @conn, mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
<%= 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, :state, value: @state %> <%= hidden_input f, :redirect_uri, value: @redirect_uri %> diff --git a/mix.exs b/mix.exs index 741f917e6..da0e88287 100644 --- a/mix.exs +++ b/mix.exs @@ -135,13 +135,11 @@ defp deps do {:poison, "~> 3.0", override: true}, # {:tesla, "~> 1.3", override: true}, {:tesla, - git: "https://git.pleroma.social/pleroma/elixir-libraries/tesla.git", - ref: "61b7503cef33f00834f78ddfafe0d5d9dec2270b", - override: true}, + github: "teamon/tesla", ref: "af3707078b10793f6a534938e56b963aff82fe3c", override: true}, {:castore, "~> 0.1"}, {:cowlib, "~> 2.8", override: true}, {:gun, - github: "ninenines/gun", ref: "e1a69b36b180a574c0ac314ced9613fdd52312cc", override: true}, + github: "ninenines/gun", ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", override: true}, {:jason, "~> 1.0"}, {:mogrify, "~> 0.6.1"}, {:ex_aws, "~> 2.1"}, @@ -153,12 +151,13 @@ defp deps do {:credo, "~> 1.1.0", only: [:dev, :test], runtime: false}, {:mock, "~> 0.3.3", only: :test}, {:crypt, - git: "https://github.com/msantos/crypt", ref: "f63a705f92c26955977ee62a313012e309a4d77a"}, + git: "https://github.com/msantos/crypt.git", + ref: "f63a705f92c26955977ee62a313012e309a4d77a"}, {:cors_plug, "~> 1.5"}, {:ex_doc, "~> 0.21", only: :dev, runtime: false}, {:web_push_encryption, "~> 0.2.1"}, {:swoosh, - git: "https://github.com/swoosh/swoosh", + git: "https://github.com/swoosh/swoosh.git", ref: "c96e0ca8a00d8f211ec1f042a4626b09f249caa5", override: true}, {:phoenix_swoosh, "~> 0.2"}, @@ -168,9 +167,7 @@ defp deps do {:floki, "~> 0.25"}, {:timex, "~> 3.5"}, {:ueberauth, "~> 0.4"}, - {:auto_linker, - git: "https://git.pleroma.social/pleroma/auto_linker.git", - ref: "95e8188490e97505c56636c1379ffdf036c1fdde"}, + {:linkify, "~> 0.2.0"}, {:http_signatures, git: "https://git.pleroma.social/pleroma/http_signatures.git", ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"}, @@ -191,6 +188,9 @@ defp deps do {:plug_static_index_html, "~> 1.0.0"}, {:excoveralls, "~> 0.12.1", only: :test}, {:flake_id, "~> 0.1.0"}, + {:concurrent_limiter, + git: "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", + ref: "8eee96c6ba39b9286ec44c51c52d9f2758951365"}, {:remote_ip, git: "https://git.pleroma.social/pleroma/remote_ip.git", ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"}, diff --git a/mix.lock b/mix.lock index f801f9e0c..80679cded 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,5 @@ %{ "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"}, - "auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "95e8188490e97505c56636c1379ffdf036c1fdde", [ref: "95e8188490e97505c56636c1379ffdf036c1fdde"]}, "base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "3b29948de2013d3f93aa898c884a9dff847e7aec75d9d6d8c1dc4c61c2716c42"}, "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, "bbcode": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/bbcode.git", "f2d267675e9a7e1ad1ea9beb4cc23382762b66c2", [ref: "v0.2.0"]}, @@ -15,13 +14,14 @@ "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"}, "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"}, + "concurrent_limiter": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", "8eee96c6ba39b9286ec44c51c52d9f2758951365", [ref: "8eee96c6ba39b9286ec44c51c52d9f2758951365"]}, "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"}, "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"}, "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"}, "credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d0bbd3222607ccaaac5c0340f7f525c627ae4d7aee6c8c8c108922620c5b6446"}, "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, - "crypt": {:git, "https://github.com/msantos/crypt", "f63a705f92c26955977ee62a313012e309a4d77a", [ref: "f63a705f92c26955977ee62a313012e309a4d77a"]}, + "crypt": {:git, "https://github.com/msantos/crypt.git", "f63a705f92c26955977ee62a313012e309a4d77a", [ref: "f63a705f92c26955977ee62a313012e309a4d77a"]}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"}, "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, @@ -49,7 +49,7 @@ "gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [: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"}, - "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"}, "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"}, @@ -62,6 +62,7 @@ "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"}, "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"}, + "linkify": {:hex, :linkify, "0.2.0", "2518bbbea21d2caa9d372424e1ad845b640c6630e2d016f1bd1f518f9ebcca28", [:mix], [], "hexpm", "b8ca8a68b79e30b7938d6c996085f3db14939f29538a59ca5101988bb7f917f6"}, "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"}, "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"}, @@ -104,10 +105,10 @@ "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"}, - "swoosh": {:git, "https://github.com/swoosh/swoosh", "c96e0ca8a00d8f211ec1f042a4626b09f249caa5", [ref: "c96e0ca8a00d8f211ec1f042a4626b09f249caa5"]}, + "swoosh": {:git, "https://github.com/swoosh/swoosh.git", "c96e0ca8a00d8f211ec1f042a4626b09f249caa5", [ref: "c96e0ca8a00d8f211ec1f042a4626b09f249caa5"]}, "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, "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"}, "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"}, diff --git a/priv/repo/migrations/20200626163359_rename_notification_privacy_option.exs b/priv/repo/migrations/20200626163359_rename_notification_privacy_option.exs new file mode 100644 index 000000000..06d7f7272 --- /dev/null +++ b/priv/repo/migrations/20200626163359_rename_notification_privacy_option.exs @@ -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 diff --git a/priv/repo/migrations/20200716195806_autolinker_to_linkify.exs b/priv/repo/migrations/20200716195806_autolinker_to_linkify.exs new file mode 100644 index 000000000..570acba84 --- /dev/null +++ b/priv/repo/migrations/20200716195806_autolinker_to_linkify.exs @@ -0,0 +1,36 @@ +defmodule Pleroma.Repo.Migrations.AutolinkerToLinkify do + use Ecto.Migration + alias Pleroma.ConfigDB + + @autolinker_path %{group: :auto_linker, key: :opts} + @linkify_path %{group: :pleroma, key: Pleroma.Formatter} + + @compat_opts [:class, :rel, :new_window, :truncate, :strip_prefix, :extra] + + def change do + with {:ok, {old, new}} <- maybe_get_params() do + move_config(old, new) + end + end + + defp move_config(%{} = old, %{} = new) do + {:ok, _} = ConfigDB.update_or_create(new) + {:ok, _} = ConfigDB.delete(old) + :ok + end + + defp maybe_get_params() do + with %ConfigDB{value: opts} <- ConfigDB.get_by_params(@autolinker_path), + opts <- transform_opts(opts), + %{} = linkify_params <- Map.put(@linkify_path, :value, opts) do + {:ok, {@autolinker_path, linkify_params}} + end + end + + def transform_opts(opts) when is_list(opts) do + opts + |> Enum.into(%{}) + |> Map.take(@compat_opts) + |> Map.to_list() + end +end diff --git a/priv/repo/migrations/20200722185515_fix_malformed_formatter_config.exs b/priv/repo/migrations/20200722185515_fix_malformed_formatter_config.exs new file mode 100644 index 000000000..77b760825 --- /dev/null +++ b/priv/repo/migrations/20200722185515_fix_malformed_formatter_config.exs @@ -0,0 +1,26 @@ +defmodule Pleroma.Repo.Migrations.FixMalformedFormatterConfig do + use Ecto.Migration + alias Pleroma.ConfigDB + + @config_path %{group: :pleroma, key: Pleroma.Formatter} + + def change do + with %ConfigDB{value: %{} = opts} <- ConfigDB.get_by_params(@config_path), + fixed_opts <- Map.to_list(opts) do + fix_config(fixed_opts) + else + _ -> :skipped + end + end + + defp fix_config(fixed_opts) when is_list(fixed_opts) do + {:ok, _} = + ConfigDB.update_or_create(%{ + group: :pleroma, + key: Pleroma.Formatter, + value: fixed_opts + }) + + :ok + end +end diff --git a/priv/static/index.html b/priv/static/index.html index 80820166a..2257dec35 100644 --- a/priv/static/index.html +++ b/priv/static/index.html @@ -1 +1 @@ -Pleroma
\ No newline at end of file +Pleroma
\ No newline at end of file diff --git a/priv/static/static/css/app.77b1644622e3bae24b6b.css b/priv/static/static/css/app.6dbc7dea4fc148c85860.css similarity index 99% rename from priv/static/static/css/app.77b1644622e3bae24b6b.css rename to priv/static/static/css/app.6dbc7dea4fc148c85860.css index 8038882c0..3927e3b77 100644 Binary files a/priv/static/static/css/app.77b1644622e3bae24b6b.css and b/priv/static/static/css/app.6dbc7dea4fc148c85860.css differ diff --git a/priv/static/static/css/app.77b1644622e3bae24b6b.css.map b/priv/static/static/css/app.6dbc7dea4fc148c85860.css.map similarity index 99% rename from priv/static/static/css/app.77b1644622e3bae24b6b.css.map rename to priv/static/static/css/app.6dbc7dea4fc148c85860.css.map index 4b042ef35..963d5b3b8 100644 --- a/priv/static/static/css/app.77b1644622e3bae24b6b.css.map +++ b/priv/static/static/css/app.6dbc7dea4fc148c85860.css.map @@ -1 +1 @@ -{"version":3,"sources":["webpack:///./src/components/tab_switcher/tab_switcher.scss","webpack:///./src/hocs/with_load_more/with_load_more.scss"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;ACtOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C","file":"static/css/app.77b1644622e3bae24b6b.css","sourcesContent":[".tab-switcher {\n display: -ms-flexbox;\n display: flex;\n}\n.tab-switcher .tab-icon {\n font-size: 2em;\n display: block;\n}\n.tab-switcher.top-tabs {\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher.top-tabs > .tabs {\n width: 100%;\n overflow-y: hidden;\n overflow-x: auto;\n padding-top: 5px;\n -ms-flex-direction: row;\n flex-direction: row;\n}\n.tab-switcher.top-tabs > .tabs::after, .tab-switcher.top-tabs > .tabs::before {\n content: \"\";\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher.top-tabs > .tabs .tab-wrapper {\n height: 28px;\n}\n.tab-switcher.top-tabs > .tabs .tab-wrapper:not(.active)::after {\n left: 0;\n right: 0;\n bottom: 0;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher.top-tabs > .tabs .tab {\n width: 100%;\n min-width: 1px;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n padding-bottom: 99px;\n margin-bottom: -93px;\n}\n.tab-switcher.top-tabs .contents.scrollable-tabs {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n}\n.tab-switcher.side-tabs {\n -ms-flex-direction: row;\n flex-direction: row;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs {\n overflow-x: auto;\n }\n}\n.tab-switcher.side-tabs > .contents {\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n.tab-switcher.side-tabs > .tabs {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n overflow-y: auto;\n overflow-x: hidden;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher.side-tabs > .tabs::after, .tab-switcher.side-tabs > .tabs::before {\n -ms-flex-negative: 0;\n flex-shrink: 0;\n -ms-flex-preferred-size: 0.5em;\n flex-basis: 0.5em;\n content: \"\";\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs::after {\n -ms-flex-positive: 1;\n flex-grow: 1;\n}\n.tab-switcher.side-tabs > .tabs::before {\n -ms-flex-positive: 0;\n flex-grow: 0;\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper {\n min-width: 10em;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs > .tabs .tab-wrapper {\n min-width: 1em;\n }\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper:not(.active)::after {\n top: 0;\n right: 0;\n bottom: 0;\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper::before {\n -ms-flex: 0 0 6px;\n flex: 0 0 6px;\n content: \"\";\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper:last-child .tab {\n margin-bottom: 0;\n}\n.tab-switcher.side-tabs > .tabs .tab {\n -ms-flex: 1;\n flex: 1;\n box-sizing: content-box;\n min-width: 10em;\n min-width: 1px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n padding-left: 1em;\n padding-right: calc(1em + 200px);\n margin-right: -200px;\n margin-left: 1em;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs > .tabs .tab {\n padding-left: 0.25em;\n padding-right: calc(.25em + 200px);\n margin-right: calc(.25em - 200px);\n margin-left: 0.25em;\n }\n .tab-switcher.side-tabs > .tabs .tab .text {\n display: none;\n }\n}\n.tab-switcher .contents {\n -ms-flex: 1 0 auto;\n flex: 1 0 auto;\n min-height: 0px;\n}\n.tab-switcher .contents .hidden {\n display: none;\n}\n.tab-switcher .contents .full-height:not(.hidden) {\n height: 100%;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher .contents .full-height:not(.hidden) > *:not(.mobile-label) {\n -ms-flex: 1;\n flex: 1;\n}\n.tab-switcher .contents.scrollable-tabs {\n overflow-y: auto;\n}\n.tab-switcher .tab {\n position: relative;\n white-space: nowrap;\n padding: 6px 1em;\n background-color: #182230;\n background-color: var(--tab, #182230);\n}\n.tab-switcher .tab, .tab-switcher .tab:active .tab-icon {\n color: #b9b9ba;\n color: var(--tabText, #b9b9ba);\n}\n.tab-switcher .tab:not(.active) {\n z-index: 4;\n}\n.tab-switcher .tab:not(.active):hover {\n z-index: 6;\n}\n.tab-switcher .tab.active {\n background: transparent;\n z-index: 5;\n color: #b9b9ba;\n color: var(--tabActiveText, #b9b9ba);\n}\n.tab-switcher .tab img {\n max-height: 26px;\n vertical-align: top;\n margin-top: -5px;\n}\n.tab-switcher .tabs {\n display: -ms-flexbox;\n display: flex;\n position: relative;\n box-sizing: border-box;\n}\n.tab-switcher .tabs::after, .tab-switcher .tabs::before {\n display: block;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n.tab-switcher .tab-wrapper {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n}\n.tab-switcher .tab-wrapper:not(.active)::after {\n content: \"\";\n position: absolute;\n z-index: 7;\n}\n.tab-switcher .mobile-label {\n padding-left: 0.3em;\n padding-bottom: 0.25em;\n margin-top: 0.5em;\n margin-left: 0.2em;\n margin-bottom: 0.25em;\n border-bottom: 1px solid var(--border, #222);\n}\n@media all and (min-width: 800px) {\n .tab-switcher .mobile-label {\n display: none;\n }\n}",".with-load-more-footer {\n padding: 10px;\n text-align: center;\n border-top: 1px solid;\n border-top-color: #222;\n border-top-color: var(--border, #222);\n}\n.with-load-more-footer .error {\n font-size: 14px;\n}\n.with-load-more-footer a {\n cursor: pointer;\n}"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack:///./src/components/tab_switcher/tab_switcher.scss","webpack:///./src/hocs/with_load_more/with_load_more.scss"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;ACtOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C","file":"static/css/app.6dbc7dea4fc148c85860.css","sourcesContent":[".tab-switcher {\n display: -ms-flexbox;\n display: flex;\n}\n.tab-switcher .tab-icon {\n font-size: 2em;\n display: block;\n}\n.tab-switcher.top-tabs {\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher.top-tabs > .tabs {\n width: 100%;\n overflow-y: hidden;\n overflow-x: auto;\n padding-top: 5px;\n -ms-flex-direction: row;\n flex-direction: row;\n}\n.tab-switcher.top-tabs > .tabs::after, .tab-switcher.top-tabs > .tabs::before {\n content: \"\";\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher.top-tabs > .tabs .tab-wrapper {\n height: 28px;\n}\n.tab-switcher.top-tabs > .tabs .tab-wrapper:not(.active)::after {\n left: 0;\n right: 0;\n bottom: 0;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher.top-tabs > .tabs .tab {\n width: 100%;\n min-width: 1px;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n padding-bottom: 99px;\n margin-bottom: -93px;\n}\n.tab-switcher.top-tabs .contents.scrollable-tabs {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n}\n.tab-switcher.side-tabs {\n -ms-flex-direction: row;\n flex-direction: row;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs {\n overflow-x: auto;\n }\n}\n.tab-switcher.side-tabs > .contents {\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n.tab-switcher.side-tabs > .tabs {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n overflow-y: auto;\n overflow-x: hidden;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher.side-tabs > .tabs::after, .tab-switcher.side-tabs > .tabs::before {\n -ms-flex-negative: 0;\n flex-shrink: 0;\n -ms-flex-preferred-size: 0.5em;\n flex-basis: 0.5em;\n content: \"\";\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs::after {\n -ms-flex-positive: 1;\n flex-grow: 1;\n}\n.tab-switcher.side-tabs > .tabs::before {\n -ms-flex-positive: 0;\n flex-grow: 0;\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper {\n min-width: 10em;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs > .tabs .tab-wrapper {\n min-width: 1em;\n }\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper:not(.active)::after {\n top: 0;\n right: 0;\n bottom: 0;\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper::before {\n -ms-flex: 0 0 6px;\n flex: 0 0 6px;\n content: \"\";\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper:last-child .tab {\n margin-bottom: 0;\n}\n.tab-switcher.side-tabs > .tabs .tab {\n -ms-flex: 1;\n flex: 1;\n box-sizing: content-box;\n min-width: 10em;\n min-width: 1px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n padding-left: 1em;\n padding-right: calc(1em + 200px);\n margin-right: -200px;\n margin-left: 1em;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs > .tabs .tab {\n padding-left: 0.25em;\n padding-right: calc(.25em + 200px);\n margin-right: calc(.25em - 200px);\n margin-left: 0.25em;\n }\n .tab-switcher.side-tabs > .tabs .tab .text {\n display: none;\n }\n}\n.tab-switcher .contents {\n -ms-flex: 1 0 auto;\n flex: 1 0 auto;\n min-height: 0px;\n}\n.tab-switcher .contents .hidden {\n display: none;\n}\n.tab-switcher .contents .full-height:not(.hidden) {\n height: 100%;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher .contents .full-height:not(.hidden) > *:not(.mobile-label) {\n -ms-flex: 1;\n flex: 1;\n}\n.tab-switcher .contents.scrollable-tabs {\n overflow-y: auto;\n}\n.tab-switcher .tab {\n position: relative;\n white-space: nowrap;\n padding: 6px 1em;\n background-color: #182230;\n background-color: var(--tab, #182230);\n}\n.tab-switcher .tab, .tab-switcher .tab:active .tab-icon {\n color: #b9b9ba;\n color: var(--tabText, #b9b9ba);\n}\n.tab-switcher .tab:not(.active) {\n z-index: 4;\n}\n.tab-switcher .tab:not(.active):hover {\n z-index: 6;\n}\n.tab-switcher .tab.active {\n background: transparent;\n z-index: 5;\n color: #b9b9ba;\n color: var(--tabActiveText, #b9b9ba);\n}\n.tab-switcher .tab img {\n max-height: 26px;\n vertical-align: top;\n margin-top: -5px;\n}\n.tab-switcher .tabs {\n display: -ms-flexbox;\n display: flex;\n position: relative;\n box-sizing: border-box;\n}\n.tab-switcher .tabs::after, .tab-switcher .tabs::before {\n display: block;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n.tab-switcher .tab-wrapper {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n}\n.tab-switcher .tab-wrapper:not(.active)::after {\n content: \"\";\n position: absolute;\n z-index: 7;\n}\n.tab-switcher .mobile-label {\n padding-left: 0.3em;\n padding-bottom: 0.25em;\n margin-top: 0.5em;\n margin-left: 0.2em;\n margin-bottom: 0.25em;\n border-bottom: 1px solid var(--border, #222);\n}\n@media all and (min-width: 800px) {\n .tab-switcher .mobile-label {\n display: none;\n }\n}",".with-load-more-footer {\n padding: 10px;\n text-align: center;\n border-top: 1px solid;\n border-top-color: #222;\n border-top-color: var(--border, #222);\n}\n.with-load-more-footer .error {\n font-size: 14px;\n}\n.with-load-more-footer a {\n cursor: pointer;\n}"],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/font/fontello.1594374054351.woff2 b/priv/static/static/font/fontello.1594374054351.woff2 deleted file mode 100644 index cb214aab3..000000000 Binary files a/priv/static/static/font/fontello.1594374054351.woff2 and /dev/null differ diff --git a/priv/static/static/font/fontello.1594374054351.eot b/priv/static/static/font/fontello.1594823398494.eot similarity index 99% rename from priv/static/static/font/fontello.1594374054351.eot rename to priv/static/static/font/fontello.1594823398494.eot index 62b619386..12e6beabf 100644 Binary files a/priv/static/static/font/fontello.1594374054351.eot and b/priv/static/static/font/fontello.1594823398494.eot differ diff --git a/priv/static/static/font/fontello.1594374054351.svg b/priv/static/static/font/fontello.1594823398494.svg similarity index 100% rename from priv/static/static/font/fontello.1594374054351.svg rename to priv/static/static/font/fontello.1594823398494.svg diff --git a/priv/static/static/font/fontello.1594374054351.ttf b/priv/static/static/font/fontello.1594823398494.ttf similarity index 99% rename from priv/static/static/font/fontello.1594374054351.ttf rename to priv/static/static/font/fontello.1594823398494.ttf index be55bef81..6f21845a8 100644 Binary files a/priv/static/static/font/fontello.1594374054351.ttf and b/priv/static/static/font/fontello.1594823398494.ttf differ diff --git a/priv/static/static/font/fontello.1594374054351.woff b/priv/static/static/font/fontello.1594823398494.woff similarity index 96% rename from priv/static/static/font/fontello.1594374054351.woff rename to priv/static/static/font/fontello.1594823398494.woff index 115945f70..a7cd098f4 100644 Binary files a/priv/static/static/font/fontello.1594374054351.woff and b/priv/static/static/font/fontello.1594823398494.woff differ diff --git a/priv/static/static/font/fontello.1594823398494.woff2 b/priv/static/static/font/fontello.1594823398494.woff2 new file mode 100644 index 000000000..c61bf111a Binary files /dev/null and b/priv/static/static/font/fontello.1594823398494.woff2 differ diff --git a/priv/static/static/fontello.1589385935077.css b/priv/static/static/fontello.1589385935077.css deleted file mode 100644 index 746492163..000000000 Binary files a/priv/static/static/fontello.1589385935077.css and /dev/null differ diff --git a/priv/static/static/fontello.1594030805019.css b/priv/static/static/fontello.1594030805019.css deleted file mode 100644 index 9251070fe..000000000 Binary files a/priv/static/static/fontello.1594030805019.css and /dev/null differ diff --git a/priv/static/static/fontello.1594134783339.css b/priv/static/static/fontello.1594134783339.css deleted file mode 100644 index ff35edaba..000000000 Binary files a/priv/static/static/fontello.1594134783339.css and /dev/null differ diff --git a/priv/static/static/fontello.1594374054351.css b/priv/static/static/fontello.1594823398494.css similarity index 90% rename from priv/static/static/fontello.1594374054351.css rename to priv/static/static/fontello.1594823398494.css index 6dea8ee3e..fe61b94c6 100644 Binary files a/priv/static/static/fontello.1594374054351.css and b/priv/static/static/fontello.1594823398494.css differ diff --git a/priv/static/static/fontello.json b/priv/static/static/fontello.json old mode 100755 new mode 100644 diff --git a/priv/static/static/js/10.2823375ec309b971aaea.js b/priv/static/static/js/10.2823375ec309b971aaea.js deleted file mode 100644 index 8f34c42ea..000000000 Binary files a/priv/static/static/js/10.2823375ec309b971aaea.js and /dev/null differ diff --git a/priv/static/static/js/10.5ef4671883649cf93524.js b/priv/static/static/js/10.5ef4671883649cf93524.js new file mode 100644 index 000000000..6819c854b Binary files /dev/null and b/priv/static/static/js/10.5ef4671883649cf93524.js differ diff --git a/priv/static/static/js/10.2823375ec309b971aaea.js.map b/priv/static/static/js/10.5ef4671883649cf93524.js.map similarity index 56% rename from priv/static/static/js/10.2823375ec309b971aaea.js.map rename to priv/static/static/js/10.5ef4671883649cf93524.js.map index 8933e2336..95fa2207e 100644 Binary files a/priv/static/static/js/10.2823375ec309b971aaea.js.map and b/priv/static/static/js/10.5ef4671883649cf93524.js.map differ diff --git a/priv/static/static/js/11.2cb4b0f72a4654070a58.js b/priv/static/static/js/11.2cb4b0f72a4654070a58.js deleted file mode 100644 index 03b0234f2..000000000 Binary files a/priv/static/static/js/11.2cb4b0f72a4654070a58.js and /dev/null differ diff --git a/priv/static/static/js/11.c5b938b4349f87567338.js b/priv/static/static/js/11.c5b938b4349f87567338.js new file mode 100644 index 000000000..b97f69bf3 Binary files /dev/null and b/priv/static/static/js/11.c5b938b4349f87567338.js differ diff --git a/priv/static/static/js/11.2cb4b0f72a4654070a58.js.map b/priv/static/static/js/11.c5b938b4349f87567338.js.map similarity index 56% rename from priv/static/static/js/11.2cb4b0f72a4654070a58.js.map rename to priv/static/static/js/11.c5b938b4349f87567338.js.map index b53e5e23a..5ccf83b1d 100644 Binary files a/priv/static/static/js/11.2cb4b0f72a4654070a58.js.map and b/priv/static/static/js/11.c5b938b4349f87567338.js.map differ diff --git a/priv/static/static/js/12.500b3e4676dd47599a58.js b/priv/static/static/js/12.500b3e4676dd47599a58.js deleted file mode 100644 index 52dfbde92..000000000 Binary files a/priv/static/static/js/12.500b3e4676dd47599a58.js and /dev/null differ diff --git a/priv/static/static/js/12.ab82f9512fa85e78c114.js b/priv/static/static/js/12.ab82f9512fa85e78c114.js new file mode 100644 index 000000000..100d72b33 Binary files /dev/null and b/priv/static/static/js/12.ab82f9512fa85e78c114.js differ diff --git a/priv/static/static/js/12.500b3e4676dd47599a58.js.map b/priv/static/static/js/12.ab82f9512fa85e78c114.js.map similarity index 56% rename from priv/static/static/js/12.500b3e4676dd47599a58.js.map rename to priv/static/static/js/12.ab82f9512fa85e78c114.js.map index 700da90b0..23335ae23 100644 Binary files a/priv/static/static/js/12.500b3e4676dd47599a58.js.map and b/priv/static/static/js/12.ab82f9512fa85e78c114.js.map differ diff --git a/priv/static/static/js/13.3ef79a2643680080d28f.js b/priv/static/static/js/13.3ef79a2643680080d28f.js deleted file mode 100644 index 4070d1f3f..000000000 Binary files a/priv/static/static/js/13.3ef79a2643680080d28f.js and /dev/null differ diff --git a/priv/static/static/js/13.40e59c5015d3307b94ad.js b/priv/static/static/js/13.40e59c5015d3307b94ad.js new file mode 100644 index 000000000..2088bb6b7 Binary files /dev/null and b/priv/static/static/js/13.40e59c5015d3307b94ad.js differ diff --git a/priv/static/static/js/13.3ef79a2643680080d28f.js.map b/priv/static/static/js/13.40e59c5015d3307b94ad.js.map similarity index 56% rename from priv/static/static/js/13.3ef79a2643680080d28f.js.map rename to priv/static/static/js/13.40e59c5015d3307b94ad.js.map index bb2f24e3a..3931b5ef9 100644 Binary files a/priv/static/static/js/13.3ef79a2643680080d28f.js.map and b/priv/static/static/js/13.40e59c5015d3307b94ad.js.map differ diff --git a/priv/static/static/js/14.b7f6eb3ea71d2ac2bb41.js b/priv/static/static/js/14.b7f6eb3ea71d2ac2bb41.js deleted file mode 100644 index 316ba1291..000000000 Binary files a/priv/static/static/js/14.b7f6eb3ea71d2ac2bb41.js and /dev/null differ diff --git a/priv/static/static/js/14.b7f6eb3ea71d2ac2bb41.js.map b/priv/static/static/js/14.b7f6eb3ea71d2ac2bb41.js.map deleted file mode 100644 index 07a26f298..000000000 Binary files a/priv/static/static/js/14.b7f6eb3ea71d2ac2bb41.js.map and /dev/null differ diff --git a/priv/static/static/js/14.de791a47ee5249a526b1.js b/priv/static/static/js/14.de791a47ee5249a526b1.js new file mode 100644 index 000000000..0e341275e Binary files /dev/null and b/priv/static/static/js/14.de791a47ee5249a526b1.js differ diff --git a/priv/static/static/js/14.de791a47ee5249a526b1.js.map b/priv/static/static/js/14.de791a47ee5249a526b1.js.map new file mode 100644 index 000000000..4bef54546 Binary files /dev/null and b/priv/static/static/js/14.de791a47ee5249a526b1.js.map differ diff --git a/priv/static/static/js/15.d814a29a970070494722.js b/priv/static/static/js/15.d814a29a970070494722.js deleted file mode 100644 index 17eaf5218..000000000 Binary files a/priv/static/static/js/15.d814a29a970070494722.js and /dev/null differ diff --git a/priv/static/static/js/15.d814a29a970070494722.js.map b/priv/static/static/js/15.d814a29a970070494722.js.map deleted file mode 100644 index 9792088bf..000000000 Binary files a/priv/static/static/js/15.d814a29a970070494722.js.map and /dev/null differ diff --git a/priv/static/static/js/15.e24854297ad682aec45a.js b/priv/static/static/js/15.e24854297ad682aec45a.js new file mode 100644 index 000000000..671370192 Binary files /dev/null and b/priv/static/static/js/15.e24854297ad682aec45a.js differ diff --git a/priv/static/static/js/15.e24854297ad682aec45a.js.map b/priv/static/static/js/15.e24854297ad682aec45a.js.map new file mode 100644 index 000000000..89789a542 Binary files /dev/null and b/priv/static/static/js/15.e24854297ad682aec45a.js.map differ diff --git a/priv/static/static/js/16.017fa510b293035ac370.js b/priv/static/static/js/16.017fa510b293035ac370.js deleted file mode 100644 index 387cfc9c7..000000000 Binary files a/priv/static/static/js/16.017fa510b293035ac370.js and /dev/null differ diff --git a/priv/static/static/js/16.017fa510b293035ac370.js.map b/priv/static/static/js/16.017fa510b293035ac370.js.map deleted file mode 100644 index 2886028bd..000000000 Binary files a/priv/static/static/js/16.017fa510b293035ac370.js.map and /dev/null differ diff --git a/priv/static/static/js/16.b7b0e4b8227a50fcb9bb.js b/priv/static/static/js/16.b7b0e4b8227a50fcb9bb.js new file mode 100644 index 000000000..6a3ea9513 Binary files /dev/null and b/priv/static/static/js/16.b7b0e4b8227a50fcb9bb.js differ diff --git a/priv/static/static/js/16.b7b0e4b8227a50fcb9bb.js.map b/priv/static/static/js/16.b7b0e4b8227a50fcb9bb.js.map new file mode 100644 index 000000000..fec45b087 Binary files /dev/null and b/priv/static/static/js/16.b7b0e4b8227a50fcb9bb.js.map differ diff --git a/priv/static/static/js/17.c63932b65417ee7346a3.js b/priv/static/static/js/17.c63932b65417ee7346a3.js deleted file mode 100644 index e3172472a..000000000 Binary files a/priv/static/static/js/17.c63932b65417ee7346a3.js and /dev/null differ diff --git a/priv/static/static/js/17.c63932b65417ee7346a3.js.map b/priv/static/static/js/17.c63932b65417ee7346a3.js.map deleted file mode 100644 index f4c55d0cc..000000000 Binary files a/priv/static/static/js/17.c63932b65417ee7346a3.js.map and /dev/null differ diff --git a/priv/static/static/js/17.c98118b6bb84ee3b5b08.js b/priv/static/static/js/17.c98118b6bb84ee3b5b08.js new file mode 100644 index 000000000..c41f0b6b8 Binary files /dev/null and b/priv/static/static/js/17.c98118b6bb84ee3b5b08.js differ diff --git a/priv/static/static/js/17.c98118b6bb84ee3b5b08.js.map b/priv/static/static/js/17.c98118b6bb84ee3b5b08.js.map new file mode 100644 index 000000000..0c20fc89b Binary files /dev/null and b/priv/static/static/js/17.c98118b6bb84ee3b5b08.js.map differ diff --git a/priv/static/static/js/18.89c20aa67a4dd067ea37.js b/priv/static/static/js/18.89c20aa67a4dd067ea37.js new file mode 100644 index 000000000..db1c78a49 Binary files /dev/null and b/priv/static/static/js/18.89c20aa67a4dd067ea37.js differ diff --git a/priv/static/static/js/18.89c20aa67a4dd067ea37.js.map b/priv/static/static/js/18.89c20aa67a4dd067ea37.js.map new file mode 100644 index 000000000..72cdf0e0e Binary files /dev/null and b/priv/static/static/js/18.89c20aa67a4dd067ea37.js.map differ diff --git a/priv/static/static/js/18.fd12f9746a55aa24a8b7.js b/priv/static/static/js/18.fd12f9746a55aa24a8b7.js deleted file mode 100644 index be1ecbba5..000000000 Binary files a/priv/static/static/js/18.fd12f9746a55aa24a8b7.js and /dev/null differ diff --git a/priv/static/static/js/18.fd12f9746a55aa24a8b7.js.map b/priv/static/static/js/18.fd12f9746a55aa24a8b7.js.map deleted file mode 100644 index c98c107b3..000000000 Binary files a/priv/static/static/js/18.fd12f9746a55aa24a8b7.js.map and /dev/null differ diff --git a/priv/static/static/js/19.3adebd64964c92700074.js b/priv/static/static/js/19.3adebd64964c92700074.js deleted file mode 100644 index 9d5adbe4e..000000000 Binary files a/priv/static/static/js/19.3adebd64964c92700074.js and /dev/null differ diff --git a/priv/static/static/js/19.3adebd64964c92700074.js.map b/priv/static/static/js/19.3adebd64964c92700074.js.map deleted file mode 100644 index d113a66dc..000000000 Binary files a/priv/static/static/js/19.3adebd64964c92700074.js.map and /dev/null differ diff --git a/priv/static/static/js/19.6e13bad8131c4501c1c5.js b/priv/static/static/js/19.6e13bad8131c4501c1c5.js new file mode 100644 index 000000000..8b32827cc Binary files /dev/null and b/priv/static/static/js/19.6e13bad8131c4501c1c5.js differ diff --git a/priv/static/static/js/19.6e13bad8131c4501c1c5.js.map b/priv/static/static/js/19.6e13bad8131c4501c1c5.js.map new file mode 100644 index 000000000..762d85e27 Binary files /dev/null and b/priv/static/static/js/19.6e13bad8131c4501c1c5.js.map differ diff --git a/priv/static/static/js/2.78a48aa26599b00c3b8d.js b/priv/static/static/js/2.78a48aa26599b00c3b8d.js new file mode 100644 index 000000000..ecb27aa9c Binary files /dev/null and b/priv/static/static/js/2.78a48aa26599b00c3b8d.js differ diff --git a/priv/static/static/js/2.78a48aa26599b00c3b8d.js.map b/priv/static/static/js/2.78a48aa26599b00c3b8d.js.map new file mode 100644 index 000000000..167cfa1c6 Binary files /dev/null and b/priv/static/static/js/2.78a48aa26599b00c3b8d.js.map differ diff --git a/priv/static/static/js/2.d81ca020d6885c6c3b03.js b/priv/static/static/js/2.d81ca020d6885c6c3b03.js deleted file mode 100644 index f751a05da..000000000 Binary files a/priv/static/static/js/2.d81ca020d6885c6c3b03.js and /dev/null differ diff --git a/priv/static/static/js/2.d81ca020d6885c6c3b03.js.map b/priv/static/static/js/2.d81ca020d6885c6c3b03.js.map deleted file mode 100644 index 9a675dbc5..000000000 Binary files a/priv/static/static/js/2.d81ca020d6885c6c3b03.js.map and /dev/null differ diff --git a/priv/static/static/js/20.3615c3cea2e1c2707a4f.js b/priv/static/static/js/20.3615c3cea2e1c2707a4f.js new file mode 100644 index 000000000..74f89016c Binary files /dev/null and b/priv/static/static/js/20.3615c3cea2e1c2707a4f.js differ diff --git a/priv/static/static/js/20.3615c3cea2e1c2707a4f.js.map b/priv/static/static/js/20.3615c3cea2e1c2707a4f.js.map new file mode 100644 index 000000000..acddecea7 Binary files /dev/null and b/priv/static/static/js/20.3615c3cea2e1c2707a4f.js.map differ diff --git a/priv/static/static/js/20.e0c3ad29d59470506c04.js b/priv/static/static/js/20.e0c3ad29d59470506c04.js deleted file mode 100644 index ddedbd1ff..000000000 Binary files a/priv/static/static/js/20.e0c3ad29d59470506c04.js and /dev/null differ diff --git a/priv/static/static/js/20.e0c3ad29d59470506c04.js.map b/priv/static/static/js/20.e0c3ad29d59470506c04.js.map deleted file mode 100644 index 83a9fbc98..000000000 Binary files a/priv/static/static/js/20.e0c3ad29d59470506c04.js.map and /dev/null differ diff --git a/priv/static/static/js/21.64dedfc646e13e6f7915.js b/priv/static/static/js/21.64dedfc646e13e6f7915.js new file mode 100644 index 000000000..407e6665e Binary files /dev/null and b/priv/static/static/js/21.64dedfc646e13e6f7915.js differ diff --git a/priv/static/static/js/21.64dedfc646e13e6f7915.js.map b/priv/static/static/js/21.64dedfc646e13e6f7915.js.map new file mode 100644 index 000000000..8e3432668 Binary files /dev/null and b/priv/static/static/js/21.64dedfc646e13e6f7915.js.map differ diff --git a/priv/static/static/js/21.849ecc09a1d58bdc64c6.js b/priv/static/static/js/21.849ecc09a1d58bdc64c6.js deleted file mode 100644 index ef58a3da1..000000000 Binary files a/priv/static/static/js/21.849ecc09a1d58bdc64c6.js and /dev/null differ diff --git a/priv/static/static/js/21.849ecc09a1d58bdc64c6.js.map b/priv/static/static/js/21.849ecc09a1d58bdc64c6.js.map deleted file mode 100644 index 9447b7ce3..000000000 Binary files a/priv/static/static/js/21.849ecc09a1d58bdc64c6.js.map and /dev/null differ diff --git a/priv/static/static/js/22.6fa63bc6a054b7638e9e.js b/priv/static/static/js/22.6fa63bc6a054b7638e9e.js new file mode 100644 index 000000000..4a8740c99 Binary files /dev/null and b/priv/static/static/js/22.6fa63bc6a054b7638e9e.js differ diff --git a/priv/static/static/js/22.6fa63bc6a054b7638e9e.js.map b/priv/static/static/js/22.6fa63bc6a054b7638e9e.js.map new file mode 100644 index 000000000..1c556f040 Binary files /dev/null and b/priv/static/static/js/22.6fa63bc6a054b7638e9e.js.map differ diff --git a/priv/static/static/js/22.8782f133c9f66d3f2bbe.js b/priv/static/static/js/22.8782f133c9f66d3f2bbe.js deleted file mode 100644 index 82692acdb..000000000 Binary files a/priv/static/static/js/22.8782f133c9f66d3f2bbe.js and /dev/null differ diff --git a/priv/static/static/js/22.8782f133c9f66d3f2bbe.js.map b/priv/static/static/js/22.8782f133c9f66d3f2bbe.js.map deleted file mode 100644 index 41e527ff6..000000000 Binary files a/priv/static/static/js/22.8782f133c9f66d3f2bbe.js.map and /dev/null differ diff --git a/priv/static/static/js/23.2653bf91bc77c2ed0160.js b/priv/static/static/js/23.2653bf91bc77c2ed0160.js deleted file mode 100644 index 2aad331b4..000000000 Binary files a/priv/static/static/js/23.2653bf91bc77c2ed0160.js and /dev/null differ diff --git a/priv/static/static/js/23.2653bf91bc77c2ed0160.js.map b/priv/static/static/js/23.2653bf91bc77c2ed0160.js.map deleted file mode 100644 index 4f031922e..000000000 Binary files a/priv/static/static/js/23.2653bf91bc77c2ed0160.js.map and /dev/null differ diff --git a/priv/static/static/js/23.e0ddea2b6e049d221ee7.js b/priv/static/static/js/23.e0ddea2b6e049d221ee7.js new file mode 100644 index 000000000..51fe36368 Binary files /dev/null and b/priv/static/static/js/23.e0ddea2b6e049d221ee7.js differ diff --git a/priv/static/static/js/23.e0ddea2b6e049d221ee7.js.map b/priv/static/static/js/23.e0ddea2b6e049d221ee7.js.map new file mode 100644 index 000000000..36bae2bf4 Binary files /dev/null and b/priv/static/static/js/23.e0ddea2b6e049d221ee7.js.map differ diff --git a/priv/static/static/js/24.38e3b9d44e9ee703ebf6.js b/priv/static/static/js/24.38e3b9d44e9ee703ebf6.js new file mode 100644 index 000000000..e5abf0af6 Binary files /dev/null and b/priv/static/static/js/24.38e3b9d44e9ee703ebf6.js differ diff --git a/priv/static/static/js/24.38e3b9d44e9ee703ebf6.js.map b/priv/static/static/js/24.38e3b9d44e9ee703ebf6.js.map new file mode 100644 index 000000000..09f3c19d0 Binary files /dev/null and b/priv/static/static/js/24.38e3b9d44e9ee703ebf6.js.map differ diff --git a/priv/static/static/js/24.f931d864a2297d880a9a.js b/priv/static/static/js/24.f931d864a2297d880a9a.js deleted file mode 100644 index 0362730e0..000000000 Binary files a/priv/static/static/js/24.f931d864a2297d880a9a.js and /dev/null differ diff --git a/priv/static/static/js/24.f931d864a2297d880a9a.js.map b/priv/static/static/js/24.f931d864a2297d880a9a.js.map deleted file mode 100644 index 2fb375e79..000000000 Binary files a/priv/static/static/js/24.f931d864a2297d880a9a.js.map and /dev/null differ diff --git a/priv/static/static/js/25.696b41c0a8660e1f85af.js b/priv/static/static/js/25.696b41c0a8660e1f85af.js new file mode 100644 index 000000000..b114890fc Binary files /dev/null and b/priv/static/static/js/25.696b41c0a8660e1f85af.js differ diff --git a/priv/static/static/js/25.696b41c0a8660e1f85af.js.map b/priv/static/static/js/25.696b41c0a8660e1f85af.js.map new file mode 100644 index 000000000..f6d208812 Binary files /dev/null and b/priv/static/static/js/25.696b41c0a8660e1f85af.js.map differ diff --git a/priv/static/static/js/25.886acc9ba83c64659279.js b/priv/static/static/js/25.886acc9ba83c64659279.js deleted file mode 100644 index 4ff4c331b..000000000 Binary files a/priv/static/static/js/25.886acc9ba83c64659279.js and /dev/null differ diff --git a/priv/static/static/js/25.886acc9ba83c64659279.js.map b/priv/static/static/js/25.886acc9ba83c64659279.js.map deleted file mode 100644 index c39f71238..000000000 Binary files a/priv/static/static/js/25.886acc9ba83c64659279.js.map and /dev/null differ diff --git a/priv/static/static/js/26.1168f22384be75dc5492.js b/priv/static/static/js/26.1168f22384be75dc5492.js new file mode 100644 index 000000000..b77a4d30f Binary files /dev/null and b/priv/static/static/js/26.1168f22384be75dc5492.js differ diff --git a/priv/static/static/js/26.1168f22384be75dc5492.js.map b/priv/static/static/js/26.1168f22384be75dc5492.js.map new file mode 100644 index 000000000..c9b0d8495 Binary files /dev/null and b/priv/static/static/js/26.1168f22384be75dc5492.js.map differ diff --git a/priv/static/static/js/26.e15b1645079c72c60586.js b/priv/static/static/js/26.e15b1645079c72c60586.js deleted file mode 100644 index 303170088..000000000 Binary files a/priv/static/static/js/26.e15b1645079c72c60586.js and /dev/null differ diff --git a/priv/static/static/js/26.e15b1645079c72c60586.js.map b/priv/static/static/js/26.e15b1645079c72c60586.js.map deleted file mode 100644 index e62345884..000000000 Binary files a/priv/static/static/js/26.e15b1645079c72c60586.js.map and /dev/null differ diff --git a/priv/static/static/js/27.3c0cfbb2a898b35486dd.js b/priv/static/static/js/27.3c0cfbb2a898b35486dd.js new file mode 100644 index 000000000..a0765356f Binary files /dev/null and b/priv/static/static/js/27.3c0cfbb2a898b35486dd.js differ diff --git a/priv/static/static/js/27.3c0cfbb2a898b35486dd.js.map b/priv/static/static/js/27.3c0cfbb2a898b35486dd.js.map new file mode 100644 index 000000000..0cc5f46b2 Binary files /dev/null and b/priv/static/static/js/27.3c0cfbb2a898b35486dd.js.map differ diff --git a/priv/static/static/js/27.7b41e5953f74af7fddd1.js b/priv/static/static/js/27.7b41e5953f74af7fddd1.js deleted file mode 100644 index 769fba11b..000000000 Binary files a/priv/static/static/js/27.7b41e5953f74af7fddd1.js and /dev/null differ diff --git a/priv/static/static/js/27.7b41e5953f74af7fddd1.js.map b/priv/static/static/js/27.7b41e5953f74af7fddd1.js.map deleted file mode 100644 index 078f5ff9a..000000000 Binary files a/priv/static/static/js/27.7b41e5953f74af7fddd1.js.map and /dev/null differ diff --git a/priv/static/static/js/28.4f39e562aaceaa01e883.js b/priv/static/static/js/28.4f39e562aaceaa01e883.js deleted file mode 100644 index 629359bda..000000000 Binary files a/priv/static/static/js/28.4f39e562aaceaa01e883.js and /dev/null differ diff --git a/priv/static/static/js/28.4f39e562aaceaa01e883.js.map b/priv/static/static/js/28.4f39e562aaceaa01e883.js.map deleted file mode 100644 index 24c675a4c..000000000 Binary files a/priv/static/static/js/28.4f39e562aaceaa01e883.js.map and /dev/null differ diff --git a/priv/static/static/js/28.926c71d6f1813e177271.js b/priv/static/static/js/28.926c71d6f1813e177271.js new file mode 100644 index 000000000..55cf840f2 Binary files /dev/null and b/priv/static/static/js/28.926c71d6f1813e177271.js differ diff --git a/priv/static/static/js/28.926c71d6f1813e177271.js.map b/priv/static/static/js/28.926c71d6f1813e177271.js.map new file mode 100644 index 000000000..1ae8f08cb Binary files /dev/null and b/priv/static/static/js/28.926c71d6f1813e177271.js.map differ diff --git a/priv/static/static/js/29.137e2a68b558eed58152.js b/priv/static/static/js/29.137e2a68b558eed58152.js deleted file mode 100644 index 50cb11ffd..000000000 Binary files a/priv/static/static/js/29.137e2a68b558eed58152.js and /dev/null differ diff --git a/priv/static/static/js/29.137e2a68b558eed58152.js.map b/priv/static/static/js/29.137e2a68b558eed58152.js.map deleted file mode 100644 index 0ac2f7fd3..000000000 Binary files a/priv/static/static/js/29.137e2a68b558eed58152.js.map and /dev/null differ diff --git a/priv/static/static/js/29.187064ebed099ae45749.js b/priv/static/static/js/29.187064ebed099ae45749.js new file mode 100644 index 000000000..6eaae0226 Binary files /dev/null and b/priv/static/static/js/29.187064ebed099ae45749.js differ diff --git a/priv/static/static/js/29.187064ebed099ae45749.js.map b/priv/static/static/js/29.187064ebed099ae45749.js.map new file mode 100644 index 000000000..b5dd63f96 Binary files /dev/null and b/priv/static/static/js/29.187064ebed099ae45749.js.map differ diff --git a/priv/static/static/js/30.73e09f3b43617410dec7.js b/priv/static/static/js/30.73e09f3b43617410dec7.js deleted file mode 100644 index 0c3d03cfa..000000000 Binary files a/priv/static/static/js/30.73e09f3b43617410dec7.js and /dev/null differ diff --git a/priv/static/static/js/30.73e09f3b43617410dec7.js.map b/priv/static/static/js/30.73e09f3b43617410dec7.js.map deleted file mode 100644 index cb546de17..000000000 Binary files a/priv/static/static/js/30.73e09f3b43617410dec7.js.map and /dev/null differ diff --git a/priv/static/static/js/30.d78855ca19bf749be905.js b/priv/static/static/js/30.d78855ca19bf749be905.js new file mode 100644 index 000000000..9202d19d3 Binary files /dev/null and b/priv/static/static/js/30.d78855ca19bf749be905.js differ diff --git a/priv/static/static/js/30.d78855ca19bf749be905.js.map b/priv/static/static/js/30.d78855ca19bf749be905.js.map new file mode 100644 index 000000000..b9f39664d Binary files /dev/null and b/priv/static/static/js/30.d78855ca19bf749be905.js.map differ diff --git a/priv/static/static/js/5.2b4a2787bacdd3d910db.js b/priv/static/static/js/5.2b4a2787bacdd3d910db.js deleted file mode 100644 index 18c059380..000000000 Binary files a/priv/static/static/js/5.2b4a2787bacdd3d910db.js and /dev/null differ diff --git a/priv/static/static/js/5.84f3dce298bc720719c7.js b/priv/static/static/js/5.84f3dce298bc720719c7.js new file mode 100644 index 000000000..242b2a525 Binary files /dev/null and b/priv/static/static/js/5.84f3dce298bc720719c7.js differ diff --git a/priv/static/static/js/5.2b4a2787bacdd3d910db.js.map b/priv/static/static/js/5.84f3dce298bc720719c7.js.map similarity index 57% rename from priv/static/static/js/5.2b4a2787bacdd3d910db.js.map rename to priv/static/static/js/5.84f3dce298bc720719c7.js.map index e9e78632d..4fcc32982 100644 Binary files a/priv/static/static/js/5.2b4a2787bacdd3d910db.js.map and b/priv/static/static/js/5.84f3dce298bc720719c7.js.map differ diff --git a/priv/static/static/js/6.9c94bc0cc78979694cf4.js b/priv/static/static/js/6.9c94bc0cc78979694cf4.js deleted file mode 100644 index 415938f67..000000000 Binary files a/priv/static/static/js/6.9c94bc0cc78979694cf4.js and /dev/null differ diff --git a/priv/static/static/js/6.b9497e1d403b901a664e.js b/priv/static/static/js/6.b9497e1d403b901a664e.js new file mode 100644 index 000000000..8c66e9062 Binary files /dev/null and b/priv/static/static/js/6.b9497e1d403b901a664e.js differ diff --git a/priv/static/static/js/6.9c94bc0cc78979694cf4.js.map b/priv/static/static/js/6.b9497e1d403b901a664e.js.map similarity index 57% rename from priv/static/static/js/6.9c94bc0cc78979694cf4.js.map rename to priv/static/static/js/6.b9497e1d403b901a664e.js.map index 948368f60..5af0576c7 100644 Binary files a/priv/static/static/js/6.9c94bc0cc78979694cf4.js.map and b/priv/static/static/js/6.b9497e1d403b901a664e.js.map differ diff --git a/priv/static/static/js/7.455b574116ce3f004ffb.js b/priv/static/static/js/7.455b574116ce3f004ffb.js new file mode 100644 index 000000000..0e35f6904 Binary files /dev/null and b/priv/static/static/js/7.455b574116ce3f004ffb.js differ diff --git a/priv/static/static/js/7.b4ac57fd946a3a189047.js.map b/priv/static/static/js/7.455b574116ce3f004ffb.js.map similarity index 57% rename from priv/static/static/js/7.b4ac57fd946a3a189047.js.map rename to priv/static/static/js/7.455b574116ce3f004ffb.js.map index 054d52650..971b570e1 100644 Binary files a/priv/static/static/js/7.b4ac57fd946a3a189047.js.map and b/priv/static/static/js/7.455b574116ce3f004ffb.js.map differ diff --git a/priv/static/static/js/7.b4ac57fd946a3a189047.js b/priv/static/static/js/7.b4ac57fd946a3a189047.js deleted file mode 100644 index 18b6ab76c..000000000 Binary files a/priv/static/static/js/7.b4ac57fd946a3a189047.js and /dev/null differ diff --git a/priv/static/static/js/8.8db9f2dcc5ed429777f7.js b/priv/static/static/js/8.8db9f2dcc5ed429777f7.js new file mode 100644 index 000000000..79082fc7c Binary files /dev/null and b/priv/static/static/js/8.8db9f2dcc5ed429777f7.js differ diff --git a/priv/static/static/js/8.e03e32ca713d01db0433.js.map b/priv/static/static/js/8.8db9f2dcc5ed429777f7.js.map similarity index 57% rename from priv/static/static/js/8.e03e32ca713d01db0433.js.map rename to priv/static/static/js/8.8db9f2dcc5ed429777f7.js.map index d1385c203..e193375de 100644 Binary files a/priv/static/static/js/8.e03e32ca713d01db0433.js.map and b/priv/static/static/js/8.8db9f2dcc5ed429777f7.js.map differ diff --git a/priv/static/static/js/8.e03e32ca713d01db0433.js b/priv/static/static/js/8.e03e32ca713d01db0433.js deleted file mode 100644 index 4d5894322..000000000 Binary files a/priv/static/static/js/8.e03e32ca713d01db0433.js and /dev/null differ diff --git a/priv/static/static/js/9.72d903ca8e0c5a532b87.js b/priv/static/static/js/9.72d903ca8e0c5a532b87.js deleted file mode 100644 index ce0f066c5..000000000 Binary files a/priv/static/static/js/9.72d903ca8e0c5a532b87.js and /dev/null differ diff --git a/priv/static/static/js/9.72d903ca8e0c5a532b87.js.map b/priv/static/static/js/9.72d903ca8e0c5a532b87.js.map deleted file mode 100644 index 4cf79de5b..000000000 Binary files a/priv/static/static/js/9.72d903ca8e0c5a532b87.js.map and /dev/null differ diff --git a/priv/static/static/js/9.da3973d058660aa9612f.js b/priv/static/static/js/9.da3973d058660aa9612f.js new file mode 100644 index 000000000..50d977f67 Binary files /dev/null and b/priv/static/static/js/9.da3973d058660aa9612f.js differ diff --git a/priv/static/static/js/9.da3973d058660aa9612f.js.map b/priv/static/static/js/9.da3973d058660aa9612f.js.map new file mode 100644 index 000000000..4c8f70599 Binary files /dev/null and b/priv/static/static/js/9.da3973d058660aa9612f.js.map differ diff --git a/priv/static/static/js/app.1e68e208590653dab5aa.js b/priv/static/static/js/app.1e68e208590653dab5aa.js deleted file mode 100644 index 27cc3d910..000000000 Binary files a/priv/static/static/js/app.1e68e208590653dab5aa.js and /dev/null differ diff --git a/priv/static/static/js/app.1e68e208590653dab5aa.js.map b/priv/static/static/js/app.1e68e208590653dab5aa.js.map deleted file mode 100644 index 71636d936..000000000 Binary files a/priv/static/static/js/app.1e68e208590653dab5aa.js.map and /dev/null differ diff --git a/priv/static/static/js/app.31bba9f1e242ff273dcb.js b/priv/static/static/js/app.31bba9f1e242ff273dcb.js new file mode 100644 index 000000000..22413689c Binary files /dev/null and b/priv/static/static/js/app.31bba9f1e242ff273dcb.js differ diff --git a/priv/static/static/js/app.31bba9f1e242ff273dcb.js.map b/priv/static/static/js/app.31bba9f1e242ff273dcb.js.map new file mode 100644 index 000000000..8ff7a00c6 Binary files /dev/null and b/priv/static/static/js/app.31bba9f1e242ff273dcb.js.map differ diff --git a/priv/static/static/js/vendors~app.247dc52c7abe6a0dab87.js b/priv/static/static/js/vendors~app.9e24ed238da5a8538f50.js similarity index 89% rename from priv/static/static/js/vendors~app.247dc52c7abe6a0dab87.js rename to priv/static/static/js/vendors~app.9e24ed238da5a8538f50.js index bf6671e4b..76c8a8dc1 100644 Binary files a/priv/static/static/js/vendors~app.247dc52c7abe6a0dab87.js and b/priv/static/static/js/vendors~app.9e24ed238da5a8538f50.js differ diff --git a/priv/static/static/js/vendors~app.247dc52c7abe6a0dab87.js.map b/priv/static/static/js/vendors~app.9e24ed238da5a8538f50.js.map similarity index 96% rename from priv/static/static/js/vendors~app.247dc52c7abe6a0dab87.js.map rename to priv/static/static/js/vendors~app.9e24ed238da5a8538f50.js.map index 2a3bf1b99..f3c067c15 100644 Binary files a/priv/static/static/js/vendors~app.247dc52c7abe6a0dab87.js.map and b/priv/static/static/js/vendors~app.9e24ed238da5a8538f50.js.map differ diff --git a/priv/static/sw-pleroma.js b/priv/static/sw-pleroma.js index 098f58d49..9b7d127fd 100644 Binary files a/priv/static/sw-pleroma.js and b/priv/static/sw-pleroma.js differ diff --git a/test/application_requirements_test.exs b/test/application_requirements_test.exs index 481cdfd73..fc609d174 100644 --- a/test/application_requirements_test.exs +++ b/test/application_requirements_test.exs @@ -9,6 +9,42 @@ defmodule Pleroma.ApplicationRequirementsTest do alias Pleroma.Repo + describe "check_confirmation_accounts!" do + setup_with_mocks([ + {Pleroma.ApplicationRequirements, [:passthrough], + [ + check_migrations_applied!: fn _ -> :ok end + ]} + ]) do + :ok + end + + setup do: clear_config([:instance, :account_activation_required]) + + test "raises if account confirmation is required but mailer isn't enable" do + Pleroma.Config.put([:instance, :account_activation_required], true) + Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false) + + assert_raise Pleroma.ApplicationRequirements.VerifyError, + "Account activation enabled, but Mailer is disabled. Cannot send confirmation emails.", + fn -> + capture_log(&Pleroma.ApplicationRequirements.verify!/0) + end + end + + test "doesn't do anything if account confirmation is disabled" do + Pleroma.Config.put([:instance, :account_activation_required], false) + Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false) + assert Pleroma.ApplicationRequirements.verify!() == :ok + end + + test "doesn't do anything if account confirmation is required and mailer is enabled" do + Pleroma.Config.put([:instance, :account_activation_required], true) + Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], true) + assert Pleroma.ApplicationRequirements.verify!() == :ok + end + end + describe "check_rum!" do setup_with_mocks([ {Pleroma.ApplicationRequirements, [:passthrough], diff --git a/test/formatter_test.exs b/test/formatter_test.exs index bef5a2c28..f066bd50a 100644 --- a/test/formatter_test.exs +++ b/test/formatter_test.exs @@ -10,6 +10,7 @@ defmodule Pleroma.FormatterTest do import Pleroma.Factory setup_all do + clear_config(Pleroma.Formatter) Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) :ok end @@ -255,6 +256,36 @@ test "it can parse mentions and return the relevant users" do assert {_text, ^expected_mentions, []} = Formatter.linkify(text) end + + test "it parses URL containing local mention" do + _user = insert(:user, %{nickname: "lain"}) + + text = "https://example.com/@lain" + + expected = ~S(https://example.com/@lain) + + assert {^expected, [], []} = Formatter.linkify(text) + end + + test "it correctly parses angry face D:< with mention" do + lain = + insert(:user, %{ + nickname: "lain@lain.com", + ap_id: "https://lain.com/users/lain", + id: "9qrWmR0cKniB0YU0TA" + }) + + text = "@lain@lain.com D:<" + + expected_text = + ~S(@lain D:<) + + expected_mentions = [ + {"@lain@lain.com", lain} + ] + + assert {^expected_text, ^expected_mentions, []} = Formatter.linkify(text) + end end describe ".parse_tags" do diff --git a/test/gun/conneciton_pool_test.exs b/test/gun/conneciton_pool_test.exs new file mode 100644 index 000000000..aea908fac --- /dev/null +++ b/test/gun/conneciton_pool_test.exs @@ -0,0 +1,101 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Gun.ConnectionPoolTest do + use Pleroma.DataCase + + import Mox + import ExUnit.CaptureLog + alias Pleroma.Config + alias Pleroma.Gun.ConnectionPool + + defp gun_mock(_) do + Pleroma.GunMock + |> stub(:open, fn _, _, _ -> Task.start_link(fn -> Process.sleep(100) end) end) + |> stub(:await_up, fn _, _ -> {:ok, :http} end) + |> stub(:set_owner, fn _, _ -> :ok end) + + :ok + end + + setup :set_mox_from_context + setup :gun_mock + + test "gives the same connection to 2 concurrent requests" do + Enum.map( + [ + "http://www.korean-books.com.kp/KBMbooks/en/periodic/pictorial/20200530163914.pdf", + "http://www.korean-books.com.kp/KBMbooks/en/periodic/pictorial/20200528183427.pdf" + ], + fn uri -> + uri = URI.parse(uri) + task_parent = self() + + Task.start_link(fn -> + {:ok, conn} = ConnectionPool.get_conn(uri, []) + ConnectionPool.release_conn(conn) + send(task_parent, conn) + end) + end + ) + + [pid, pid] = + for _ <- 1..2 do + receive do + pid -> pid + end + end + end + + test "connection limit is respected with concurrent requests" do + clear_config([:connections_pool, :max_connections]) do + Config.put([:connections_pool, :max_connections], 1) + # The supervisor needs a reboot to apply the new config setting + Process.exit(Process.whereis(Pleroma.Gun.ConnectionPool.WorkerSupervisor), :kill) + + on_exit(fn -> + Process.exit(Process.whereis(Pleroma.Gun.ConnectionPool.WorkerSupervisor), :kill) + end) + end + + capture_log(fn -> + Enum.map( + [ + "https://ninenines.eu/", + "https://youtu.be/PFGwMiDJKNY" + ], + fn uri -> + uri = URI.parse(uri) + task_parent = self() + + Task.start_link(fn -> + result = ConnectionPool.get_conn(uri, []) + # Sleep so that we don't end up with a situation, + # where request from the second process gets processed + # only after the first process already released the connection + Process.sleep(50) + + case result do + {:ok, pid} -> + ConnectionPool.release_conn(pid) + + _ -> + nil + end + + send(task_parent, result) + end) + end + ) + + [{:error, :pool_full}, {:ok, _pid}] = + for _ <- 1..2 do + receive do + result -> result + end + end + |> Enum.sort() + end) + end +end diff --git a/test/http/adapter_helper/gun_test.exs b/test/http/adapter_helper/gun_test.exs index 2e961826e..80589c73d 100644 --- a/test/http/adapter_helper/gun_test.exs +++ b/test/http/adapter_helper/gun_test.exs @@ -9,24 +9,10 @@ defmodule Pleroma.HTTP.AdapterHelper.GunTest do import Mox alias Pleroma.Config - alias Pleroma.Gun.Conn alias Pleroma.HTTP.AdapterHelper.Gun - alias Pleroma.Pool.Connections setup :verify_on_exit! - defp gun_mock(_) do - gun_mock() - :ok - end - - defp gun_mock do - Pleroma.GunMock - |> stub(:open, fn _, _, _ -> Task.start_link(fn -> Process.sleep(1000) end) end) - |> stub(:await_up, fn _, _ -> {:ok, :http} end) - |> stub(:set_owner, fn _, _ -> :ok end) - end - describe "options/1" do setup do: clear_config([:http, :adapter], a: 1, b: 2) @@ -35,7 +21,6 @@ test "https url with default port" do opts = Gun.options([receive_conn: false], uri) assert opts[:certificates_verification] - assert opts[:tls_opts][:log_level] == :warning end test "https ipv4 with default port" do @@ -43,7 +28,6 @@ test "https ipv4 with default port" do opts = Gun.options([receive_conn: false], uri) assert opts[:certificates_verification] - assert opts[:tls_opts][:log_level] == :warning end test "https ipv6 with default port" do @@ -51,7 +35,6 @@ test "https ipv6 with default port" do opts = Gun.options([receive_conn: false], uri) assert opts[:certificates_verification] - assert opts[:tls_opts][:log_level] == :warning end test "https url with non standart port" do @@ -62,46 +45,12 @@ test "https url with non standart port" do assert opts[:certificates_verification] end - test "get conn on next request" do - gun_mock() - level = Application.get_env(:logger, :level) - Logger.configure(level: :debug) - on_exit(fn -> Logger.configure(level: level) end) - uri = URI.parse("http://some-domain2.com") - - opts = Gun.options(uri) - - assert opts[:conn] == nil - assert opts[:close_conn] == nil - - Process.sleep(50) - opts = Gun.options(uri) - - assert is_pid(opts[:conn]) - assert opts[:close_conn] == false - end - test "merges with defaul http adapter config" do defaults = Gun.options([receive_conn: false], URI.parse("https://example.com")) assert Keyword.has_key?(defaults, :a) assert Keyword.has_key?(defaults, :b) end - test "default ssl adapter opts with connection" do - gun_mock() - uri = URI.parse("https://some-domain.com") - - :ok = Conn.open(uri, :gun_connections) - - opts = Gun.options(uri) - - assert opts[:certificates_verification] - refute opts[:tls_opts] == [] - - assert opts[:close_conn] == false - assert is_pid(opts[:conn]) - end - test "parses string proxy host & port" do proxy = Config.get([:http, :proxy_url]) Config.put([:http, :proxy_url], "localhost:8123") @@ -132,127 +81,4 @@ test "passed opts have more weight than defaults" do assert opts[:proxy] == {'example.com', 4321} end end - - describe "options/1 with receive_conn parameter" do - setup :gun_mock - - test "receive conn by default" do - uri = URI.parse("http://another-domain.com") - :ok = Conn.open(uri, :gun_connections) - - received_opts = Gun.options(uri) - assert received_opts[:close_conn] == false - assert is_pid(received_opts[:conn]) - end - - test "don't receive conn if receive_conn is false" do - uri = URI.parse("http://another-domain.com") - :ok = Conn.open(uri, :gun_connections) - - opts = [receive_conn: false] - received_opts = Gun.options(opts, uri) - assert received_opts[:close_conn] == nil - assert received_opts[:conn] == nil - end - end - - describe "after_request/1" do - setup :gun_mock - - test "body_as not chunks" do - uri = URI.parse("http://some-domain.com") - :ok = Conn.open(uri, :gun_connections) - opts = Gun.options(uri) - :ok = Gun.after_request(opts) - conn = opts[:conn] - - assert %Connections{ - conns: %{ - "http:some-domain.com:80" => %Pleroma.Gun.Conn{ - conn: ^conn, - conn_state: :idle, - used_by: [] - } - } - } = Connections.get_state(:gun_connections) - end - - test "body_as chunks" do - uri = URI.parse("http://some-domain.com") - :ok = Conn.open(uri, :gun_connections) - opts = Gun.options([body_as: :chunks], uri) - :ok = Gun.after_request(opts) - conn = opts[:conn] - self = self() - - assert %Connections{ - conns: %{ - "http:some-domain.com:80" => %Pleroma.Gun.Conn{ - conn: ^conn, - conn_state: :active, - used_by: [{^self, _}] - } - } - } = Connections.get_state(:gun_connections) - end - - test "with no connection" do - uri = URI.parse("http://uniq-domain.com") - - :ok = Conn.open(uri, :gun_connections) - - opts = Gun.options([body_as: :chunks], uri) - conn = opts[:conn] - opts = Keyword.delete(opts, :conn) - self = self() - - :ok = Gun.after_request(opts) - - assert %Connections{ - conns: %{ - "http:uniq-domain.com:80" => %Pleroma.Gun.Conn{ - conn: ^conn, - conn_state: :active, - used_by: [{^self, _}] - } - } - } = Connections.get_state(:gun_connections) - end - - test "with ipv4" do - uri = URI.parse("http://127.0.0.1") - :ok = Conn.open(uri, :gun_connections) - opts = Gun.options(uri) - :ok = Gun.after_request(opts) - conn = opts[:conn] - - assert %Connections{ - conns: %{ - "http:127.0.0.1:80" => %Pleroma.Gun.Conn{ - conn: ^conn, - conn_state: :idle, - used_by: [] - } - } - } = Connections.get_state(:gun_connections) - end - - test "with ipv6" do - uri = URI.parse("http://[2a03:2880:f10c:83:face:b00c:0:25de]") - :ok = Conn.open(uri, :gun_connections) - opts = Gun.options(uri) - :ok = Gun.after_request(opts) - conn = opts[:conn] - - assert %Connections{ - conns: %{ - "http:2a03:2880:f10c:83:face:b00c:0:25de:80" => %Pleroma.Gun.Conn{ - conn: ^conn, - conn_state: :idle, - used_by: [] - } - } - } = Connections.get_state(:gun_connections) - end - end end diff --git a/test/http/connection_test.exs b/test/http/connection_test.exs deleted file mode 100644 index 7c94a50b2..000000000 --- a/test/http/connection_test.exs +++ /dev/null @@ -1,135 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.HTTP.ConnectionTest do - use ExUnit.Case - use Pleroma.Tests.Helpers - - import ExUnit.CaptureLog - - alias Pleroma.Config - alias Pleroma.HTTP.Connection - - describe "parse_host/1" do - test "as atom to charlist" do - assert Connection.parse_host(:localhost) == 'localhost' - end - - test "as string to charlist" do - assert Connection.parse_host("localhost.com") == 'localhost.com' - end - - test "as string ip to tuple" do - assert Connection.parse_host("127.0.0.1") == {127, 0, 0, 1} - end - end - - describe "parse_proxy/1" do - test "ip with port" do - assert Connection.parse_proxy("127.0.0.1:8123") == {:ok, {127, 0, 0, 1}, 8123} - end - - test "host with port" do - assert Connection.parse_proxy("localhost:8123") == {:ok, 'localhost', 8123} - end - - test "as tuple" do - assert Connection.parse_proxy({:socks4, :localhost, 9050}) == - {:ok, :socks4, 'localhost', 9050} - end - - test "as tuple with string host" do - assert Connection.parse_proxy({:socks5, "localhost", 9050}) == - {:ok, :socks5, 'localhost', 9050} - end - end - - describe "parse_proxy/1 errors" do - test "ip without port" do - capture_log(fn -> - assert Connection.parse_proxy("127.0.0.1") == {:error, :invalid_proxy} - end) =~ "parsing proxy fail \"127.0.0.1\"" - end - - test "host without port" do - capture_log(fn -> - assert Connection.parse_proxy("localhost") == {:error, :invalid_proxy} - end) =~ "parsing proxy fail \"localhost\"" - end - - test "host with bad port" do - capture_log(fn -> - assert Connection.parse_proxy("localhost:port") == {:error, :invalid_proxy_port} - end) =~ "parsing port in proxy fail \"localhost:port\"" - end - - test "ip with bad port" do - capture_log(fn -> - assert Connection.parse_proxy("127.0.0.1:15.9") == {:error, :invalid_proxy_port} - end) =~ "parsing port in proxy fail \"127.0.0.1:15.9\"" - end - - test "as tuple without port" do - capture_log(fn -> - assert Connection.parse_proxy({:socks5, :localhost}) == {:error, :invalid_proxy} - end) =~ "parsing proxy fail {:socks5, :localhost}" - end - - test "with nil" do - assert Connection.parse_proxy(nil) == nil - end - end - - describe "options/3" do - setup do: clear_config([:http, :proxy_url]) - - test "without proxy_url in config" do - Config.delete([:http, :proxy_url]) - - opts = Connection.options(%URI{}) - refute Keyword.has_key?(opts, :proxy) - end - - test "parses string proxy host & port" do - Config.put([:http, :proxy_url], "localhost:8123") - - opts = Connection.options(%URI{}) - assert opts[:proxy] == {'localhost', 8123} - end - - test "parses tuple proxy scheme host and port" do - Config.put([:http, :proxy_url], {:socks, 'localhost', 1234}) - - opts = Connection.options(%URI{}) - assert opts[:proxy] == {:socks, 'localhost', 1234} - end - - test "passed opts have more weight than defaults" do - Config.put([:http, :proxy_url], {:socks5, 'localhost', 1234}) - - opts = Connection.options(%URI{}, proxy: {'example.com', 4321}) - - assert opts[:proxy] == {'example.com', 4321} - end - end - - describe "format_host/1" do - test "with domain" do - assert Connection.format_host("example.com") == 'example.com' - end - - test "with idna domain" do - assert Connection.format_host("ですexample.com") == 'xn--example-183fne.com' - end - - test "with ipv4" do - assert Connection.format_host("127.0.0.1") == '127.0.0.1' - end - - test "with ipv6" do - assert Connection.format_host("2a03:2880:f10c:83:face:b00c:0:25de") == - '2a03:2880:f10c:83:face:b00c:0:25de' - end - end -end diff --git a/test/migrations/20200716195806_autolinker_to_linkify_test.exs b/test/migrations/20200716195806_autolinker_to_linkify_test.exs new file mode 100644 index 000000000..250d11c61 --- /dev/null +++ b/test/migrations/20200716195806_autolinker_to_linkify_test.exs @@ -0,0 +1,68 @@ +defmodule Pleroma.Repo.Migrations.AutolinkerToLinkifyTest do + use Pleroma.DataCase + import Pleroma.Factory + import Pleroma.Tests.Helpers + alias Pleroma.ConfigDB + + setup do: clear_config(Pleroma.Formatter) + setup_all do: require_migration("20200716195806_autolinker_to_linkify") + + test "change/0 converts auto_linker opts for Pleroma.Formatter", %{migration: migration} do + autolinker_opts = [ + extra: true, + validate_tld: true, + class: false, + strip_prefix: false, + new_window: false, + rel: "testing" + ] + + insert(:config, group: :auto_linker, key: :opts, value: autolinker_opts) + + migration.change() + + assert nil == ConfigDB.get_by_params(%{group: :auto_linker, key: :opts}) + + %{value: new_opts} = ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Formatter}) + + assert new_opts == [ + class: false, + extra: true, + new_window: false, + rel: "testing", + strip_prefix: false + ] + + Pleroma.Config.put(Pleroma.Formatter, new_opts) + assert new_opts == Pleroma.Config.get(Pleroma.Formatter) + + {text, _mentions, []} = + Pleroma.Formatter.linkify( + "https://www.businessinsider.com/walmart-will-close-stores-on-thanksgiving-ending-black-friday-tradition-2020-7\n\nOmg will COVID finally end Black Friday???" + ) + + assert text == + "https://www.businessinsider.com/walmart-will-close-stores-on-thanksgiving-ending-black-friday-tradition-2020-7\n\nOmg will COVID finally end Black Friday???" + end + + test "transform_opts/1 returns a list of compatible opts", %{migration: migration} do + old_opts = [ + extra: true, + validate_tld: true, + class: false, + strip_prefix: false, + new_window: false, + rel: "qqq" + ] + + expected_opts = [ + class: false, + extra: true, + new_window: false, + rel: "qqq", + strip_prefix: false + ] + + assert migration.transform_opts(old_opts) == expected_opts + end +end diff --git a/test/migrations/20200722185515_fix_malformed_formatter_config_test.exs b/test/migrations/20200722185515_fix_malformed_formatter_config_test.exs new file mode 100644 index 000000000..d3490478e --- /dev/null +++ b/test/migrations/20200722185515_fix_malformed_formatter_config_test.exs @@ -0,0 +1,66 @@ +defmodule Pleroma.Repo.Migrations.FixMalformedFormatterConfigTest do + use Pleroma.DataCase + import Pleroma.Factory + import Pleroma.Tests.Helpers + alias Pleroma.ConfigDB + + setup do: clear_config(Pleroma.Formatter) + setup_all do: require_migration("20200722185515_fix_malformed_formatter_config") + + test "change/0 converts a map into a list", %{migration: migration} do + incorrect_opts = %{ + class: false, + extra: true, + new_window: false, + rel: "F", + strip_prefix: false + } + + insert(:config, group: :pleroma, key: Pleroma.Formatter, value: incorrect_opts) + + assert :ok == migration.change() + + %{value: new_opts} = ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Formatter}) + + assert new_opts == [ + class: false, + extra: true, + new_window: false, + rel: "F", + strip_prefix: false + ] + + Pleroma.Config.put(Pleroma.Formatter, new_opts) + assert new_opts == Pleroma.Config.get(Pleroma.Formatter) + + {text, _mentions, []} = + Pleroma.Formatter.linkify( + "https://www.businessinsider.com/walmart-will-close-stores-on-thanksgiving-ending-black-friday-tradition-2020-7\n\nOmg will COVID finally end Black Friday???" + ) + + assert text == + "https://www.businessinsider.com/walmart-will-close-stores-on-thanksgiving-ending-black-friday-tradition-2020-7\n\nOmg will COVID finally end Black Friday???" + end + + test "change/0 skips if Pleroma.Formatter config is already a list", %{migration: migration} do + opts = [ + class: false, + extra: true, + new_window: false, + rel: "ugc", + strip_prefix: false + ] + + insert(:config, group: :pleroma, key: Pleroma.Formatter, value: opts) + + assert :skipped == migration.change() + + %{value: new_opts} = ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Formatter}) + + assert new_opts == opts + end + + test "change/0 skips if Pleroma.Formatter is empty", %{migration: migration} do + assert :skipped == migration.change() + end +end diff --git a/test/notification_test.exs b/test/notification_test.exs index 13e82ab2a..8243cfd34 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -246,49 +246,18 @@ test "it creates a notification for an activity from a muted thread" do assert Notification.create_notification(activity, muter) end - test "it disables notifications from followers" do - follower = insert(:user) - - followed = - insert(:user, notification_settings: %Pleroma.User.NotificationSetting{followers: false}) - - User.follow(follower, followed) - {:ok, activity} = CommonAPI.post(follower, %{status: "hey @#{followed.nickname}"}) - refute Notification.create_notification(activity, followed) - end - - test "it disables notifications from non-followers" do + test "it disables notifications from strangers" do follower = insert(:user) followed = insert(:user, - notification_settings: %Pleroma.User.NotificationSetting{non_followers: false} + notification_settings: %Pleroma.User.NotificationSetting{block_from_strangers: true} ) {:ok, activity} = CommonAPI.post(follower, %{status: "hey @#{followed.nickname}"}) refute Notification.create_notification(activity, followed) end - test "it disables notifications from people the user follows" do - follower = - insert(:user, notification_settings: %Pleroma.User.NotificationSetting{follows: false}) - - followed = insert(:user) - User.follow(follower, followed) - follower = Repo.get(User, follower.id) - {:ok, activity} = CommonAPI.post(followed, %{status: "hey @#{follower.nickname}"}) - refute Notification.create_notification(activity, follower) - end - - test "it disables notifications from people the user does not follow" do - follower = - insert(:user, notification_settings: %Pleroma.User.NotificationSetting{non_follows: false}) - - followed = insert(:user) - {:ok, activity} = CommonAPI.post(followed, %{status: "hey @#{follower.nickname}"}) - refute Notification.create_notification(activity, follower) - end - test "it doesn't create a notification for user if he is the activity author" do activity = insert(:note_activity) author = User.get_cached_by_ap_id(activity.data["actor"]) diff --git a/test/pagination_test.exs b/test/pagination_test.exs index 9165427ae..e526f23e8 100644 --- a/test/pagination_test.exs +++ b/test/pagination_test.exs @@ -54,6 +54,20 @@ test "paginates by min_id & limit", %{notes: notes} do assert length(paginated) == 1 end + + test "handles id gracefully", %{notes: notes} do + id = Enum.at(notes, 1).id |> Integer.to_string() + + paginated = + Pagination.fetch_paginated(Object, %{ + id: "9s99Hq44Cnv8PKBwWG", + max_id: id, + limit: 20, + offset: 0 + }) + + assert length(paginated) == 1 + end end describe "offset" do diff --git a/test/pool/connections_test.exs b/test/pool/connections_test.exs deleted file mode 100644 index aeda54875..000000000 --- a/test/pool/connections_test.exs +++ /dev/null @@ -1,760 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Pool.ConnectionsTest do - use ExUnit.Case, async: true - use Pleroma.Tests.Helpers - - import ExUnit.CaptureLog - import Mox - - alias Pleroma.Gun.Conn - alias Pleroma.GunMock - alias Pleroma.Pool.Connections - - setup :verify_on_exit! - - setup_all do - name = :test_connections - {:ok, pid} = Connections.start_link({name, [checkin_timeout: 150]}) - {:ok, _} = Registry.start_link(keys: :unique, name: Pleroma.GunMock) - - on_exit(fn -> - if Process.alive?(pid), do: GenServer.stop(name) - end) - - {:ok, name: name} - end - - defp open_mock(num \\ 1) do - GunMock - |> expect(:open, num, &start_and_register(&1, &2, &3)) - |> expect(:await_up, num, fn _, _ -> {:ok, :http} end) - |> expect(:set_owner, num, fn _, _ -> :ok end) - end - - defp connect_mock(mock) do - mock - |> expect(:connect, &connect(&1, &2)) - |> expect(:await, &await(&1, &2)) - end - - defp info_mock(mock), do: expect(mock, :info, &info(&1)) - - defp start_and_register('gun-not-up.com', _, _), do: {:error, :timeout} - - defp start_and_register(host, port, _) do - {:ok, pid} = Task.start_link(fn -> Process.sleep(1000) end) - - scheme = - case port do - 443 -> "https" - _ -> "http" - end - - Registry.register(GunMock, pid, %{ - origin_scheme: scheme, - origin_host: host, - origin_port: port - }) - - {:ok, pid} - end - - defp info(pid) do - [{_, info}] = Registry.lookup(GunMock, pid) - info - end - - defp connect(pid, _) do - ref = make_ref() - Registry.register(GunMock, ref, pid) - ref - end - - defp await(pid, ref) do - [{_, ^pid}] = Registry.lookup(GunMock, ref) - {:response, :fin, 200, []} - end - - defp now, do: :os.system_time(:second) - - describe "alive?/2" do - test "is alive", %{name: name} do - assert Connections.alive?(name) - end - - test "returns false if not started" do - refute Connections.alive?(:some_random_name) - end - end - - test "opens connection and reuse it on next request", %{name: name} do - open_mock() - url = "http://some-domain.com" - key = "http:some-domain.com:80" - refute Connections.checkin(url, name) - :ok = Conn.open(url, name) - - conn = Connections.checkin(url, name) - assert is_pid(conn) - assert Process.alive?(conn) - - self = self() - - %Connections{ - conns: %{ - ^key => %Conn{ - conn: ^conn, - gun_state: :up, - used_by: [{^self, _}], - conn_state: :active - } - } - } = Connections.get_state(name) - - reused_conn = Connections.checkin(url, name) - - assert conn == reused_conn - - %Connections{ - conns: %{ - ^key => %Conn{ - conn: ^conn, - gun_state: :up, - used_by: [{^self, _}, {^self, _}], - conn_state: :active - } - } - } = Connections.get_state(name) - - :ok = Connections.checkout(conn, self, name) - - %Connections{ - conns: %{ - ^key => %Conn{ - conn: ^conn, - gun_state: :up, - used_by: [{^self, _}], - conn_state: :active - } - } - } = Connections.get_state(name) - - :ok = Connections.checkout(conn, self, name) - - %Connections{ - conns: %{ - ^key => %Conn{ - conn: ^conn, - gun_state: :up, - used_by: [], - conn_state: :idle - } - } - } = Connections.get_state(name) - end - - test "reuse connection for idna domains", %{name: name} do - open_mock() - url = "http://ですsome-domain.com" - refute Connections.checkin(url, name) - - :ok = Conn.open(url, name) - - conn = Connections.checkin(url, name) - assert is_pid(conn) - assert Process.alive?(conn) - - self = self() - - %Connections{ - conns: %{ - "http:ですsome-domain.com:80" => %Conn{ - conn: ^conn, - gun_state: :up, - used_by: [{^self, _}], - conn_state: :active - } - } - } = Connections.get_state(name) - - reused_conn = Connections.checkin(url, name) - - assert conn == reused_conn - end - - test "reuse for ipv4", %{name: name} do - open_mock() - url = "http://127.0.0.1" - - refute Connections.checkin(url, name) - - :ok = Conn.open(url, name) - - conn = Connections.checkin(url, name) - assert is_pid(conn) - assert Process.alive?(conn) - - self = self() - - %Connections{ - conns: %{ - "http:127.0.0.1:80" => %Conn{ - conn: ^conn, - gun_state: :up, - used_by: [{^self, _}], - conn_state: :active - } - } - } = Connections.get_state(name) - - reused_conn = Connections.checkin(url, name) - - assert conn == reused_conn - - :ok = Connections.checkout(conn, self, name) - :ok = Connections.checkout(reused_conn, self, name) - - %Connections{ - conns: %{ - "http:127.0.0.1:80" => %Conn{ - conn: ^conn, - gun_state: :up, - used_by: [], - conn_state: :idle - } - } - } = Connections.get_state(name) - end - - test "reuse for ipv6", %{name: name} do - open_mock() - url = "http://[2a03:2880:f10c:83:face:b00c:0:25de]" - - refute Connections.checkin(url, name) - - :ok = Conn.open(url, name) - - conn = Connections.checkin(url, name) - assert is_pid(conn) - assert Process.alive?(conn) - - self = self() - - %Connections{ - conns: %{ - "http:2a03:2880:f10c:83:face:b00c:0:25de:80" => %Conn{ - conn: ^conn, - gun_state: :up, - used_by: [{^self, _}], - conn_state: :active - } - } - } = Connections.get_state(name) - - reused_conn = Connections.checkin(url, name) - - assert conn == reused_conn - end - - test "up and down ipv4", %{name: name} do - open_mock() - |> info_mock() - |> allow(self(), name) - - self = self() - url = "http://127.0.0.1" - :ok = Conn.open(url, name) - conn = Connections.checkin(url, name) - send(name, {:gun_down, conn, nil, nil, nil}) - send(name, {:gun_up, conn, nil}) - - %Connections{ - conns: %{ - "http:127.0.0.1:80" => %Conn{ - conn: ^conn, - gun_state: :up, - used_by: [{^self, _}], - conn_state: :active - } - } - } = Connections.get_state(name) - end - - test "up and down ipv6", %{name: name} do - self = self() - - open_mock() - |> info_mock() - |> allow(self, name) - - url = "http://[2a03:2880:f10c:83:face:b00c:0:25de]" - :ok = Conn.open(url, name) - conn = Connections.checkin(url, name) - send(name, {:gun_down, conn, nil, nil, nil}) - send(name, {:gun_up, conn, nil}) - - %Connections{ - conns: %{ - "http:2a03:2880:f10c:83:face:b00c:0:25de:80" => %Conn{ - conn: ^conn, - gun_state: :up, - used_by: [{^self, _}], - conn_state: :active - } - } - } = Connections.get_state(name) - end - - test "reuses connection based on protocol", %{name: name} do - open_mock(2) - http_url = "http://some-domain.com" - http_key = "http:some-domain.com:80" - https_url = "https://some-domain.com" - https_key = "https:some-domain.com:443" - - refute Connections.checkin(http_url, name) - :ok = Conn.open(http_url, name) - conn = Connections.checkin(http_url, name) - assert is_pid(conn) - assert Process.alive?(conn) - - refute Connections.checkin(https_url, name) - :ok = Conn.open(https_url, name) - https_conn = Connections.checkin(https_url, name) - - refute conn == https_conn - - reused_https = Connections.checkin(https_url, name) - - refute conn == reused_https - - assert reused_https == https_conn - - %Connections{ - conns: %{ - ^http_key => %Conn{ - conn: ^conn, - gun_state: :up - }, - ^https_key => %Conn{ - conn: ^https_conn, - gun_state: :up - } - } - } = Connections.get_state(name) - end - - test "connection can't get up", %{name: name} do - expect(GunMock, :open, &start_and_register(&1, &2, &3)) - url = "http://gun-not-up.com" - - assert capture_log(fn -> - refute Conn.open(url, name) - refute Connections.checkin(url, name) - end) =~ - "Opening connection to http://gun-not-up.com failed with error {:error, :timeout}" - end - - test "process gun_down message and then gun_up", %{name: name} do - self = self() - - open_mock() - |> info_mock() - |> allow(self, name) - - url = "http://gun-down-and-up.com" - key = "http:gun-down-and-up.com:80" - :ok = Conn.open(url, name) - conn = Connections.checkin(url, name) - - assert is_pid(conn) - assert Process.alive?(conn) - - %Connections{ - conns: %{ - ^key => %Conn{ - conn: ^conn, - gun_state: :up, - used_by: [{^self, _}] - } - } - } = Connections.get_state(name) - - send(name, {:gun_down, conn, :http, nil, nil}) - - %Connections{ - conns: %{ - ^key => %Conn{ - conn: ^conn, - gun_state: :down, - used_by: [{^self, _}] - } - } - } = Connections.get_state(name) - - send(name, {:gun_up, conn, :http}) - - conn2 = Connections.checkin(url, name) - assert conn == conn2 - - assert is_pid(conn2) - assert Process.alive?(conn2) - - %Connections{ - conns: %{ - ^key => %Conn{ - conn: _, - gun_state: :up, - used_by: [{^self, _}, {^self, _}] - } - } - } = Connections.get_state(name) - end - - test "async processes get same conn for same domain", %{name: name} do - open_mock() - url = "http://some-domain.com" - :ok = Conn.open(url, name) - - tasks = - for _ <- 1..5 do - Task.async(fn -> - Connections.checkin(url, name) - end) - end - - tasks_with_results = Task.yield_many(tasks) - - results = - Enum.map(tasks_with_results, fn {task, res} -> - res || Task.shutdown(task, :brutal_kill) - end) - - conns = for {:ok, value} <- results, do: value - - %Connections{ - conns: %{ - "http:some-domain.com:80" => %Conn{ - conn: conn, - gun_state: :up - } - } - } = Connections.get_state(name) - - assert Enum.all?(conns, fn res -> res == conn end) - end - - test "remove frequently used and idle", %{name: name} do - open_mock(3) - self = self() - http_url = "http://some-domain.com" - https_url = "https://some-domain.com" - :ok = Conn.open(https_url, name) - :ok = Conn.open(http_url, name) - - conn1 = Connections.checkin(https_url, name) - - [conn2 | _conns] = - for _ <- 1..4 do - Connections.checkin(http_url, name) - end - - http_key = "http:some-domain.com:80" - - %Connections{ - conns: %{ - ^http_key => %Conn{ - conn: ^conn2, - gun_state: :up, - conn_state: :active, - used_by: [{^self, _}, {^self, _}, {^self, _}, {^self, _}] - }, - "https:some-domain.com:443" => %Conn{ - conn: ^conn1, - gun_state: :up, - conn_state: :active, - used_by: [{^self, _}] - } - } - } = Connections.get_state(name) - - :ok = Connections.checkout(conn1, self, name) - - another_url = "http://another-domain.com" - :ok = Conn.open(another_url, name) - conn = Connections.checkin(another_url, name) - - %Connections{ - conns: %{ - "http:another-domain.com:80" => %Conn{ - conn: ^conn, - gun_state: :up - }, - ^http_key => %Conn{ - conn: _, - gun_state: :up - } - } - } = Connections.get_state(name) - end - - describe "with proxy" do - test "as ip", %{name: name} do - open_mock() - |> connect_mock() - - url = "http://proxy-string.com" - key = "http:proxy-string.com:80" - :ok = Conn.open(url, name, proxy: {{127, 0, 0, 1}, 8123}) - - conn = Connections.checkin(url, name) - - %Connections{ - conns: %{ - ^key => %Conn{ - conn: ^conn, - gun_state: :up - } - } - } = Connections.get_state(name) - - reused_conn = Connections.checkin(url, name) - - assert reused_conn == conn - end - - test "as host", %{name: name} do - open_mock() - |> connect_mock() - - url = "http://proxy-tuple-atom.com" - :ok = Conn.open(url, name, proxy: {'localhost', 9050}) - conn = Connections.checkin(url, name) - - %Connections{ - conns: %{ - "http:proxy-tuple-atom.com:80" => %Conn{ - conn: ^conn, - gun_state: :up - } - } - } = Connections.get_state(name) - - reused_conn = Connections.checkin(url, name) - - assert reused_conn == conn - end - - test "as ip and ssl", %{name: name} do - open_mock() - |> connect_mock() - - url = "https://proxy-string.com" - - :ok = Conn.open(url, name, proxy: {{127, 0, 0, 1}, 8123}) - conn = Connections.checkin(url, name) - - %Connections{ - conns: %{ - "https:proxy-string.com:443" => %Conn{ - conn: ^conn, - gun_state: :up - } - } - } = Connections.get_state(name) - - reused_conn = Connections.checkin(url, name) - - assert reused_conn == conn - end - - test "as host and ssl", %{name: name} do - open_mock() - |> connect_mock() - - url = "https://proxy-tuple-atom.com" - :ok = Conn.open(url, name, proxy: {'localhost', 9050}) - conn = Connections.checkin(url, name) - - %Connections{ - conns: %{ - "https:proxy-tuple-atom.com:443" => %Conn{ - conn: ^conn, - gun_state: :up - } - } - } = Connections.get_state(name) - - reused_conn = Connections.checkin(url, name) - - assert reused_conn == conn - end - - test "with socks type", %{name: name} do - open_mock() - - url = "http://proxy-socks.com" - - :ok = Conn.open(url, name, proxy: {:socks5, 'localhost', 1234}) - - conn = Connections.checkin(url, name) - - %Connections{ - conns: %{ - "http:proxy-socks.com:80" => %Conn{ - conn: ^conn, - gun_state: :up - } - } - } = Connections.get_state(name) - - reused_conn = Connections.checkin(url, name) - - assert reused_conn == conn - end - - test "with socks4 type and ssl", %{name: name} do - open_mock() - url = "https://proxy-socks.com" - - :ok = Conn.open(url, name, proxy: {:socks4, 'localhost', 1234}) - - conn = Connections.checkin(url, name) - - %Connections{ - conns: %{ - "https:proxy-socks.com:443" => %Conn{ - conn: ^conn, - gun_state: :up - } - } - } = Connections.get_state(name) - - reused_conn = Connections.checkin(url, name) - - assert reused_conn == conn - end - end - - describe "crf/3" do - setup do - crf = Connections.crf(1, 10, 1) - {:ok, crf: crf} - end - - test "more used will have crf higher", %{crf: crf} do - # used 3 times - crf1 = Connections.crf(1, 10, crf) - crf1 = Connections.crf(1, 10, crf1) - - # used 2 times - crf2 = Connections.crf(1, 10, crf) - - assert crf1 > crf2 - end - - test "recently used will have crf higher on equal references", %{crf: crf} do - # used 3 sec ago - crf1 = Connections.crf(3, 10, crf) - - # used 4 sec ago - crf2 = Connections.crf(4, 10, crf) - - assert crf1 > crf2 - end - - test "equal crf on equal reference and time", %{crf: crf} do - # used 2 times - crf1 = Connections.crf(1, 10, crf) - - # used 2 times - crf2 = Connections.crf(1, 10, crf) - - assert crf1 == crf2 - end - - test "recently used will have higher crf", %{crf: crf} do - crf1 = Connections.crf(2, 10, crf) - crf1 = Connections.crf(1, 10, crf1) - - crf2 = Connections.crf(3, 10, crf) - crf2 = Connections.crf(4, 10, crf2) - assert crf1 > crf2 - end - end - - describe "get_unused_conns/1" do - test "crf is equalent, sorting by reference", %{name: name} do - Connections.add_conn(name, "1", %Conn{ - conn_state: :idle, - last_reference: now() - 1 - }) - - Connections.add_conn(name, "2", %Conn{ - conn_state: :idle, - last_reference: now() - }) - - assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(name) - end - - test "reference is equalent, sorting by crf", %{name: name} do - Connections.add_conn(name, "1", %Conn{ - conn_state: :idle, - crf: 1.999 - }) - - Connections.add_conn(name, "2", %Conn{ - conn_state: :idle, - crf: 2 - }) - - assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(name) - end - - test "higher crf and lower reference", %{name: name} do - Connections.add_conn(name, "1", %Conn{ - conn_state: :idle, - crf: 3, - last_reference: now() - 1 - }) - - Connections.add_conn(name, "2", %Conn{ - conn_state: :idle, - crf: 2, - last_reference: now() - }) - - assert [{"2", _unused_conn} | _others] = Connections.get_unused_conns(name) - end - - test "lower crf and lower reference", %{name: name} do - Connections.add_conn(name, "1", %Conn{ - conn_state: :idle, - crf: 1.99, - last_reference: now() - 1 - }) - - Connections.add_conn(name, "2", %Conn{ - conn_state: :idle, - crf: 2, - last_reference: now() - }) - - assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(name) - end - end - - test "count/1" do - name = :test_count - {:ok, _} = Connections.start_link({name, [checkin_timeout: 150]}) - assert Connections.count(name) == 0 - Connections.add_conn(name, "1", %Conn{conn: self()}) - assert Connections.count(name) == 1 - Connections.remove_conn(name, "1") - assert Connections.count(name) == 0 - end -end diff --git a/test/support/helpers.ex b/test/support/helpers.ex index 26281b45e..5cbf2e291 100644 --- a/test/support/helpers.ex +++ b/test/support/helpers.ex @@ -32,6 +32,11 @@ defmacro clear_config(config_path, temp_setting) do end end + def require_migration(migration_name) do + [{module, _}] = Code.require_file("#{migration_name}.exs", "priv/repo/migrations") + {:ok, %{migration: module}} + end + defmacro __using__(_opts) do quote do import Pleroma.Tests.Helpers, diff --git a/test/upload/filter/anonymize_filename_test.exs b/test/upload/filter/anonymize_filename_test.exs index 2d5c580f1..adff70f57 100644 --- a/test/upload/filter/anonymize_filename_test.exs +++ b/test/upload/filter/anonymize_filename_test.exs @@ -9,6 +9,8 @@ defmodule Pleroma.Upload.Filter.AnonymizeFilenameTest do alias Pleroma.Upload setup do + File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") + upload_file = %Upload{ name: "an… image.jpg", content_type: "image/jpg", diff --git a/test/uploaders/local_test.exs b/test/uploaders/local_test.exs index ae2cfef94..18122ff6c 100644 --- a/test/uploaders/local_test.exs +++ b/test/uploaders/local_test.exs @@ -14,6 +14,7 @@ test "it returns path to local folder for files" do describe "put_file/1" do test "put file to local folder" do + File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") file_path = "local_upload/files/image.jpg" file = %Pleroma.Upload{ @@ -32,6 +33,7 @@ test "put file to local folder" do describe "delete_file/1" do test "deletes local file" do + File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") file_path = "local_upload/files/image.jpg" file = %Pleroma.Upload{ diff --git a/test/user/notification_setting_test.exs b/test/user/notification_setting_test.exs index 95bca22c4..308da216a 100644 --- a/test/user/notification_setting_test.exs +++ b/test/user/notification_setting_test.exs @@ -8,11 +8,11 @@ defmodule Pleroma.User.NotificationSettingTest do alias Pleroma.User.NotificationSetting describe "changeset/2" do - test "sets valid privacy option" do + test "sets option to hide notification contents" do changeset = NotificationSetting.changeset( %NotificationSetting{}, - %{"privacy_option" => true} + %{"hide_notification_contents" => true} ) assert %Ecto.Changeset{valid?: true} = changeset diff --git a/test/user_test.exs b/test/user_test.exs index 57cc054af..8cdf3a891 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -17,6 +17,7 @@ defmodule Pleroma.UserTest do import Pleroma.Factory import ExUnit.CaptureLog + import Swoosh.TestAssertions setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -385,9 +386,11 @@ test "fetches correct profile for nickname beginning with number" do password_confirmation: "test", email: "email@example.com" } + setup do: clear_config([:instance, :autofollowed_nicknames]) setup do: clear_config([:instance, :welcome_message]) setup do: clear_config([:instance, :welcome_user_nickname]) + setup do: clear_config([:instance, :account_activation_required]) test "it autofollows accounts that are set for it" do user = insert(:user) @@ -421,7 +424,14 @@ test "it sends a welcome message if it is set" do assert activity.actor == welcome_user.ap_id end - setup do: clear_config([:instance, :account_activation_required]) + test "it sends a confirm email" do + Pleroma.Config.put([:instance, :account_activation_required], true) + + cng = User.register_changeset(%User{}, @full_user_data) + {:ok, registered_user} = User.register(cng) + ObanHelpers.perform_all() + assert_email_sent(Pleroma.Emails.UserEmail.account_confirmation_email(registered_user)) + end test "it requires an email, name, nickname and password, bio is optional when account_activation_required is enabled" do Pleroma.Config.put([:instance, :account_activation_required], true) diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index e722f7c04..ed900d8f8 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -1082,6 +1082,45 @@ test "it increases like count when receiving a like action", %{conn: conn} do assert object = Object.get_by_ap_id(note_object.data["id"]) assert object.data["like_count"] == 1 end + + test "it doesn't spreads faulty attributedTo or actor fields", %{ + conn: conn, + activity: activity + } do + reimu = insert(:user, nickname: "reimu") + cirno = insert(:user, nickname: "cirno") + + assert reimu.ap_id + assert cirno.ap_id + + activity = + activity + |> put_in(["object", "actor"], reimu.ap_id) + |> put_in(["object", "attributedTo"], reimu.ap_id) + |> put_in(["actor"], reimu.ap_id) + |> put_in(["attributedTo"], reimu.ap_id) + + _reimu_outbox = + conn + |> assign(:user, cirno) + |> put_req_header("content-type", "application/activity+json") + |> post("/users/#{reimu.nickname}/outbox", activity) + |> json_response(403) + + cirno_outbox = + conn + |> assign(:user, cirno) + |> put_req_header("content-type", "application/activity+json") + |> post("/users/#{cirno.nickname}/outbox", activity) + |> json_response(201) + + assert cirno_outbox["attributedTo"] == nil + assert cirno_outbox["actor"] == cirno.ap_id + + assert cirno_object = Object.normalize(cirno_outbox["object"]) + assert cirno_object.data["actor"] == cirno.ap_id + assert cirno_object.data["attributedTo"] == cirno.ap_id + end end describe "/relay/followers" do diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index f3951462f..d6eab7337 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -1179,7 +1179,8 @@ test "it can create a Flag activity", "id" => activity_ap_id, "content" => content, "published" => activity_with_object.object.data["published"], - "actor" => AccountView.render("show.json", %{user: target_account}) + "actor" => + AccountView.render("show.json", %{user: target_account, skip_visibility_check: true}) } assert %Activity{ diff --git a/test/web/activity_pub/mrf/anti_followbot_policy_test.exs b/test/web/activity_pub/mrf/anti_followbot_policy_test.exs index fca0de7c6..3c795f5ac 100644 --- a/test/web/activity_pub/mrf/anti_followbot_policy_test.exs +++ b/test/web/activity_pub/mrf/anti_followbot_policy_test.exs @@ -21,7 +21,7 @@ test "matches followbots by nickname" do "id" => "https://example.com/activities/1234" } - {:reject, nil} = AntiFollowbotPolicy.filter(message) + assert {:reject, "[AntiFollowbotPolicy]" <> _} = AntiFollowbotPolicy.filter(message) end test "matches followbots by display name" do @@ -36,7 +36,7 @@ test "matches followbots by display name" do "id" => "https://example.com/activities/1234" } - {:reject, nil} = AntiFollowbotPolicy.filter(message) + assert {:reject, "[AntiFollowbotPolicy]" <> _} = AntiFollowbotPolicy.filter(message) end end diff --git a/test/web/activity_pub/mrf/hellthread_policy_test.exs b/test/web/activity_pub/mrf/hellthread_policy_test.exs index 6e9daa7f9..26f5bcdaa 100644 --- a/test/web/activity_pub/mrf/hellthread_policy_test.exs +++ b/test/web/activity_pub/mrf/hellthread_policy_test.exs @@ -50,7 +50,8 @@ test "rejects the message if the recipient count is above reject_threshold", %{ } do Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 0, reject_threshold: 2}) - {:reject, nil} = filter(message) + assert {:reject, "[HellthreadPolicy] 3 recipients is over the limit of 2"} == + filter(message) end test "does not reject the message if the recipient count is below reject_threshold", %{ diff --git a/test/web/activity_pub/mrf/keyword_policy_test.exs b/test/web/activity_pub/mrf/keyword_policy_test.exs index fd1f7aec8..b3d0f3d90 100644 --- a/test/web/activity_pub/mrf/keyword_policy_test.exs +++ b/test/web/activity_pub/mrf/keyword_policy_test.exs @@ -25,7 +25,8 @@ test "rejects if string matches in content" do } } - assert {:reject, nil} == KeywordPolicy.filter(message) + assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} = + KeywordPolicy.filter(message) end test "rejects if string matches in summary" do @@ -39,7 +40,8 @@ test "rejects if string matches in summary" do } } - assert {:reject, nil} == KeywordPolicy.filter(message) + assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} = + KeywordPolicy.filter(message) end test "rejects if regex matches in content" do @@ -55,7 +57,8 @@ test "rejects if regex matches in content" do } } - {:reject, nil} == KeywordPolicy.filter(message) + {:reject, "[KeywordPolicy] Matches with rejected keyword"} == + KeywordPolicy.filter(message) end) end @@ -72,7 +75,8 @@ test "rejects if regex matches in summary" do } } - {:reject, nil} == KeywordPolicy.filter(message) + {:reject, "[KeywordPolicy] Matches with rejected keyword"} == + KeywordPolicy.filter(message) end) end end diff --git a/test/web/activity_pub/mrf/mention_policy_test.exs b/test/web/activity_pub/mrf/mention_policy_test.exs index aa003bef5..220309cc9 100644 --- a/test/web/activity_pub/mrf/mention_policy_test.exs +++ b/test/web/activity_pub/mrf/mention_policy_test.exs @@ -76,7 +76,8 @@ test "to" do "to" => ["https://example.com/blocked"] } - assert MentionPolicy.filter(message) == {:reject, nil} + assert MentionPolicy.filter(message) == + {:reject, "[MentionPolicy] Rejected for mention of https://example.com/blocked"} end test "cc" do @@ -88,7 +89,8 @@ test "cc" do "cc" => ["https://example.com/blocked"] } - assert MentionPolicy.filter(message) == {:reject, nil} + assert MentionPolicy.filter(message) == + {:reject, "[MentionPolicy] Rejected for mention of https://example.com/blocked"} end end end diff --git a/test/web/activity_pub/mrf/reject_non_public_test.exs b/test/web/activity_pub/mrf/reject_non_public_test.exs index f36299b86..58b46b9a2 100644 --- a/test/web/activity_pub/mrf/reject_non_public_test.exs +++ b/test/web/activity_pub/mrf/reject_non_public_test.exs @@ -64,7 +64,7 @@ test "it's rejected when addrer of message in the follower addresses of user and } Pleroma.Config.put([:mrf_rejectnonpublic, :allow_followersonly], false) - assert {:reject, nil} = RejectNonPublic.filter(message) + assert {:reject, _} = RejectNonPublic.filter(message) end end @@ -94,7 +94,7 @@ test "it's reject when direct messages aren't allow" do } Pleroma.Config.put([:mrf_rejectnonpublic, :allow_direct], false) - assert {:reject, nil} = RejectNonPublic.filter(message) + assert {:reject, _} = RejectNonPublic.filter(message) end end end diff --git a/test/web/activity_pub/mrf/simple_policy_test.exs b/test/web/activity_pub/mrf/simple_policy_test.exs index b7b9bc6a2..e842d8d8d 100644 --- a/test/web/activity_pub/mrf/simple_policy_test.exs +++ b/test/web/activity_pub/mrf/simple_policy_test.exs @@ -124,7 +124,7 @@ test "has a matching host" do report_message = build_report_message() local_message = build_local_message() - assert SimplePolicy.filter(report_message) == {:reject, nil} + assert {:reject, _} = SimplePolicy.filter(report_message) assert SimplePolicy.filter(local_message) == {:ok, local_message} end @@ -133,7 +133,7 @@ test "match with wildcard domain" do report_message = build_report_message() local_message = build_local_message() - assert SimplePolicy.filter(report_message) == {:reject, nil} + assert {:reject, _} = SimplePolicy.filter(report_message) assert SimplePolicy.filter(local_message) == {:ok, local_message} end end @@ -241,7 +241,7 @@ test "activity has a matching host" do remote_message = build_remote_message() - assert SimplePolicy.filter(remote_message) == {:reject, nil} + assert {:reject, _} = SimplePolicy.filter(remote_message) end test "activity matches with wildcard domain" do @@ -249,7 +249,7 @@ test "activity matches with wildcard domain" do remote_message = build_remote_message() - assert SimplePolicy.filter(remote_message) == {:reject, nil} + assert {:reject, _} = SimplePolicy.filter(remote_message) end test "actor has a matching host" do @@ -257,7 +257,7 @@ test "actor has a matching host" do remote_user = build_remote_user() - assert SimplePolicy.filter(remote_user) == {:reject, nil} + assert {:reject, _} = SimplePolicy.filter(remote_user) end end @@ -279,7 +279,7 @@ test "is not empty but activity doesn't have a matching host" do remote_message = build_remote_message() assert SimplePolicy.filter(local_message) == {:ok, local_message} - assert SimplePolicy.filter(remote_message) == {:reject, nil} + assert {:reject, _} = SimplePolicy.filter(remote_message) end test "activity has a matching host" do @@ -429,7 +429,7 @@ test "it accepts deletions even from non-whitelisted servers" do test "it rejects the deletion" do deletion_message = build_remote_deletion_message() - assert SimplePolicy.filter(deletion_message) == {:reject, nil} + assert {:reject, _} = SimplePolicy.filter(deletion_message) end end @@ -439,7 +439,7 @@ test "it rejects the deletion" do test "it rejects the deletion" do deletion_message = build_remote_deletion_message() - assert SimplePolicy.filter(deletion_message) == {:reject, nil} + assert {:reject, _} = SimplePolicy.filter(deletion_message) end end diff --git a/test/web/activity_pub/mrf/tag_policy_test.exs b/test/web/activity_pub/mrf/tag_policy_test.exs index e7793641a..6ff71d640 100644 --- a/test/web/activity_pub/mrf/tag_policy_test.exs +++ b/test/web/activity_pub/mrf/tag_policy_test.exs @@ -12,8 +12,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicyTest do describe "mrf_tag:disable-any-subscription" do test "rejects message" do actor = insert(:user, tags: ["mrf_tag:disable-any-subscription"]) - message = %{"object" => actor.ap_id, "type" => "Follow"} - assert {:reject, nil} = TagPolicy.filter(message) + message = %{"object" => actor.ap_id, "type" => "Follow", "actor" => actor.ap_id} + assert {:reject, _} = TagPolicy.filter(message) end end @@ -22,7 +22,7 @@ test "rejects non-local follow requests" do actor = insert(:user, tags: ["mrf_tag:disable-remote-subscription"]) follower = insert(:user, tags: ["mrf_tag:disable-remote-subscription"], local: false) message = %{"object" => actor.ap_id, "type" => "Follow", "actor" => follower.ap_id} - assert {:reject, nil} = TagPolicy.filter(message) + assert {:reject, _} = TagPolicy.filter(message) end test "allows non-local follow requests" do diff --git a/test/web/activity_pub/mrf/user_allowlist_policy_test.exs b/test/web/activity_pub/mrf/user_allowlist_policy_test.exs index ba1b69658..8e1ad5bc8 100644 --- a/test/web/activity_pub/mrf/user_allowlist_policy_test.exs +++ b/test/web/activity_pub/mrf/user_allowlist_policy_test.exs @@ -26,6 +26,6 @@ test "rejected if allow list isn't empty and user not in allow list" do actor = insert(:user) Pleroma.Config.put([:mrf_user_allowlist], %{"localhost" => ["test-ap-id"]}) message = %{"actor" => actor.ap_id} - assert UserAllowListPolicy.filter(message) == {:reject, nil} + assert {:reject, _} = UserAllowListPolicy.filter(message) end end diff --git a/test/web/activity_pub/mrf/vocabulary_policy_test.exs b/test/web/activity_pub/mrf/vocabulary_policy_test.exs index 69f22bb77..2bceb67ee 100644 --- a/test/web/activity_pub/mrf/vocabulary_policy_test.exs +++ b/test/web/activity_pub/mrf/vocabulary_policy_test.exs @@ -46,7 +46,7 @@ test "it does not accept disallowed child objects" do } } - {:reject, nil} = VocabularyPolicy.filter(message) + {:reject, _} = VocabularyPolicy.filter(message) end test "it does not accept disallowed parent types" do @@ -60,7 +60,7 @@ test "it does not accept disallowed parent types" do } } - {:reject, nil} = VocabularyPolicy.filter(message) + {:reject, _} = VocabularyPolicy.filter(message) end end @@ -75,7 +75,7 @@ test "it rejects based on parent activity type" do "object" => "whatever" } - {:reject, nil} = VocabularyPolicy.filter(message) + {:reject, _} = VocabularyPolicy.filter(message) end test "it rejects based on child object type" do @@ -89,7 +89,7 @@ test "it rejects based on child object type" do } } - {:reject, nil} = VocabularyPolicy.filter(message) + {:reject, _} = VocabularyPolicy.filter(message) end test "it passes through objects that aren't disallowed" do diff --git a/test/web/activity_pub/publisher_test.exs b/test/web/activity_pub/publisher_test.exs index c2bc38d52..b9388b966 100644 --- a/test/web/activity_pub/publisher_test.exs +++ b/test/web/activity_pub/publisher_test.exs @@ -123,6 +123,39 @@ test "it returns inbox for messages involving single recipients in total" do end describe "publish_one/1" do + test "publish to url with with different ports" do + inbox80 = "http://42.site/users/nick1/inbox" + inbox42 = "http://42.site:42/users/nick1/inbox" + + mock(fn + %{method: :post, url: "http://42.site:42/users/nick1/inbox"} -> + {:ok, %Tesla.Env{status: 200, body: "port 42"}} + + %{method: :post, url: "http://42.site/users/nick1/inbox"} -> + {:ok, %Tesla.Env{status: 200, body: "port 80"}} + end) + + actor = insert(:user) + + assert {:ok, %{body: "port 42"}} = + Publisher.publish_one(%{ + inbox: inbox42, + json: "{}", + actor: actor, + id: 1, + unreachable_since: true + }) + + assert {:ok, %{body: "port 80"}} = + Publisher.publish_one(%{ + inbox: inbox80, + json: "{}", + actor: actor, + id: 1, + unreachable_since: true + }) + end + test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified", Instances, [:passthrough], @@ -131,7 +164,6 @@ test "it returns inbox for messages involving single recipients in total" do inbox = "http://200.site/users/nick1/inbox" assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) - assert called(Instances.set_reachable(inbox)) end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 248b410c6..7d33feaf2 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -160,7 +160,7 @@ test "it does not crash if the object in inReplyTo can't be fetched" do assert capture_log(fn -> {:ok, _returned_activity} = Transmogrifier.handle_incoming(data) - end) =~ "[error] Couldn't fetch \"https://404.site/whatever\", error: nil" + end) =~ "[warn] Couldn't fetch \"https://404.site/whatever\", error: nil" end test "it works for incoming notices" do @@ -710,7 +710,7 @@ test "it accepts Flag activities" do "id" => activity.data["id"], "content" => "test post", "published" => object.data["published"], - "actor" => AccountView.render("show.json", %{user: user}) + "actor" => AccountView.render("show.json", %{user: user, skip_visibility_check: true}) } message = %{ diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs index 361dc5a41..d50213545 100644 --- a/test/web/activity_pub/utils_test.exs +++ b/test/web/activity_pub/utils_test.exs @@ -482,7 +482,8 @@ test "returns map with Flag object" do "id" => activity_ap_id, "content" => content, "published" => activity.object.data["published"], - "actor" => AccountView.render("show.json", %{user: target_account}) + "actor" => + AccountView.render("show.json", %{user: target_account, skip_visibility_check: true}) } assert %{ diff --git a/test/web/admin_api/controllers/admin_api_controller_test.exs b/test/web/admin_api/controllers/admin_api_controller_test.exs index 85529446f..b5d5bd8c7 100644 --- a/test/web/admin_api/controllers/admin_api_controller_test.exs +++ b/test/web/admin_api/controllers/admin_api_controller_test.exs @@ -9,6 +9,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do import ExUnit.CaptureLog import Mock import Pleroma.Factory + import Swoosh.TestAssertions alias Pleroma.Activity alias Pleroma.Config @@ -1849,6 +1850,9 @@ test "it resend emails for two users", %{conn: conn, admin: admin} do "@#{admin.nickname} re-sent confirmation email for users: @#{first_user.nickname}, @#{ second_user.nickname }" + + ObanHelpers.perform_all() + assert_email_sent(Pleroma.Emails.UserEmail.account_confirmation_email(first_user)) end end diff --git a/test/web/admin_api/views/report_view_test.exs b/test/web/admin_api/views/report_view_test.exs index f00b0afb2..5a02292be 100644 --- a/test/web/admin_api/views/report_view_test.exs +++ b/test/web/admin_api/views/report_view_test.exs @@ -4,11 +4,14 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do use Pleroma.DataCase + import Pleroma.Factory + + alias Pleroma.Web.AdminAPI alias Pleroma.Web.AdminAPI.Report alias Pleroma.Web.AdminAPI.ReportView alias Pleroma.Web.CommonAPI - alias Pleroma.Web.MastodonAPI.AccountView + alias Pleroma.Web.MastodonAPI alias Pleroma.Web.MastodonAPI.StatusView test "renders a report" do @@ -21,13 +24,16 @@ test "renders a report" do content: nil, actor: Map.merge( - AccountView.render("show.json", %{user: user}), - Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user}) + MastodonAPI.AccountView.render("show.json", %{user: user, skip_visibility_check: true}), + AdminAPI.AccountView.render("show.json", %{user: user}) ), account: Map.merge( - AccountView.render("show.json", %{user: other_user}), - Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: other_user}) + MastodonAPI.AccountView.render("show.json", %{ + user: other_user, + skip_visibility_check: true + }), + AdminAPI.AccountView.render("show.json", %{user: other_user}) ), statuses: [], notes: [], @@ -56,13 +62,16 @@ test "includes reported statuses" do content: nil, actor: Map.merge( - AccountView.render("show.json", %{user: user}), - Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user}) + MastodonAPI.AccountView.render("show.json", %{user: user, skip_visibility_check: true}), + AdminAPI.AccountView.render("show.json", %{user: user}) ), account: Map.merge( - AccountView.render("show.json", %{user: other_user}), - Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: other_user}) + MastodonAPI.AccountView.render("show.json", %{ + user: other_user, + skip_visibility_check: true + }), + AdminAPI.AccountView.render("show.json", %{user: other_user}) ), statuses: [StatusView.render("show.json", %{activity: activity})], state: "open", diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index 28d21371a..e6b283aab 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -583,6 +583,15 @@ test "getting followers, pagination", %{user: user, conn: conn} do |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3_id}") |> json_response_and_validate_schema(200) + assert [%{"id" => ^follower2_id}, %{"id" => ^follower1_id}] = + conn + |> get( + "/api/v1/accounts/#{user.id}/followers?id=#{user.id}&limit=20&max_id=#{ + follower3_id + }" + ) + |> json_response_and_validate_schema(200) + res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3_id}") assert [%{"id" => ^follower2_id}] = json_response_and_validate_schema(res_conn, 200) @@ -654,6 +663,16 @@ test "getting following, pagination", %{user: user, conn: conn} do assert id2 == following2.id assert id1 == following1.id + res_conn = + get( + conn, + "/api/v1/accounts/#{user.id}/following?id=#{user.id}&limit=20&max_id=#{following3.id}" + ) + + assert [%{"id" => id2}, %{"id" => id1}] = json_response_and_validate_schema(res_conn, 200) + assert id2 == following2.id + assert id1 == following1.id + res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}") diff --git a/test/web/mastodon_api/controllers/domain_block_controller_test.exs b/test/web/mastodon_api/controllers/domain_block_controller_test.exs index 01a24afcf..664654500 100644 --- a/test/web/mastodon_api/controllers/domain_block_controller_test.exs +++ b/test/web/mastodon_api/controllers/domain_block_controller_test.exs @@ -32,6 +32,38 @@ test "blocking / unblocking a domain" do refute User.blocks?(user, other_user) end + test "blocking a domain via query params" do + %{user: user, conn: conn} = oauth_access(["write:blocks"]) + other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"}) + + ret_conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/domain_blocks?domain=dogwhistle.zone") + + assert %{} == json_response_and_validate_schema(ret_conn, 200) + user = User.get_cached_by_ap_id(user.ap_id) + assert User.blocks?(user, other_user) + end + + test "unblocking a domain via query params" do + %{user: user, conn: conn} = oauth_access(["write:blocks"]) + other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"}) + + User.block_domain(user, "dogwhistle.zone") + user = refresh_record(user) + assert User.blocks?(user, other_user) + + ret_conn = + conn + |> put_req_header("content-type", "application/json") + |> delete("/api/v1/domain_blocks?domain=dogwhistle.zone") + + assert %{} == json_response_and_validate_schema(ret_conn, 200) + user = User.get_cached_by_ap_id(user.ap_id) + refute User.blocks?(user, other_user) + end + test "getting a list of domain blocks" do %{user: user, conn: conn} = oauth_access(["read:blocks"]) diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs index fd2de8d80..d34f300da 100644 --- a/test/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/web/mastodon_api/controllers/status_controller_test.exs @@ -22,6 +22,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do setup do: clear_config([:instance, :federating]) setup do: clear_config([:instance, :allow_relay]) setup do: clear_config([:rich_media, :enabled]) + setup do: clear_config([:mrf, :policies]) + setup do: clear_config([:mrf_keyword, :reject]) describe "posting statuses" do setup do: oauth_access(["write:statuses"]) @@ -157,6 +159,17 @@ test "it fails to create a status if `expires_in` is less or equal than an hour" |> json_response_and_validate_schema(422) end + test "Get MRF reason when posting a status is rejected by one", %{conn: conn} do + Pleroma.Config.put([:mrf_keyword, :reject], ["GNO"]) + Pleroma.Config.put([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy]) + + assert %{"error" => "[KeywordPolicy] Matches with rejected keyword"} = + conn + |> put_req_header("content-type", "application/json") + |> post("api/v1/statuses", %{"status" => "GNO/Linux"}) + |> json_response_and_validate_schema(422) + end + test "posting an undefined status with an attachment", %{user: user, conn: conn} do file = %Plug.Upload{ content_type: "image/jpg", diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs index 17f035add..8f37efa3c 100644 --- a/test/web/mastodon_api/views/account_view_test.exs +++ b/test/web/mastodon_api/views/account_view_test.exs @@ -95,7 +95,7 @@ test "Represent a user account" do } } - assert expected == AccountView.render("show.json", %{user: user}) + assert expected == AccountView.render("show.json", %{user: user, skip_visibility_check: true}) end test "Favicon is nil when :instances_favicons is disabled" do @@ -108,22 +108,20 @@ test "Favicon is nil when :instances_favicons is disabled" do favicon: "https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-16x16.png" } - } = AccountView.render("show.json", %{user: user}) + } = AccountView.render("show.json", %{user: user, skip_visibility_check: true}) Config.put([:instances_favicons, :enabled], false) - assert %{pleroma: %{favicon: nil}} = AccountView.render("show.json", %{user: user}) + assert %{pleroma: %{favicon: nil}} = + AccountView.render("show.json", %{user: user, skip_visibility_check: true}) end test "Represent the user account for the account owner" do user = insert(:user) notification_settings = %{ - followers: true, - follows: true, - non_followers: true, - non_follows: true, - privacy_option: false + block_from_strangers: false, + hide_notification_contents: false } privacy = user.default_scope @@ -192,7 +190,7 @@ test "Represent a Service(bot) account" do } } - assert expected == AccountView.render("show.json", %{user: user}) + assert expected == AccountView.render("show.json", %{user: user, skip_visibility_check: true}) end test "Represent a Funkwhale channel" do @@ -201,7 +199,9 @@ test "Represent a Funkwhale channel" do "https://channels.tests.funkwhale.audio/federation/actors/compositions" ) - assert represented = AccountView.render("show.json", %{user: user}) + assert represented = + AccountView.render("show.json", %{user: user, skip_visibility_check: true}) + assert represented.acct == "compositions@channels.tests.funkwhale.audio" assert represented.url == "https://channels.tests.funkwhale.audio/channels/compositions" end @@ -226,6 +226,23 @@ test "Represent a smaller mention" do assert expected == AccountView.render("mention.json", %{user: user}) end + test "demands :for or :skip_visibility_check option for account rendering" do + clear_config([:restrict_unauthenticated, :profiles, :local], false) + + user = insert(:user) + user_id = user.id + + assert %{id: ^user_id} = AccountView.render("show.json", %{user: user, for: nil}) + assert %{id: ^user_id} = AccountView.render("show.json", %{user: user, for: user}) + + assert %{id: ^user_id} = + AccountView.render("show.json", %{user: user, skip_visibility_check: true}) + + assert_raise RuntimeError, ~r/:skip_visibility_check or :for option is required/, fn -> + AccountView.render("show.json", %{user: user}) + end + end + describe "relationship" do defp test_relationship_rendering(user, other_user, expected_result) do opts = %{user: user, target: other_user, relationships: nil} @@ -339,7 +356,7 @@ test "returns the settings store if the requesting user is the represented user assert result.pleroma.settings_store == %{:fe => "test"} - result = AccountView.render("show.json", %{user: user, with_pleroma_settings: true}) + result = AccountView.render("show.json", %{user: user, for: nil, with_pleroma_settings: true}) assert result.pleroma[:settings_store] == nil result = AccountView.render("show.json", %{user: user, for: user}) @@ -348,13 +365,13 @@ test "returns the settings store if the requesting user is the represented user test "doesn't sanitize display names" do user = insert(:user, name: " username ") - result = AccountView.render("show.json", %{user: user}) + result = AccountView.render("show.json", %{user: user, skip_visibility_check: true}) assert result.display_name == " username " end test "never display nil user follow counts" do user = insert(:user, following_count: 0, follower_count: 0) - result = AccountView.render("show.json", %{user: user}) + result = AccountView.render("show.json", %{user: user, skip_visibility_check: true}) assert result.following_count == 0 assert result.followers_count == 0 @@ -378,7 +395,7 @@ test "shows when follows/followers stats are hidden and sets follow/follower cou followers_count: 0, following_count: 0, pleroma: %{hide_follows_count: true, hide_followers_count: true} - } = AccountView.render("show.json", %{user: user}) + } = AccountView.render("show.json", %{user: user, skip_visibility_check: true}) end test "shows when follows/followers are hidden" do @@ -391,7 +408,7 @@ test "shows when follows/followers are hidden" do followers_count: 1, following_count: 1, pleroma: %{hide_follows: true, hide_followers: true} - } = AccountView.render("show.json", %{user: user}) + } = AccountView.render("show.json", %{user: user, skip_visibility_check: true}) end test "shows actual follower/following count to the account owner" do @@ -534,7 +551,7 @@ test "uses mediaproxy urls when it's enabled" do emoji: %{"joker_smile" => "https://evil.website/society.png"} ) - AccountView.render("show.json", %{user: user}) + AccountView.render("show.json", %{user: user, skip_visibility_check: true}) |> Enum.all?(fn {key, url} when key in [:avatar, :avatar_static, :header, :header_static] -> String.starts_with?(url, Pleroma.Web.base_url()) diff --git a/test/web/mastodon_api/views/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index fa26b3129..8703d5ba7 100644 --- a/test/web/mastodon_api/views/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -56,6 +56,23 @@ test "has an emoji reaction list" do ] end + test "works correctly with badly formatted emojis" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{status: "yo"}) + + activity + |> Object.normalize(false) + |> Object.update_data(%{"reactions" => %{"☕" => [user.ap_id], "x" => 1}}) + + activity = Activity.get_by_id(activity.id) + + status = StatusView.render("show.json", activity: activity, for: user) + + assert status[:pleroma][:emoji_reactions] == [ + %{name: "☕", count: 1, me: true} + ] + end + test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do user = insert(:user) @@ -177,7 +194,7 @@ test "a note activity" do id: to_string(note.id), uri: object_data["id"], url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note), - account: AccountView.render("show.json", %{user: user}), + account: AccountView.render("show.json", %{user: user, skip_visibility_check: true}), in_reply_to_id: nil, in_reply_to_account_id: nil, card: nil, diff --git a/test/web/pleroma_api/controllers/chat_controller_test.exs b/test/web/pleroma_api/controllers/chat_controller_test.exs index 82e16741d..d71e80d03 100644 --- a/test/web/pleroma_api/controllers/chat_controller_test.exs +++ b/test/web/pleroma_api/controllers/chat_controller_test.exs @@ -332,5 +332,27 @@ test "it return a list of chats the current user is participating in, in descend chat_1.id |> to_string() ] end + + test "it is not affected by :restrict_unauthenticated setting (issue #1973)", %{ + conn: conn, + user: user + } do + clear_config([:restrict_unauthenticated, :profiles, :local], true) + clear_config([:restrict_unauthenticated, :profiles, :remote], true) + + user2 = insert(:user) + user3 = insert(:user, local: false) + + {:ok, _chat_12} = Chat.get_or_create(user.id, user2.ap_id) + {:ok, _chat_13} = Chat.get_or_create(user.id, user3.ap_id) + + result = + conn + |> get("/api/v1/pleroma/chats") + |> json_response_and_validate_schema(200) + + account_ids = Enum.map(result, &get_in(&1, ["account", "id"])) + assert Enum.sort(account_ids) == Enum.sort([user2.id, user3.id]) + end end end diff --git a/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs b/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs index df58a5eb6..e113bb15f 100644 --- a/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs +++ b/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs @@ -14,6 +14,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do ) setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], false) + setup do: clear_config([:instance, :public], true) + setup do admin = insert(:user, is_admin: true) token = insert(:oauth_admin_token, user: admin) @@ -27,6 +29,11 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do {:ok, %{admin_conn: admin_conn}} end + test "GET /api/pleroma/emoji/packs when :public: false", %{conn: conn} do + Config.put([:instance, :public], false) + conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200) + end + test "GET /api/pleroma/emoji/packs", %{conn: conn} do resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200) diff --git a/test/web/pleroma_api/views/chat_view_test.exs b/test/web/pleroma_api/views/chat_view_test.exs index 14eecb1bd..02484b705 100644 --- a/test/web/pleroma_api/views/chat_view_test.exs +++ b/test/web/pleroma_api/views/chat_view_test.exs @@ -26,7 +26,8 @@ test "it represents a chat" do assert represented_chat == %{ id: "#{chat.id}", - account: AccountView.render("show.json", user: recipient), + account: + AccountView.render("show.json", user: recipient, skip_visibility_check: true), unread: 0, last_message: nil, updated_at: Utils.to_masto_date(chat.updated_at) diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs index b48952b29..aeb5c1fbd 100644 --- a/test/web/push/impl_test.exs +++ b/test/web/push/impl_test.exs @@ -238,9 +238,11 @@ test "builds content for chat messages with no content" do } end - test "hides details for notifications when privacy option enabled" do + test "hides contents of notifications when option enabled" do user = insert(:user, nickname: "Bob") - user2 = insert(:user, nickname: "Rob", notification_settings: %{privacy_option: true}) + + user2 = + insert(:user, nickname: "Rob", notification_settings: %{hide_notification_contents: true}) {:ok, activity} = CommonAPI.post(user, %{ @@ -284,9 +286,11 @@ test "hides details for notifications when privacy option enabled" do } end - test "returns regular content for notifications with privacy option disabled" do + test "returns regular content when hiding contents option disabled" do user = insert(:user, nickname: "Bob") - user2 = insert(:user, nickname: "Rob", notification_settings: %{privacy_option: false}) + + user2 = + insert(:user, nickname: "Rob", notification_settings: %{hide_notification_contents: false}) {:ok, activity} = CommonAPI.post(user, %{ diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index df9d59f6a..20a45cb6f 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -9,7 +9,6 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.UserInviteToken - alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.TwitterAPI.TwitterAPI setup_all do @@ -28,13 +27,10 @@ test "it registers a new user and returns the user." do {:ok, user} = TwitterAPI.register_user(data) - fetched_user = User.get_cached_by_nickname("lain") - - assert AccountView.render("show.json", %{user: user}) == - AccountView.render("show.json", %{user: fetched_user}) + assert user == User.get_cached_by_nickname("lain") end - test "it registers a new user with empty string in bio and returns the user." do + test "it registers a new user with empty string in bio and returns the user" do data = %{ :username => "lain", :email => "lain@wired.jp", @@ -46,10 +42,7 @@ test "it registers a new user with empty string in bio and returns the user." do {:ok, user} = TwitterAPI.register_user(data) - fetched_user = User.get_cached_by_nickname("lain") - - assert AccountView.render("show.json", %{user: user}) == - AccountView.render("show.json", %{user: fetched_user}) + assert user == User.get_cached_by_nickname("lain") end test "it sends confirmation email if :account_activation_required is specified in instance config" do @@ -171,13 +164,10 @@ test "returns user on success" do {:ok, user} = TwitterAPI.register_user(data) - fetched_user = User.get_cached_by_nickname("vinny") + assert user == User.get_cached_by_nickname("vinny") + invite = Repo.get_by(UserInviteToken, token: invite.token) - assert invite.used == true - - assert AccountView.render("show.json", %{user: user}) == - AccountView.render("show.json", %{user: fetched_user}) end test "returns error on invalid token" do @@ -234,10 +224,8 @@ test "returns error on expired token" do check_fn = fn invite -> data = Map.put(data, :token, invite.token) {:ok, user} = TwitterAPI.register_user(data) - fetched_user = User.get_cached_by_nickname("vinny") - assert AccountView.render("show.json", %{user: user}) == - AccountView.render("show.json", %{user: fetched_user}) + assert user == User.get_cached_by_nickname("vinny") end {:ok, data: data, check_fn: check_fn} @@ -297,14 +285,11 @@ test "returns user on success, after him registration fails" do } {:ok, user} = TwitterAPI.register_user(data) - fetched_user = User.get_cached_by_nickname("vinny") + assert user == User.get_cached_by_nickname("vinny") + invite = Repo.get_by(UserInviteToken, token: invite.token) - assert invite.used == true - assert AccountView.render("show.json", %{user: user}) == - AccountView.render("show.json", %{user: fetched_user}) - data = %{ :username => "GrimReaper", :email => "death@reapers.afterlife", @@ -339,13 +324,10 @@ test "returns user on success" do } {:ok, user} = TwitterAPI.register_user(data) - fetched_user = User.get_cached_by_nickname("vinny") + assert user == User.get_cached_by_nickname("vinny") + invite = Repo.get_by(UserInviteToken, token: invite.token) - refute invite.used - - assert AccountView.render("show.json", %{user: user}) == - AccountView.render("show.json", %{user: fetched_user}) end test "error after max uses" do @@ -364,13 +346,11 @@ test "error after max uses" do } {:ok, user} = TwitterAPI.register_user(data) - fetched_user = User.get_cached_by_nickname("vinny") + assert user == User.get_cached_by_nickname("vinny") + invite = Repo.get_by(UserInviteToken, token: invite.token) assert invite.used == true - assert AccountView.render("show.json", %{user: user}) == - AccountView.render("show.json", %{user: fetched_user}) - data = %{ :username => "GrimReaper", :email => "death@reapers.afterlife", diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs index 76e9369f7..109c1e637 100644 --- a/test/web/twitter_api/util_controller_test.exs +++ b/test/web/twitter_api/util_controller_test.exs @@ -191,7 +191,7 @@ test "it imports blocks with different nickname variations", %{conn: conn} do test "it updates notification settings", %{user: user, conn: conn} do conn |> put("/api/pleroma/notification_settings", %{ - "followers" => false, + "block_from_strangers" => true, "bar" => 1 }) |> json_response(:ok) @@ -199,27 +199,21 @@ test "it updates notification settings", %{user: user, conn: conn} do user = refresh_record(user) assert %Pleroma.User.NotificationSetting{ - followers: false, - follows: true, - non_follows: true, - non_followers: true, - privacy_option: false + block_from_strangers: true, + hide_notification_contents: false } == user.notification_settings end - test "it updates notification privacy option", %{user: user, conn: conn} do + test "it updates notification settings to enable hiding contents", %{user: user, conn: conn} do conn - |> put("/api/pleroma/notification_settings", %{"privacy_option" => "1"}) + |> put("/api/pleroma/notification_settings", %{"hide_notification_contents" => "1"}) |> json_response(:ok) user = refresh_record(user) assert %Pleroma.User.NotificationSetting{ - followers: true, - follows: true, - non_follows: true, - non_followers: true, - privacy_option: true + block_from_strangers: false, + hide_notification_contents: true } == user.notification_settings end end