diff --git a/.gitignore b/.gitignore index c108ad262..b62891f26 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ secret /*.ez /test/uploads /.elixir_ls +/test/fixtures/DSCN0010_tmp.jpg /test/fixtures/test_tmp.txt /test/fixtures/image_tmp.jpg /test/tmp/ @@ -27,6 +28,8 @@ erl_crash.dump # variables. /config/*.secret.exs /config/generated_config.exs +/config/*.env + # Database setup file, some may forget to delete it /config/setup_db.psql diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b4bd59b43..c9ab84892 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -58,26 +58,25 @@ unit-testing: alias: postgres command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] script: + - apt-get update && apt-get install -y libimage-exiftool-perl - mix deps.get - mix ecto.create - mix ecto.migrate - mix coveralls --preload-modules -# Removed to fix CI issue. In this early state it wasn't adding much value anyway. -# TODO Fix and reinstate federated testing -# federated-testing: -# stage: test -# cache: *testing_cache_policy -# services: -# - name: minibikini/postgres-with-rum:12 -# alias: postgres -# command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] -# script: -# - mix deps.get -# - mix ecto.create -# - mix ecto.migrate -# - epmd -daemon -# - mix test --trace --only federated +federated-testing: + stage: test + cache: *testing_cache_policy + services: + - name: minibikini/postgres-with-rum:12 + alias: postgres + command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] + script: + - mix deps.get + - mix ecto.create + - mix ecto.migrate + - epmd -daemon + - mix test --trace --only federated unit-testing-rum: stage: test @@ -91,6 +90,7 @@ unit-testing-rum: <<: *global_variables RUM_ENABLED: "true" script: + - apt-get update && apt-get install -y libimage-exiftool-perl - mix deps.get - mix ecto.create - mix ecto.migrate 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 2e914e776..129c269aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,14 @@ 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 - OGP rich media parser merged with TwitterCard - Configuration: `:instance, rewrite_policy` moved to `:mrf, policies`, `:instance, :mrf_transparency` moved to `:mrf, :transparency`, `:instance, :mrf_transparency_exclusions` moved to `:mrf, :transparency_exclusions`. Old config namespace is deprecated. +- Configuration: `:media_proxy, whitelist` format changed to host with scheme (e.g. `http://example.com` instead of `example.com`). Domain format is deprecated. +- **Breaking:** Configuration: `:instance, welcome_user_nickname` moved to `:welcome, :direct_message, :sender_nickname`, `:instance, :welcome_message` moved to `:welcome, :direct_message, :message`. Old config namespace is deprecated.
API Changes @@ -24,6 +27,13 @@ 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` +- Pleroma API: `/api/pleroma/captcha` responses now include `seconds_valid` with an integer value.
@@ -39,6 +49,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added +- Chats: Added `accepts_chat_messages` field to user, exposed in APIs and federation. - Chats: Added support for federated chats. For details, see the docs. - ActivityPub: Added support for existing AP ids for instances migrated from Mastodon. - Instance: Add `background_image` to configuration and `/api/v1/instance` @@ -56,10 +67,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - MRF (`EmojiStealPolicy`): New MRF Policy which allows to automatically download emojis from remote instances - Support pagination in emoji packs API (for packs and for files in pack) - Support for viewing instances favicons next to posts and accounts +- Added Pleroma.Upload.Filter.Exiftool as an alternate EXIF stripping mechanism targeting GPS/location metadata. +- "By approval" registrations mode. +- Configuration: Added `:welcome` settings for the welcome message to newly registered users. +- Ability to hide favourites and emoji reactions in the API with `[:instance, :show_reactions]` config.
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. @@ -81,7 +97,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - `blob:` urls not being allowed by connect-src CSP - Mastodon API: fix `GET /api/v1/notifications` not returning the full result set - Rich Media Previews for Twitter links +- 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)] @@ -111,6 +130,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`.
@@ -178,6 +198,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/Dockerfile b/Dockerfile index 29931a5e3..0f4fcd0bb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,7 +33,7 @@ ARG DATA=/var/lib/pleroma RUN echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\ apk update &&\ - apk add imagemagick ncurses postgresql-client &&\ + apk add exiftool imagemagick ncurses postgresql-client &&\ adduser --system --shell /bin/false --home ${HOME} pleroma &&\ mkdir -p ${DATA}/uploads &&\ mkdir -p ${DATA}/static &&\ diff --git a/config/config.exs b/config/config.exs index acbb7639a..8cf7f169e 100644 --- a/config/config.exs +++ b/config/config.exs @@ -205,6 +205,7 @@ registrations_open: true, invites_enabled: false, account_activation_required: false, + account_approval_required: false, federating: true, federation_incoming_replies_max_depth: 100, federation_reachability_timeout_days: 7, @@ -228,8 +229,6 @@ autofollowed_nicknames: [], max_pinned_statuses: 1, attachment_links: false, - welcome_user_nickname: nil, - welcome_message: nil, max_report_comment_size: 1000, safe_dm_mentions: false, healthcheck: false, @@ -242,6 +241,7 @@ max_remote_account_fields: 20, account_field_name_length: 512, account_field_value_length: 2048, + registration_reason_length: 500, external_user_synchronization: true, extended_nickname_format: true, cleanup_attachments: false, @@ -255,6 +255,21 @@ number: 5, length: 16 ] + ], + show_reactions: true + +config :pleroma, :welcome, + direct_message: [ + enabled: false, + sender_nickname: nil, + message: nil + ], + email: [ + enabled: false, + sender: nil, + subject: "Welcome to <%= instance_name %>", + html: "Welcome to <%= instance_name %>", + text: "Welcome to <%= instance_name %>" ] config :pleroma, :feed, @@ -501,8 +516,7 @@ config :pleroma, Oban, repo: Pleroma.Repo, - verbose: false, - prune: {:maxlen, 1500}, + log: false, queues: [ activity_expiration: 10, federator_incoming: 50, @@ -516,6 +530,7 @@ attachments_cleanup: 5, new_users_digest: 1 ], + plugins: [Oban.Plugins.Pruner], crontab: [ {"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker}, {"0 * * * *", Pleroma.Workers.Cron.StatsWorker}, @@ -530,16 +545,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", @@ -638,6 +651,16 @@ config :pleroma, :static_fe, enabled: false +# Example of frontend configuration +# This example will make us serve the primary frontend from the +# frontends directory within your `:pleroma, :instance, static_dir`. +# e.g., instance/static/frontends/pleroma/develop/ +# +# With no frontend configuration, the bundled files from the `static` directory will +# be used. +# +# config :pleroma, :frontends, primary: %{"name" => "pleroma", "ref" => "develop"} + config :pleroma, :web_cache_ttl, activity_pub: nil, activity_pub_question: 30_000 @@ -651,32 +674,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 03b84bfc8..11fbe0d78 100644 --- a/config/description.exs +++ b/config/description.exs @@ -23,18 +23,14 @@ key: :uploader, type: :module, description: "Module which will be used for uploads", - suggestions: [Pleroma.Uploaders.Local, Pleroma.Uploaders.S3] + suggestions: {:list_behaviour_implementations, Pleroma.Uploaders.Uploader} }, %{ key: :filters, type: {:list, :module}, description: "List of filter modules for uploads. Module names are shortened (removed leading `Pleroma.Upload.Filter.` part), but on adding custom module you need to use full name.", - suggestions: - Generator.list_modules_in_dir( - "lib/pleroma/upload/filter", - "Elixir.Pleroma.Upload.Filter." - ) + suggestions: {:list_behaviour_implementations, Pleroma.Upload.Filter} }, %{ key: :link_name, @@ -665,6 +661,11 @@ type: :boolean, description: "Require users to confirm their emails before signing in" }, + %{ + key: :account_approval_required, + type: :boolean, + description: "Require users to be manually approved by an admin before signing in" + }, %{ key: :federating, type: :boolean, @@ -782,23 +783,6 @@ type: :boolean, description: "Enable to automatically add attachment link text to statuses" }, - %{ - key: :welcome_message, - type: :string, - description: - "A message that will be sent to a newly registered users as a direct message", - suggestions: [ - "Hi, @username! Welcome on board!" - ] - }, - %{ - key: :welcome_user_nickname, - type: :string, - description: "The nickname of the local user that sends the welcome message", - suggestions: [ - "lain" - ] - }, %{ key: :max_report_comment_size, type: :integer, @@ -895,6 +879,14 @@ 2048 ] }, + %{ + key: :registration_reason_length, + type: :integer, + description: "Maximum registration reason length. Default: 500.", + suggestions: [ + 500 + ] + }, %{ key: :external_user_synchronization, type: :boolean, @@ -963,6 +955,89 @@ description: "The instance thumbnail can be any image that represents your instance and is used by some apps or services when they display information about your instance.", suggestions: ["/instance/thumbnail.jpeg"] + }, + %{ + key: :show_reactions, + type: :boolean, + description: "Let favourites and emoji reactions be viewed through the API." + } + ] + }, + %{ + group: :welcome, + type: :group, + description: "Welcome messages settings", + children: [ + %{ + group: :direct_message, + type: :group, + descpiption: "Direct message settings", + children: [ + %{ + key: :enabled, + type: :boolean, + description: "Enables sends direct message for new user after registration" + }, + %{ + key: :message, + type: :string, + description: + "A message that will be sent to a newly registered users as a direct message", + suggestions: [ + "Hi, @username! Welcome on board!" + ] + }, + %{ + key: :sender_nickname, + type: :string, + description: "The nickname of the local user that sends the welcome message", + suggestions: [ + "lain" + ] + } + ] + }, + %{ + group: :email, + type: :group, + descpiption: "Email message settings", + children: [ + %{ + key: :enabled, + type: :boolean, + description: "Enables sends direct message for new user after registration" + }, + %{ + key: :sender, + type: [:string, :tuple], + description: + "The email address or tuple with `{nickname, email}` that will use as sender to the welcome email.", + suggestions: [ + {"Pleroma App", "welcome@pleroma.app"} + ] + }, + %{ + key: :subject, + type: :string, + description: + "The subject of welcome email. Can be use EEX template with `user` and `instance_name` variables.", + suggestions: ["Welcome to <%= instance_name%>"] + }, + %{ + key: :html, + type: :string, + description: + "The html content of welcome email. Can be use EEX template with `user` and `instance_name` variables.", + suggestions: ["

Hello <%= user.name%>. Welcome to <%= instance_name%>

"] + }, + %{ + key: :text, + type: :string, + description: + "The text content of welcome email. Can be use EEX template with `user` and `instance_name` variables.", + suggestions: ["Hello <%= user.name%>. \n Welcome to <%= instance_name%>\n"] + } + ] } ] }, @@ -1071,6 +1146,7 @@ }, %{ key: :webhook_url, + label: "Webhook URL", type: :string, description: "Configure the Slack incoming webhook", suggestions: ["https://hooks.slack.com/services/YOUR-KEY-HERE"] @@ -1404,11 +1480,7 @@ type: [:module, {:list, :module}], description: "A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.", - suggestions: - Generator.list_modules_in_dir( - "lib/pleroma/web/activity_pub/mrf", - "Elixir.Pleroma.Web.ActivityPub.MRF." - ) + suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF} }, %{ key: :transparency, @@ -1433,6 +1505,7 @@ group: :pleroma, key: :mrf_simple, tab: :mrf, + related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy", label: "MRF Simple", type: :group, description: "Simple ingress policies", @@ -1499,6 +1572,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", @@ -1515,6 +1589,7 @@ group: :pleroma, key: :mrf_subchain, tab: :mrf, + related_policy: "Pleroma.Web.ActivityPub.MRF.SubchainPolicy", label: "MRF Subchain", type: :group, description: @@ -1523,7 +1598,7 @@ children: [ %{ key: :match_actor, - type: :map, + type: {:map, {:list, :string}}, description: "Matches a series of regular expressions against the actor field", suggestions: [ %{ @@ -1537,6 +1612,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, @@ -1558,6 +1634,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", @@ -1583,27 +1660,28 @@ 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", children: [ %{ key: :reject, - type: [:string, :regex], + type: {:list, :string}, description: "A list of patterns which result in message being rejected. Each pattern can be a string or a regular expression.", suggestions: ["foo", ~r/foo/iu] }, %{ key: :federated_timeline_removal, - type: [:string, :regex], + type: {:list, :string}, description: "A list of patterns which result in message being removed from federated timelines (a.k.a unlisted). Each pattern can be a string or a regular expression.", suggestions: ["foo", ~r/foo/iu] }, %{ key: :replace, - type: [{:tuple, :string, :string}, {:tuple, :regex, :string}], + type: {:list, :tuple}, description: "A list of tuples containing {pattern, replacement}. Each pattern can be a string or a regular expression.", suggestions: [{"foo", "bar"}, {~r/foo/iu, "bar"}] @@ -1614,6 +1692,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", @@ -1630,6 +1709,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", @@ -1653,6 +1733,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." <> @@ -1775,8 +1857,8 @@ %{ key: :whitelist, type: {:list, :string}, - description: "List of domains to bypass the mediaproxy", - suggestions: ["example.com"] + description: "List of hosts with scheme to bypass the mediaproxy", + suggestions: ["http://example.com"] } ] }, @@ -1793,15 +1875,20 @@ }, %{ key: :headers, - type: {:list, :tuple}, - description: "HTTP headers of request.", + type: {:keyword, :string}, + description: "HTTP headers of request", suggestions: [{"x-refresh", 1}] }, %{ key: :options, type: :keyword, - description: "Request options.", - suggestions: [params: %{ts: "xxx"}] + description: "Request options", + children: [ + %{ + key: :params, + type: {:map, :string} + } + ] } ] }, @@ -2010,13 +2097,15 @@ label: "Pleroma Admin Token", type: :group, description: - "Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the `admin_token` parameter", + "Allows setting a token that can be used to authenticate requests with admin privileges without a normal user account token. Append the `admin_token` parameter to requests to utilize it. (Please reconsider using HTTP Basic Auth or OAuth-based authentication if possible)", children: [ %{ key: :admin_token, type: :string, description: "Admin token", - suggestions: ["We recommend a secure random string or UUID"] + suggestions: [ + "Please use a high entropy string or UUID" + ] } ] }, @@ -2034,18 +2123,11 @@ """, children: [ %{ - key: :verbose, + key: :log, type: {:dropdown, :atom}, description: "Logs verbose mode", suggestions: [false, :error, :warn, :info, :debug] }, - %{ - key: :prune, - type: [:atom, :tuple], - description: - "Non-retryable jobs [pruning settings](https://github.com/sorentwo/oban#pruning)", - suggestions: [:disabled, {:maxlen, 1500}, {:maxage, 60 * 60}] - }, %{ key: :queues, type: {:keyword, :integer}, @@ -2223,45 +2305,53 @@ ] }, %{ - 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, - type: [:string, false], + type: [:string, :boolean], description: "Specify the class to be added to the generated link. Disable to clear.", suggestions: ["auto-linker", false] }, %{ key: :rel, - type: [:string, false], + type: [:string, :boolean], description: "Override the rel attribute. Disable to clear.", suggestions: ["ugc", "noopener noreferrer", false] }, %{ 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], + type: [:integer, :boolean], 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] } ] }, @@ -2524,7 +2614,7 @@ %{ key: :styling, type: :map, - description: "a map with color settings for email templates.", + description: "A map with color settings for email templates.", suggestions: [ %{ link_color: "#d8a070", @@ -2579,8 +2669,7 @@ %{ key: :enabled, type: :boolean, - description: "Enables new users admin digest email when `true`", - suggestions: [false] + description: "Enables new users admin digest email when `true`" } ] }, @@ -2630,7 +2719,7 @@ }, %{ key: :groups, - type: {:keyword, :string, {:list, :string}}, + type: {:keyword, {:list, :string}}, description: "Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the group name" <> " and the value is the location or array of locations. * can be used as a wildcard.", @@ -2910,8 +2999,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, @@ -3106,8 +3196,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.", @@ -3169,36 +3260,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] } ] }, @@ -3207,108 +3299,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, @@ -3444,8 +3457,7 @@ key: :strict, type: :boolean, description: - "Enables strict input validation (useful in development, not recommended in production)", - suggestions: [false] + "Enables strict input validation (useful in development, not recommended in production)" } ] }, @@ -3461,5 +3473,56 @@ description: "Allow/disallow displaying and getting instances favicons" } ] + }, + %{ + group: :ex_aws, + key: :s3, + type: :group, + descriptions: "S3 service related settings", + children: [ + %{ + key: :access_key_id, + type: :string, + description: "S3 access key ID", + suggestions: ["AKIAQ8UKHTGIYN7DMWWJ"] + }, + %{ + key: :secret_access_key, + type: :string, + description: "Secret access key", + suggestions: ["JFGt+fgH1UQ7vLUQjpW+WvjTdV/UNzVxcwn7DkaeFKtBS5LvoXvIiME4NQBsT6ZZ"] + }, + %{ + key: :host, + type: :string, + description: "S3 host", + suggestions: ["s3.eu-central-1.amazonaws.com"] + } + ] + }, + %{ + group: :pleroma, + key: :frontends, + type: :group, + description: "Installed frontends management", + children: [ + %{ + key: :primary, + type: :map, + description: "Primary frontend, the one that is served for all pages by default", + children: [ + %{ + key: "name", + type: :string, + description: "Name of the installed primary frontend" + }, + %{ + key: "ref", + type: :string, + description: "reference of the installed primary frontend to be used" + } + ] + } + ] } ] diff --git a/config/test.exs b/config/test.exs index e6596e0bc..db0655e73 100644 --- a/config/test.exs +++ b/config/test.exs @@ -79,8 +79,8 @@ config :pleroma, Oban, queues: false, - prune: :disabled, - crontab: false + crontab: false, + plugins: false config :pleroma, Pleroma.ScheduledActivity, daily_user_limit: 2, @@ -113,6 +113,13 @@ config :pleroma, :instances_favicons, enabled: true +config :pleroma, Pleroma.Uploaders.S3, + bucket: nil, + streaming_enabled: true, + public_endpoint: nil + +config :tzdata, :autoupdate, :disabled + if File.exists?("./config/test.secret.exs") do import_config "test.secret.exs" else diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md index baf895d90..4b143e4ee 100644 --- a/docs/API/admin_api.md +++ b/docs/API/admin_api.md @@ -19,6 +19,7 @@ Configuration options: - `local`: only local users - `external`: only external users - `active`: only active users + - `need_approval`: only unapproved users - `deactivated`: only deactivated users - `is_admin`: users with admin role - `is_moderator`: users with moderator role @@ -46,7 +47,10 @@ Configuration options: "local": bool, "tags": array, "avatar": string, - "display_name": string + "display_name": string, + "confirmation_pending": bool, + "approval_pending": bool, + "registration_reason": string, }, ... ] @@ -242,6 +246,24 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret } ``` +## `PATCH /api/pleroma/admin/users/approve` + +### Approve user + +- Params: + - `nicknames`: nicknames array +- Response: + +```json +{ + users: [ + { + // user object + } + ] +} +``` + ## `GET /api/pleroma/admin/users/:nickname_or_id` ### Retrive the details of a user diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md index 03c7f4608..38865dc68 100644 --- a/docs/API/differences_in_mastoapi_responses.md +++ b/docs/API/differences_in_mastoapi_responses.md @@ -64,13 +64,14 @@ Has these additional fields under the `pleroma` object: - `hide_follows`: boolean, true when the user has follow hiding enabled - `hide_followers_count`: boolean, true when the user has follower stat hiding enabled - `hide_follows_count`: boolean, true when the user has follow stat hiding enabled -- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials` -- `chat_token`: The token needed for Pleroma chat. Only returned in `verify_credentials` +- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `/api/v1/accounts/verify_credentials` and `/api/v1/accounts/update_credentials` +- `chat_token`: The token needed for Pleroma chat. Only returned in `/api/v1/accounts/verify_credentials` - `deactivated`: boolean, true when the user is deactivated - `allow_following_move`: boolean, true when the user allows automatically follow moved following accounts - `unread_conversation_count`: The count of unread conversations. Only returned to the account owner. - `unread_notifications_count`: The count of unread notifications. Only returned to the account owner. - `notification_settings`: object, can be absent. See `/api/pleroma/notification_settings` for the parameters/keys returned. +- `accepts_chat_messages`: boolean, but can be null if we don't have that information about a user - `favicon`: nullable URL string, Favicon image of the user's instance ### Source @@ -168,7 +169,7 @@ Returns: array of Status. The maximum number of statuses is limited to 100 per request. -## PATCH `/api/v1/update_credentials` +## PATCH `/api/v1/accounts/update_credentials` Additional parameters can be added to the JSON body/Form data: @@ -186,6 +187,7 @@ Additional parameters can be added to the JSON body/Form data: - `pleroma_background_image` - sets the background image of the user. Can be set to "" (an empty string) to reset. - `discoverable` - if true, discovery of this account in search results and other services is allowed. - `actor_type` - the type of this account. +- `accepts_chat_messages` - if false, this account will reject all chat messages. All images (avatar, banner and background) can be reset to the default by sending an empty string ("") instead of a file. @@ -195,7 +197,7 @@ Pleroma has mechanism that allows frontends to save blobs of json for each user The parameter should have a form of `{frontend_name: {...}}`, with `frontend_name` identifying your type of client, e.g. `pleroma_fe`. It will overwrite everything under this property, but will not overwrite other frontend's settings. -This information is returned in the `verify_credentials` endpoint. +This information is returned in the `/api/v1/accounts/verify_credentials` endpoint. ## Authentication @@ -234,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..4e97d26c0 100644 --- a/docs/API/pleroma_api.md +++ b/docs/API/pleroma_api.md @@ -50,7 +50,7 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi * Authentication: not required * Params: none * Response: Provider specific JSON, the only guaranteed parameter is `type` -* Example response: `{"type": "kocaptcha", "token": "whatever", "url": "https://captcha.kotobank.ch/endpoint"}` +* Example response: `{"type": "kocaptcha", "token": "whatever", "url": "https://captcha.kotobank.ch/endpoint", "seconds_valid": 300}` ## `/api/pleroma/delete_account` ### Delete an account @@ -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/administration/CLI_tasks/release_environments.md b/docs/administration/CLI_tasks/release_environments.md new file mode 100644 index 000000000..36ab43864 --- /dev/null +++ b/docs/administration/CLI_tasks/release_environments.md @@ -0,0 +1,9 @@ +# Generate release environment file + +```sh tab="OTP" + ./bin/pleroma_ctl release_env gen +``` + +```sh tab="From Source" +mix pleroma.release_env gen +``` diff --git a/docs/administration/CLI_tasks/user.md b/docs/administration/CLI_tasks/user.md index 1e6f4a8b4..3b4c421a7 100644 --- a/docs/administration/CLI_tasks/user.md +++ b/docs/administration/CLI_tasks/user.md @@ -57,11 +57,11 @@ mix pleroma.user invites ## Revoke invite ```sh tab="OTP" - ./bin/pleroma_ctl user revoke_invite + ./bin/pleroma_ctl user revoke_invite ``` ```sh tab="From Source" -mix pleroma.user revoke_invite +mix pleroma.user revoke_invite ``` diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index d775534b6..9c768abef 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -33,6 +33,7 @@ To add configuration to your config file, you can copy it from the base config. * `registrations_open`: Enable registrations for anyone, invitations can be enabled when false. * `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`). * `account_activation_required`: Require users to confirm their emails before signing in. +* `account_approval_required`: Require users to be manually approved by an admin before signing in. * `federating`: Enable federation with other instances. * `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes. * `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it. @@ -46,8 +47,6 @@ To add configuration to your config file, you can copy it from the base config. * `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature. * `autofollowed_nicknames`: Set to nicknames of (local) users that every new user should automatically follow. * `attachment_links`: Set to true to enable automatically adding attachment link text to statuses. -* `welcome_message`: A message that will be send to a newly registered users as a direct message. -* `welcome_user_nickname`: The nickname of the local user that sends the welcome message. * `max_report_comment_size`: The maximum size of the report comment (Default: `1000`). * `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). Default: `false`. * `healthcheck`: If set to true, system data will be shown on ``/api/pleroma/healthcheck``. @@ -60,8 +59,40 @@ To add configuration to your config file, you can copy it from the base config. * `max_remote_account_fields`: The maximum number of custom fields in the remote user profile (default: `20`). * `account_field_name_length`: An account field name maximum length (default: `512`). * `account_field_value_length`: An account field value maximum length (default: `2048`). +* `registration_reason_length`: Maximum registration reason length (default: `500`). * `external_user_synchronization`: Enabling following/followers counters synchronization for external users. * `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances. +* `show_reactions`: Let favourites and emoji reactions be viewed through the API (default: `true`). + +## Welcome +* `direct_message`: - welcome message sent as a direct message. + * `enabled`: Enables the send a direct message to a newly registered user. Defaults to `false`. + * `sender_nickname`: The nickname of the local user that sends the welcome message. + * `message`: A message that will be send to a newly registered users as a direct message. +* `email`: - welcome message sent as a email. + * `enabled`: Enables the send a welcome email to a newly registered user. Defaults to `false`. + * `sender`: The email address or tuple with `{nickname, email}` that will use as sender to the welcome email. + * `subject`: A subject of welcome email. + * `html`: A html that will be send to a newly registered users as a email. + * `text`: A text that will be send to a newly registered users as a email. + + Example: + + ```elixir + config :pleroma, :welcome, + direct_message: [ + enabled: true, + sender_nickname: "lain", + message: "Hi, @username! Welcome on board!" + ], + email: [ + enabled: true, + sender: {"Pleroma App", "welcome@pleroma.app"}, + subject: "Welcome to <%= instance_name %>", + html: "Welcome to <%= instance_name %>", + text: "Welcome to <%= instance_name %>" + ] + ``` ## Message rewrite facility @@ -252,6 +283,7 @@ This section describe PWA manifest instance-specific values. Currently this opti * `background_color`: Describe the background color of the app. (Example: `"#191b22"`, `"aliceblue"`). ## :emoji + * `shortcode_globs`: Location of custom emoji files. `*` can be used as a wildcard. Example `["/emoji/custom/**/*.png"]` * `pack_extensions`: A list of file extensions for emojis, when no emoji.txt for a pack is present. Example `[".png", ".gif"]` * `groups`: Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the groupname and the value the location or array of locations. `*` can be used as a wildcard. Example `[Custom: ["/emoji/*.png", "/emoji/custom/*.png"]]` @@ -260,13 +292,14 @@ This section describe PWA manifest instance-specific values. Currently this opti memory for this amount of seconds multiplied by the number of files. ## :media_proxy + * `enabled`: Enables proxying of remote media to the instance’s proxy * `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts. * `proxy_opts`: All options defined in `Pleroma.ReverseProxy` documentation, defaults to `[max_body_length: (25*1_048_576)]`. -* `whitelist`: List of domains to bypass the mediaproxy +* `whitelist`: List of hosts with scheme to bypass the mediaproxy (e.g. `https://example.com`) * `invalidation`: options for remove media from cache after delete object: - * `enabled`: Enables purge cache - * `provider`: Which one of the [purge cache strategy](#purge-cache-strategy) to use. + * `enabled`: Enables purge cache + * `provider`: Which one of the [purge cache strategy](#purge-cache-strategy) to use. ### Purge cache strategy @@ -278,6 +311,7 @@ Urls of attachments pass to script as arguments. * `script_path`: path to external script. Example: + ```elixir config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Script, script_path: "./installation/nginx-cache-purge.example" @@ -445,37 +479,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 @@ -494,7 +523,7 @@ A built-in captcha provider. Enabled by default. #### Pleroma.Captcha.Kocaptcha Kocaptcha is a very simple captcha service with a single API endpoint, -the source code is here: https://github.com/koto-bank/kocaptcha. The default endpoint +the source code is here: [kocaptcha](https://github.com/koto-bank/kocaptcha). The default endpoint `https://captcha.kotobank.ch` is hosted by the developer. * `endpoint`: the Kocaptcha endpoint to use. @@ -502,6 +531,7 @@ the source code is here: https://github.com/koto-bank/kocaptcha. The default end ## Uploads ### Pleroma.Upload + * `uploader`: Which one of the [uploaders](#uploaders) to use. * `filters`: List of [upload filters](#upload-filters) to use. * `link_name`: When enabled Pleroma will add a `name` parameter to the url of the upload, for example `https://instance.tld/media/corndog.png?name=corndog.png`. This is needed to provide the correct filename in Content-Disposition headers when using filters like `Pleroma.Upload.Filter.Dedupe` @@ -514,10 +544,15 @@ the source code is here: https://github.com/koto-bank/kocaptcha. The default end `strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`. ### Uploaders + #### Pleroma.Uploaders.Local + * `uploads`: Which directory to store the user-uploads in, relative to pleroma’s working directory. #### Pleroma.Uploaders.S3 + +Don't forget to configure [Ex AWS S3](#ex-aws-s3-settings) + * `bucket`: S3 bucket name. * `bucket_namespace`: S3 bucket namespace. * `public_endpoint`: S3 endpoint that the user finally accesses(ex. "https://s3.dualstack.ap-northeast-1.amazonaws.com") @@ -526,17 +561,23 @@ For example, when using CDN to S3 virtual host format, set "". At this time, write CNAME to CDN in public_endpoint. * `streaming_enabled`: Enable streaming uploads, when enabled the file will be sent to the server in chunks as it's being read. This may be unsupported by some providers, try disabling this if you have upload problems. +#### Ex AWS S3 settings + +* `access_key_id`: Access key ID +* `secret_access_key`: Secret access key +* `host`: S3 host + +Example: + +```elixir +config :ex_aws, :s3, + access_key_id: "xxxxxxxxxx", + secret_access_key: "yyyyyyyyyy", + host: "s3.eu-central-1.amazonaws.com" +``` ### Upload filters -#### Pleroma.Upload.Filter.Mogrify - -* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"implode", "1"}]`. - -#### Pleroma.Upload.Filter.Dedupe - -No specific configuration. - #### Pleroma.Upload.Filter.AnonymizeFilename This filter replaces the filename (not the path) of an upload. For complete obfuscation, add @@ -544,6 +585,20 @@ This filter replaces the filename (not the path) of an upload. For complete obfu * `text`: Text to replace filenames in links. If empty, `{random}.extension` will be used. You can get the original filename extension by using `{extension}`, for example `custom-file-name.{extension}`. +#### Pleroma.Upload.Filter.Dedupe + +No specific configuration. + +#### Pleroma.Upload.Filter.Exiftool + +This filter only strips the GPS and location metadata with Exiftool leaving color profiles and attributes intact. + +No specific configuration. + +#### Pleroma.Upload.Filter.Mogrify + +* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"implode", "1"}]`. + ## Email ### Pleroma.Emails.Mailer @@ -604,8 +659,7 @@ Email notifications settings. Configuration options described in [Oban readme](https://github.com/sorentwo/oban#usage): * `repo` - app's Ecto repo (`Pleroma.Repo`) -* `verbose` - logs verbosity -* `prune` - non-retryable jobs [pruning settings](https://github.com/sorentwo/oban#pruning) (`:disabled` / `{:maxlen, value}` / `{:maxage, value}`) +* `log` - logs verbosity * `queues` - job queues (see below) * `crontab` - periodic jobs, see [`Oban.Cron`](#obancron) @@ -790,6 +844,8 @@ or curl -H "X-Admin-Token: somerandomtoken" "http://localhost:4000/api/pleroma/admin/users/invites" ``` +Warning: it's discouraged to use this feature because of the associated security risk: static / rarely changed instance-wide token is much weaker compared to email-password pair of a real admin user; consider using HTTP Basic Auth or OAuth-based authentication instead. + ### :auth * `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator. @@ -909,30 +965,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`) @@ -983,7 +1038,7 @@ Restrict access for unauthenticated users to timelines (public and federated), u * `local` * `remote` -Note: setting `restrict_unauthenticated/timelines/local` to `true` has no practical sense if `restrict_unauthenticated/timelines/federated` is set to `false` (since local public activities will still be delivered to unauthenticated users as part of federated timeline). +Note: setting `restrict_unauthenticated/timelines/local` to `true` has no practical sense if `restrict_unauthenticated/timelines/federated` is set to `false` (since local public activities will still be delivered to unauthenticated users as part of federated timeline). ## Pleroma.Web.ApiSpec.CastAndValidate @@ -994,3 +1049,25 @@ Note: setting `restrict_unauthenticated/timelines/local` to `true` has no practi Control favicons for instances. * `enabled`: Allow/disallow displaying and getting instances favicons + +## Frontend management + +Frontends in Pleroma are swappable - you can specify which one to use here. + +For now, you can set a frontend with the key `primary` and the options of `name` and `ref`. This will then make Pleroma serve the frontend from a folder constructed by concatenating the instance static path, `frontends` and the name and ref. + +The key `primary` refers to the frontend that will be served by default for general requests. In the future, other frontends like the admin frontend will also be configurable here. + +If you don't set anything here, the bundled frontend will be used. + +Example: + +``` +config :pleroma, :frontends, + primary: %{ + "name" => "pleroma", + "ref" => "stable" + } +``` + +This would serve the frontend from the the folder at `$instance_static/frontends/pleroma/stable`. You have to copy the frontend into this folder yourself. You can choose the name and ref any way you like, but they will be used by mix tasks to automate installation in the future, the name referring to the project and the ref referring to a commit. diff --git a/docs/configuration/howto_database_config.md b/docs/configuration/howto_database_config.md new file mode 100644 index 000000000..9ed4d6cdd --- /dev/null +++ b/docs/configuration/howto_database_config.md @@ -0,0 +1,153 @@ +# How to activate Pleroma in-database configuration +## Explanation + +The configuration of Pleroma has traditionally been managed with a config file, e.g. `config/prod.secret.exs`. This method requires a restart of the application for any configuration changes to take effect. We have made it possible to control most settings in the AdminFE interface after running a migration script. + +## Migration to database config + +1. Run the mix task to migrate to the database. You'll receive some debugging output and a few messages informing you of what happened. + + **Source:** + + ``` + $ mix pleroma.config migrate_to_db + ``` + + or + + **OTP:** + + *Note: OTP users need Pleroma to be running for `pleroma_ctl` commands to work* + + ``` + $ ./bin/pleroma_ctl config migrate_to_db + ``` + + ``` + 10:04:34.155 [debug] QUERY OK source="config" db=1.6ms decode=2.0ms queue=33.5ms idle=0.0ms + SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 [] + Migrating settings from file: /home/pleroma/config/dev.secret.exs + + 10:04:34.240 [debug] QUERY OK db=4.5ms queue=0.3ms idle=92.2ms + TRUNCATE config; [] + + 10:04:34.244 [debug] QUERY OK db=2.8ms queue=0.3ms idle=97.2ms + ALTER SEQUENCE config_id_seq RESTART; [] + + 10:04:34.256 [debug] QUERY OK source="config" db=0.8ms queue=1.4ms idle=109.8ms + SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 WHERE ((c0."group" = $1) AND (c0."key" = $2)) [":pleroma", ":instance"] + + 10:04:34.292 [debug] QUERY OK db=2.6ms queue=1.7ms idle=137.7ms + INSERT INTO "config" ("group","key","value","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" [":pleroma", ":instance", <<131, 108, 0, 0, 0, 1, 104, 2, 100, 0, 4, 110, 97, 109, 101, 109, 0, 0, 0, 7, 66, 108, 101, 114, 111, 109, 97, 106>>, ~N[2020-07-12 15:04:34], ~N[2020-07-12 15:04:34]] + Settings for key instance migrated. + Settings for group :pleroma migrated. + ``` + +2. It is recommended to backup your config file now. + + ``` + cp config/dev.secret.exs config/dev.secret.exs.orig + ``` + +3. Edit your Pleroma config to enable database configuration: + + ``` + config :pleroma, configurable_from_database: true + ``` + +4. ⚠️ **THIS IS NOT REQUIRED** ⚠️ + + Now you can edit your config file and strip it down to the only settings which are not possible to control in the database. e.g., the Postgres (Repo) and webserver (Endpoint) settings cannot be controlled in the database because the application needs the settings to start up and access the database. + + Any settings in the database will override those in the config file, but you may find it less confusing if the setting is only declared in one place. + + A non-exhaustive list of settings that are only possible in the config file include the following: + + * config :pleroma, Pleroma.Web.Endpoint + * config :pleroma, Pleroma.Repo + * config :pleroma, configurable\_from\_database + * config :pleroma, :database, rum_enabled + * config :pleroma, :connections_pool + + Here is an example of a server config stripped down after migration: + + ``` + use Mix.Config + + config :pleroma, Pleroma.Web.Endpoint, + url: [host: "cool.pleroma.site", scheme: "https", port: 443] + + config :pleroma, Pleroma.Repo, + adapter: Ecto.Adapters.Postgres, + username: "pleroma", + password: "MySecretPassword", + database: "pleroma_prod", + hostname: "localhost" + + config :pleroma, configurable_from_database: true + ``` + +5. Restart your instance and you can now access the Settings tab in AdminFE. + + +## Reverting back from database config + +1. Run the mix task to migrate back from the database. You'll receive some debugging output and a few messages informing you of what happened. + + **Source:** + + ``` + $ mix pleroma.config migrate_from_db + ``` + + or + + **OTP:** + + ``` + $ ./bin/pleroma_ctl config migrate_from_db + ``` + + ``` + 10:26:30.593 [debug] QUERY OK source="config" db=9.8ms decode=1.2ms queue=26.0ms idle=0.0ms + SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 [] + + 10:26:30.659 [debug] QUERY OK source="config" db=1.1ms idle=80.7ms + SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 [] + Database configuration settings have been saved to config/dev.exported_from_db.secret.exs + ``` + +2. Remove `config :pleroma, configurable_from_database: true` from your config. The in-database configuration still exists, but it will not be used. Future migrations will erase the database config before importing your config file again. + +3. Restart your instance. + +## Debugging + +### Clearing database config +You can clear the database config by truncating the `config` table in the database. e.g., + +``` +psql -d pleroma_dev +pleroma_dev=# TRUNCATE config; +TRUNCATE TABLE +``` + +Additionally, every time you migrate the configuration to the database the config table is automatically truncated to ensure a clean migration. + +### Manually removing a setting +If you encounter a situation where the server cannot run properly because of an invalid setting in the database and this is preventing you from accessing AdminFE, you can manually remove the offending setting if you know which one it is. + +e.g., here is an example showing a minimal configuration in the database. Only the `config :pleroma, :instance` settings are in the table: + +``` +psql -d pleroma_dev +pleroma_dev=# select * from config; + id | key | value | inserted_at | updated_at | group +----+-----------+------------------------------------------------------------+---------------------+---------------------+---------- + 1 | :instance | \x836c0000000168026400046e616d656d00000007426c65726f6d616a | 2020-07-12 15:33:29 | 2020-07-12 15:33:29 | :pleroma +(1 row) +pleroma_dev=# delete from config where key = ':instance' and group = ':pleroma'; +DELETE 1 +``` + +Now the `config :pleroma, :instance` settings have been removed from the database. diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md index e4f822d1c..338dfa7d0 100644 --- a/docs/installation/otp_en.md +++ b/docs/installation/otp_en.md @@ -121,6 +121,9 @@ chown -R pleroma /etc/pleroma # Run the config generator su pleroma -s $SHELL -lc "./bin/pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql" +# Run the environment file generator. +su pleroma -s $SHELL -lc "./bin/pleroma_ctl release_env gen" + # Create the postgres database su postgres -s $SHELL -lc "psql -f /tmp/setup_db.psql" @@ -131,7 +134,7 @@ su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate" # su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/" # Start the instance to verify that everything is working as expected -su pleroma -s $SHELL -lc "./bin/pleroma daemon" +su pleroma -s $SHELL -lc "export $(cat /opt/pleroma/config/pleroma.env); ./bin/pleroma daemon" # Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly sleep 20 && curl http://localhost:4000/api/v1/instance @@ -200,6 +203,7 @@ rc-update add pleroma # Copy the service into a proper directory cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service + # Start pleroma and enable it on boot systemctl start pleroma systemctl enable pleroma @@ -275,4 +279,3 @@ This will create an account withe the username of 'joeuser' with the email addre ## Questions Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**. - diff --git a/installation/init.d/pleroma b/installation/init.d/pleroma index 384536f7e..e908cda1b 100755 --- a/installation/init.d/pleroma +++ b/installation/init.d/pleroma @@ -8,6 +8,7 @@ pidfile="/var/run/pleroma.pid" directory=/opt/pleroma healthcheck_delay=60 healthcheck_timer=30 +export $(cat /opt/pleroma/config/pleroma.env) : ${pleroma_port:-4000} diff --git a/installation/pleroma.service b/installation/pleroma.service index 5dcbc1387..ee00a3b7a 100644 --- a/installation/pleroma.service +++ b/installation/pleroma.service @@ -17,6 +17,8 @@ Environment="MIX_ENV=prod" Environment="HOME=/var/lib/pleroma" ; Path to the folder containing the Pleroma installation. WorkingDirectory=/opt/pleroma +; Path to the environment file. the file contains RELEASE_COOKIE and etc +EnvironmentFile=/opt/pleroma/config/pleroma.env ; Path to the Mix binary. ExecStart=/usr/bin/mix phx.server diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex index 3ad6edbfb..074492a46 100644 --- a/lib/mix/pleroma.ex +++ b/lib/mix/pleroma.ex @@ -3,15 +3,53 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Pleroma do + @apps [ + :restarter, + :ecto, + :ecto_sql, + :postgrex, + :db_connection, + :cachex, + :flake_id, + :swoosh, + :timex + ] + @cachex_children ["object", "user"] @doc "Common functions to be reused in mix tasks" def start_pleroma do + Pleroma.Config.Holder.save_default() Application.put_env(:phoenix, :serve_endpoints, false, persistent: true) if Pleroma.Config.get(:env) != :test do Application.put_env(:logger, :console, level: :debug) end - {:ok, _} = Application.ensure_all_started(:pleroma) + adapter = Application.get_env(:tesla, :adapter) + + apps = + if adapter == Tesla.Adapter.Gun do + [:gun | @apps] + else + [:hackney | @apps] + end + + Enum.each(apps, &Application.ensure_all_started/1) + + 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, [])) + + Supervisor.start_link(children ++ cachex_children, + strategy: :one_for_one, + name: Pleroma.Supervisor + ) if Pleroma.Config.get(:env) not in [:test, :benchmark] do pleroma_rebooted?() @@ -82,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/config.ex b/lib/mix/tasks/pleroma/config.ex index d5129d410..904c5a74b 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -83,7 +83,7 @@ defp create(group, settings) do defp migrate_from_db(opts) do if Pleroma.Config.get([:configurable_from_database]) do - env = opts[:env] || "prod" + env = opts[:env] || Pleroma.Config.get(:env) config_path = if Pleroma.Config.get(:release) do @@ -105,6 +105,10 @@ defp migrate_from_db(opts) do :ok = File.close(file) System.cmd("mix", ["format", config_path]) + + shell_info( + "Database configuration settings have been exported to config/#{env}.exported_from_db.secret.exs" + ) else migration_error() end @@ -112,7 +116,7 @@ defp migrate_from_db(opts) do defp migration_error do shell_error( - "Migration is not allowed in config. You can change this behavior by setting `configurable_from_database` to true." + "Migration is not allowed in config. You can change this behavior by setting `config :pleroma, configurable_from_database: true`" ) end 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/mix/tasks/pleroma/release_env.ex b/lib/mix/tasks/pleroma/release_env.ex new file mode 100644 index 000000000..9da74ffcf --- /dev/null +++ b/lib/mix/tasks/pleroma/release_env.ex @@ -0,0 +1,76 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Pleroma.ReleaseEnv do + use Mix.Task + import Mix.Pleroma + + @shortdoc "Generate Pleroma environment file." + @moduledoc File.read!("docs/administration/CLI_tasks/release_environments.md") + + def run(["gen" | rest]) do + {options, [], []} = + OptionParser.parse( + rest, + strict: [ + force: :boolean, + path: :string + ], + aliases: [ + p: :path, + f: :force + ] + ) + + file_path = + get_option( + options, + :path, + "Environment file path", + "./config/pleroma.env" + ) + + env_path = Path.expand(file_path) + + proceed? = + if File.exists?(env_path) do + get_option( + options, + :force, + "Environment file already exists. Do you want to overwrite the #{env_path} file? (y/n)", + "n" + ) === "y" + else + true + end + + if proceed? do + case do_generate(env_path) do + {:error, reason} -> + shell_error( + File.Error.message(%{action: "write to file", reason: reason, path: env_path}) + ) + + _ -> + shell_info("\nThe file generated: #{env_path}.\n") + + shell_info(""" + WARNING: before start pleroma app please make sure to make the file read-only and non-modifiable. + Example: + chmod 0444 #{file_path} + chattr +i #{file_path} + """) + end + else + shell_info("\nThe file is exist. #{env_path}.\n") + end + end + + def do_generate(path) do + content = "RELEASE_COOKIE=#{Base.encode32(:crypto.strong_rand_bytes(32))}" + + File.mkdir_p!(Path.dirname(path)) + File.write(path, content) + end +end diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index b8d65b03e..ba44c045a 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -232,7 +232,7 @@ def run(["tag", nickname | tags]) do with %User{} = user <- User.get_cached_by_nickname(nickname) do user = user |> User.tag(tags) - shell_info("Tags of #{user.nickname}: #{inspect(tags)}") + shell_info("Tags of #{user.nickname}: #{inspect(user.tags)}") else _ -> shell_error("Could not change user tags for #{nickname}") @@ -245,7 +245,7 @@ def run(["untag", nickname | tags]) do with %User{} = user <- User.get_cached_by_nickname(nickname) do user = user |> User.untag(tags) - shell_info("Tags of #{user.nickname}: #{inspect(tags)}") + shell_info("Tags of #{user.nickname}: #{inspect(user.tags)}") else _ -> shell_error("Could not change user tags for #{nickname}") diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 32773d3c9..0ffb55358 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -35,6 +35,11 @@ def user_agent do # See http://elixir-lang.org/docs/stable/elixir/Application.html # for more information on OTP Applications def start(_type, _args) do + # Scrubbers are compiled at runtime and therefore will cause a conflict + # every time the application is restarted, so we disable module + # conflicts at runtime + Code.compiler_options(ignore_module_conflict: true) + Pleroma.Telemetry.Logger.attach() Config.Holder.save_default() Pleroma.HTML.compile_scrubbers() Config.DeprecationWarnings.warn() @@ -42,6 +47,7 @@ def start(_type, _args) do Pleroma.ApplicationRequirements.verify!() setup_instrumenters() load_custom_modules() + Pleroma.Docs.JSON.compile() adapter = Application.get_env(:tesla, :adapter) @@ -162,7 +168,8 @@ defp idempotency_expiration, defp seconds_valid_interval, do: :timer.seconds(Config.get!([Pleroma.Captcha, :seconds_valid])) - defp build_cachex(type, opts), + @spec build_cachex(String.t(), keyword()) :: map() + def build_cachex(type, opts), do: %{ id: String.to_atom("cachex_" <> type), start: {Cachex, :start_link, [String.to_atom(type <> "_cache"), opts]}, @@ -217,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 @@ -238,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..16f62b6f5 100644 --- a/lib/pleroma/application_requirements.ex +++ b/lib/pleroma/application_requirements.ex @@ -16,7 +16,9 @@ defmodule VerifyError, do: defexception([:message]) @spec verify!() :: :ok | VerifyError.t() def verify! do :ok + |> check_confirmation_accounts! |> check_migrations_applied!() + |> check_welcome_message_config!() |> check_rum!() |> handle_result() end @@ -24,6 +26,40 @@ def verify! do defp handle_result(:ok), do: :ok defp handle_result({:error, message}), do: raise(VerifyError, message: message) + defp check_welcome_message_config!(:ok) do + if Pleroma.Config.get([:welcome, :email, :enabled], false) and + not Pleroma.Emails.Mailer.enabled?() do + Logger.error(""" + To send welcome email do you need to enable mail. + \nconfig :pleroma, Pleroma.Emails.Mailer, enabled: true + """) + + {:error, "The mail disabled."} + else + :ok + end + end + + defp check_welcome_message_config!(result), do: result + + # 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/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex index 6bc2fa158..337506647 100644 --- a/lib/pleroma/captcha/kocaptcha.ex +++ b/lib/pleroma/captcha/kocaptcha.ex @@ -21,7 +21,8 @@ def new do type: :kocaptcha, token: json_resp["token"], url: endpoint <> json_resp["url"], - answer_data: json_resp["md5"] + answer_data: json_resp["md5"], + seconds_valid: Pleroma.Config.get([Pleroma.Captcha, :seconds_valid]) } end end diff --git a/lib/pleroma/captcha/native.ex b/lib/pleroma/captcha/native.ex index a90631d61..8d604d2b2 100644 --- a/lib/pleroma/captcha/native.ex +++ b/lib/pleroma/captcha/native.ex @@ -17,7 +17,8 @@ def new do type: :native, token: token(), url: "data:image/png;base64," <> Base.encode64(img_binary), - answer_data: answer_data + answer_data: answer_data, + seconds_valid: Pleroma.Config.get([Pleroma.Captcha, :seconds_valid]) } end end 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/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex index 0a6c724fb..0f52eb210 100644 --- a/lib/pleroma/config/deprecation_warnings.ex +++ b/lib/pleroma/config/deprecation_warnings.ex @@ -54,6 +54,25 @@ def warn do check_hellthread_threshold() mrf_user_allowlist() check_old_mrf_config() + check_media_proxy_whitelist_config() + check_welcome_message_config() + end + + def check_welcome_message_config do + instance_config = Pleroma.Config.get([:instance]) + + use_old_config = + Keyword.has_key?(instance_config, :welcome_user_nickname) or + Keyword.has_key?(instance_config, :welcome_message) + + if use_old_config do + Logger.error(""" + !!!DEPRECATION WARNING!!! + Your config is using the old namespace for Welcome messages configuration. You need to change to the new namespace: + \n* `config :pleroma, :instance, welcome_user_nickname` is now `config :pleroma, :welcome, :direct_message, :sender_nickname` + \n* `config :pleroma, :instance, welcome_message` is now `config :pleroma, :welcome, :direct_message, :message` + """) + end end def check_old_mrf_config do @@ -65,7 +84,7 @@ def check_old_mrf_config do move_namespace_and_warn(@mrf_config_map, warning_preface) end - @spec move_namespace_and_warn([config_map()], String.t()) :: :ok + @spec move_namespace_and_warn([config_map()], String.t()) :: :ok | nil def move_namespace_and_warn(config_map, warning_preface) do warning = Enum.reduce(config_map, "", fn @@ -84,4 +103,16 @@ def move_namespace_and_warn(config_map, warning_preface) do Logger.warn(warning_preface <> warning) end end + + @spec check_media_proxy_whitelist_config() :: :ok | nil + def check_media_proxy_whitelist_config do + whitelist = Config.get([:media_proxy, :whitelist]) + + if Enum.any?(whitelist, &(not String.starts_with?(&1, "http"))) do + Logger.warn(""" + !!!DEPRECATION WARNING!!! + Your config is using old format (only domain) for MediaProxy whitelist option. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later. + """) + end + end end diff --git a/lib/pleroma/config/helpers.ex b/lib/pleroma/config/helpers.ex new file mode 100644 index 000000000..3dce40ea0 --- /dev/null +++ b/lib/pleroma/config/helpers.ex @@ -0,0 +1,17 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Config.Helpers do + alias Pleroma.Config + + def instance_name, do: Config.get([:instance, :name]) + + defp instance_notify_email do + Config.get([:instance, :notify_email]) || Config.get([:instance, :email]) + end + + def sender do + {instance_name(), instance_notify_email()} + end +end diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index eb86b8ff4..a0d7b7d71 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -31,8 +31,8 @@ defmodule Pleroma.Config.TransferTask do {:pleroma, :gopher, [:enabled]} ] - def start_link(_) do - load_and_update_env() + def start_link(restart_pleroma? \\ true) do + load_and_update_env([], restart_pleroma?) if Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Repo) :ignore end diff --git a/lib/pleroma/docs/generator.ex b/lib/pleroma/docs/generator.ex index e0fc8cd02..a671a6278 100644 --- a/lib/pleroma/docs/generator.ex +++ b/lib/pleroma/docs/generator.ex @@ -6,16 +6,21 @@ def process(implementation, descriptions) do implementation.process(descriptions) end - @spec list_modules_in_dir(String.t(), String.t()) :: [module()] - def list_modules_in_dir(dir, start) do - with {:ok, files} <- File.ls(dir) do - files - |> Enum.filter(&String.ends_with?(&1, ".ex")) - |> Enum.map(fn filename -> - module = filename |> String.trim_trailing(".ex") |> Macro.camelize() - String.to_atom(start <> module) - end) - end + @spec list_behaviour_implementations(behaviour :: module()) :: [module()] + def list_behaviour_implementations(behaviour) do + :code.all_loaded() + |> Enum.filter(fn {module, _} -> + # This shouldn't be needed as all modules are expected to have module_info/1, + # but in test enviroments some transient modules `:elixir_compiler_XX` + # are loaded for some reason (where XX is a random integer). + if function_exported?(module, :module_info, 1) do + module.module_info(:attributes) + |> Keyword.get_values(:behaviour) + |> List.flatten() + |> Enum.member?(behaviour) + end + end) + |> Enum.map(fn {module, _} -> module end) end @doc """ @@ -87,6 +92,12 @@ defp humanize(entity) do else: string end + defp format_suggestions({:list_behaviour_implementations, behaviour}) do + behaviour + |> list_behaviour_implementations() + |> format_suggestions() + end + defp format_suggestions([]), do: [] defp format_suggestions([suggestion | tail]) do diff --git a/lib/pleroma/docs/json.ex b/lib/pleroma/docs/json.ex index d1cf1f487..feeb4320e 100644 --- a/lib/pleroma/docs/json.ex +++ b/lib/pleroma/docs/json.ex @@ -1,5 +1,19 @@ defmodule Pleroma.Docs.JSON do @behaviour Pleroma.Docs.Generator + @external_resource "config/description.exs" + @raw_config Pleroma.Config.Loader.read("config/description.exs") + @raw_descriptions @raw_config[:pleroma][:config_description] + @term __MODULE__.Compiled + + @spec compile :: :ok + def compile do + :persistent_term.put(@term, Pleroma.Docs.Generator.convert_to_strings(@raw_descriptions)) + end + + @spec compiled_descriptions :: Map.t() + def compiled_descriptions do + :persistent_term.get(@term) + end @spec process(keyword()) :: {:ok, String.t()} def process(descriptions) do @@ -13,11 +27,4 @@ def process(descriptions) do {:ok, path} end end - - def compile do - with config <- Pleroma.Config.Loader.read("config/description.exs") do - config[:pleroma][:config_description] - |> Pleroma.Docs.Generator.convert_to_strings() - end - end end diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex index 68b106499..da3f20f43 100644 --- a/lib/pleroma/docs/markdown.ex +++ b/lib/pleroma/docs/markdown.ex @@ -68,6 +68,11 @@ defp print_suggestion(file, suggestion, as_list \\ false) do IO.write(file, " #{list_mark}`#{inspect(suggestion)}`\n") end + defp print_suggestions(file, {:list_behaviour_implementations, behaviour}) do + suggestions = Pleroma.Docs.Generator.list_behaviour_implementations(behaviour) + print_suggestions(file, suggestions) + end + defp print_suggestions(_file, nil), do: nil defp print_suggestions(_file, ""), do: nil diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex index aa0b2a66b..c27ad1065 100644 --- a/lib/pleroma/emails/admin_email.ex +++ b/lib/pleroma/emails/admin_email.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Emails.AdminEmail do import Swoosh.Email alias Pleroma.Config + alias Pleroma.HTML alias Pleroma.Web.Router.Helpers defp instance_config, do: Config.get(:instance) @@ -82,4 +83,18 @@ def report(to, reporter, account, statuses, comment) do |> subject("#{instance_name()} Report") |> html_body(html_body) end + + def new_unapproved_registration(to, account) do + html_body = """ +

New account for review: @#{account.nickname}

+
#{HTML.strip_tags(account.registration_reason)}
+ Visit AdminFE + """ + + new() + |> to({to.name, to.email}) + |> from({instance_name(), instance_notify_email()}) + |> subject("New account up for review on #{instance_name()} (@#{account.nickname})") + |> html_body(html_body) + end end diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex index dfadc10b3..313533859 100644 --- a/lib/pleroma/emails/user_email.ex +++ b/lib/pleroma/emails/user_email.ex @@ -12,17 +12,22 @@ defmodule Pleroma.Emails.UserEmail do alias Pleroma.Web.Endpoint alias Pleroma.Web.Router - defp instance_name, do: Config.get([:instance, :name]) - - defp sender do - email = Config.get([:instance, :notify_email]) || Config.get([:instance, :email]) - {instance_name(), email} - end + import Pleroma.Config.Helpers, only: [instance_name: 0, sender: 0] defp recipient(email, nil), do: email defp recipient(email, name), do: {name, email} defp recipient(%User{} = user), do: recipient(user.email, user.name) + @spec welcome(User.t(), map()) :: Swoosh.Email.t() + def welcome(user, opts \\ %{}) do + new() + |> to(recipient(user)) + |> from(Map.get(opts, :sender, sender())) + |> subject(Map.get(opts, :subject, "Welcome to #{instance_name()}!")) + |> html_body(Map.get(opts, :html, "Welcome to #{instance_name()}!")) + |> text_body(Map.get(opts, :text, "Welcome to #{instance_name()}!")) + end + def password_reset_email(user, token) when is_binary(token) do password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token) 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..f34602b73 --- /dev/null +++ b/lib/pleroma/gun/connection_pool.ex @@ -0,0 +1,82 @@ +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 + + @spec get_conn(URI.t(), keyword()) :: {:ok, pid()} | {:error, term()} + 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.call(worker_pid, :add_client) + {: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()}) + + receive do + {:conn_pid, pid} -> + Process.demonitor(ref) + {:ok, pid} + + {:DOWN, ^ref, :process, ^worker_pid, reason} -> + case reason do + {:shutdown, {:error, _} = error} -> error + {:shutdown, error} -> {:error, error} + _ -> {:error, reason} + end + end + end + + @spec release_conn(pid()) :: :ok + 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.call(worker_pid, :remove_client) + + [] -> + :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..fec9d0efa --- /dev/null +++ b/lib/pleroma/gun/connection_pool/worker.ex @@ -0,0 +1,133 @@ +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}, state) do + case handle_call(:add_client, {client_pid, nil}, state) do + {:reply, conn_pid, state, :hibernate} -> + send(client_pid, {:conn_pid, conn_pid}) + {:noreply, state, :hibernate} + end + end + + @impl true + def handle_cast({:remove_client, client_pid}, state) do + case handle_call(:remove_client, {client_pid, nil}, state) do + {:reply, _, state, :hibernate} -> + {:noreply, state, :hibernate} + end + end + + @impl true + def handle_call(:add_client, {client_pid, _}, %{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) + + 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) + {:reply, conn_pid, state, :hibernate} + end + + @impl true + def handle_call(: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 + + {:reply, :ok, %{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 + :telemetry.execute( + [:pleroma, :connection_pool, :client_death], + %{client_pid: pid, reason: reason}, + %{key: state.key} + ) + + handle_cast({:remove_client, pid}, state) + 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/http/request_builder.ex b/lib/pleroma/http/request_builder.ex index 2fc876d92..8a44a001d 100644 --- a/lib/pleroma/http/request_builder.ex +++ b/lib/pleroma/http/request_builder.ex @@ -34,10 +34,12 @@ def url(request, u), do: %{request | url: u} @spec headers(Request.t(), Request.headers()) :: Request.t() def headers(request, headers) do headers_list = - if Pleroma.Config.get([:http, :send_user_agent]) do + with true <- Pleroma.Config.get([:http, :send_user_agent]), + nil <- Enum.find(headers, fn {key, _val} -> String.downcase(key) == "user-agent" end) do [{"user-agent", Pleroma.Application.user_agent()} | headers] else - headers + _ -> + headers end %{request | headers: headers_list} diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index 7aacd9d80..31c9afe2a 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -409,6 +409,17 @@ def get_log_entry_message(%ModerationLog{ "@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}" end + @spec get_log_entry_message(ModerationLog) :: String.t() + def get_log_entry_message(%ModerationLog{ + data: %{ + "actor" => %{"nickname" => actor_nickname}, + "action" => "approve", + "subject" => users + } + }) do + "@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}" + end + @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ 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/plugs/admin_secret_authentication_plug.ex b/lib/pleroma/plugs/admin_secret_authentication_plug.ex index b4b47a31f..2e54df47a 100644 --- a/lib/pleroma/plugs/admin_secret_authentication_plug.ex +++ b/lib/pleroma/plugs/admin_secret_authentication_plug.ex @@ -4,6 +4,9 @@ defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do import Plug.Conn + + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Plugs.RateLimiter alias Pleroma.User def init(options) do @@ -11,7 +14,10 @@ def init(options) do end def secret_token do - Pleroma.Config.get(:admin_token) + case Pleroma.Config.get(:admin_token) do + blank when blank in [nil, ""] -> nil + token -> token + end end def call(%{assigns: %{user: %User{}}} = conn, _), do: conn @@ -26,9 +32,9 @@ def call(conn, _) do def authenticate(%{params: %{"admin_token" => admin_token}} = conn) do if admin_token == secret_token() do - assign(conn, :user, %User{is_admin: true}) + assign_admin_user(conn) else - conn + handle_bad_token(conn) end end @@ -36,8 +42,19 @@ def authenticate(conn) do token = secret_token() case get_req_header(conn, "x-admin-token") do - [^token] -> assign(conn, :user, %User{is_admin: true}) - _ -> conn + blank when blank in [[], [""]] -> conn + [^token] -> assign_admin_user(conn) + _ -> handle_bad_token(conn) end end + + defp assign_admin_user(conn) do + conn + |> assign(:user, %User{is_admin: true}) + |> OAuthScopesPlug.skip_plug() + end + + defp handle_bad_token(conn) do + RateLimiter.call(conn, name: :authentication) + end end diff --git a/lib/pleroma/plugs/frontend_static.ex b/lib/pleroma/plugs/frontend_static.ex new file mode 100644 index 000000000..f549ca75f --- /dev/null +++ b/lib/pleroma/plugs/frontend_static.ex @@ -0,0 +1,54 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Plugs.FrontendStatic do + require Pleroma.Constants + + @moduledoc """ + This is a shim to call `Plug.Static` but with runtime `from` configuration`. It dispatches to the different frontends. + """ + @behaviour Plug + + def file_path(path, frontend_type \\ :primary) do + if configuration = Pleroma.Config.get([:frontends, frontend_type]) do + instance_static_path = Pleroma.Config.get([:instance, :static_dir], "instance/static") + + Path.join([ + instance_static_path, + "frontends", + configuration["name"], + configuration["ref"], + path + ]) + else + nil + end + end + + def init(opts) do + opts + |> Keyword.put(:from, "__unconfigured_frontend_static_plug") + |> Plug.Static.init() + end + + def call(conn, opts) do + frontend_type = Map.get(opts, :frontend_type, :primary) + path = file_path("", frontend_type) + + if path do + conn + |> call_static(opts, path) + else + conn + end + end + + defp call_static(conn, opts, from) do + opts = + opts + |> Map.put(:from, from) + + Plug.Static.call(conn, opts) + end +end diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex index 7d65cf078..c363b193b 100644 --- a/lib/pleroma/plugs/http_security_plug.ex +++ b/lib/pleroma/plugs/http_security_plug.ex @@ -108,31 +108,48 @@ defp csp_string do |> :erlang.iolist_to_binary() end + defp build_csp_from_whitelist([], acc), do: acc + + defp build_csp_from_whitelist([last], acc) do + [build_csp_param_from_whitelist(last) | acc] + end + + defp build_csp_from_whitelist([head | tail], acc) do + build_csp_from_whitelist(tail, [[?\s, build_csp_param_from_whitelist(head)] | acc]) + end + + # TODO: use `build_csp_param/1` after removing support bare domains for media proxy whitelist + defp build_csp_param_from_whitelist("http" <> _ = url) do + build_csp_param(url) + end + + defp build_csp_param_from_whitelist(url), do: url + defp build_csp_multimedia_source_list do media_proxy_whitelist = - Enum.reduce(Config.get([:media_proxy, :whitelist]), [], fn host, acc -> - add_source(acc, host) - end) - - media_proxy_base_url = build_csp_param(Config.get([:media_proxy, :base_url])) - - upload_base_url = build_csp_param(Config.get([Pleroma.Upload, :base_url])) - - s3_endpoint = build_csp_param(Config.get([Pleroma.Uploaders.S3, :public_endpoint])) + [:media_proxy, :whitelist] + |> Config.get() + |> build_csp_from_whitelist([]) captcha_method = Config.get([Pleroma.Captcha, :method]) + captcha_endpoint = Config.get([captcha_method, :endpoint]) - captcha_endpoint = build_csp_param(Config.get([captcha_method, :endpoint])) + base_endpoints = + [ + [:media_proxy, :base_url], + [Pleroma.Upload, :base_url], + [Pleroma.Uploaders.S3, :public_endpoint] + ] + |> Enum.map(&Config.get/1) - [] - |> add_source(media_proxy_base_url) - |> add_source(upload_base_url) - |> add_source(s3_endpoint) + [captcha_endpoint | base_endpoints] + |> Enum.map(&build_csp_param/1) + |> Enum.reduce([], &add_source(&2, &1)) |> add_source(media_proxy_whitelist) - |> add_source(captcha_endpoint) end defp add_source(iodata, nil), do: iodata + defp add_source(iodata, []), do: iodata defp add_source(iodata, source), do: [[?\s, source] | iodata] defp add_csp_param(csp_iodata, nil), do: csp_iodata diff --git a/lib/pleroma/plugs/instance_static.ex b/lib/pleroma/plugs/instance_static.ex index 7516f75c3..0fb57e422 100644 --- a/lib/pleroma/plugs/instance_static.ex +++ b/lib/pleroma/plugs/instance_static.ex @@ -16,28 +16,24 @@ def file_path(path) do instance_path = Path.join(Pleroma.Config.get([:instance, :static_dir], "instance/static/"), path) - if File.exists?(instance_path) do - instance_path - else + frontend_path = Pleroma.Plugs.FrontendStatic.file_path(path, :primary) + + (File.exists?(instance_path) && instance_path) || + (frontend_path && File.exists?(frontend_path) && frontend_path) || Path.join(Application.app_dir(:pleroma, "priv/static/"), path) - end end def init(opts) do opts |> Keyword.put(:from, "__unconfigured_instance_static_plug") - |> Keyword.put(:at, "/__unconfigured_instance_static_plug") |> Plug.Static.init() end for only <- Pleroma.Constants.static_only_files() do - at = Plug.Router.Utils.split("/") - def call(%{request_path: "/" <> unquote(only) <> _} = conn, opts) do call_static( conn, opts, - unquote(at), Pleroma.Config.get([:instance, :static_dir], "instance/static") ) end @@ -47,11 +43,10 @@ def call(conn, _) do conn end - defp call_static(conn, opts, at, from) do + defp call_static(conn, opts, from) do opts = opts |> Map.put(:from, from) - |> Map.put(:at, at) Plug.Static.call(conn, opts) end diff --git a/lib/pleroma/plugs/user_is_admin_plug.ex b/lib/pleroma/plugs/user_is_admin_plug.ex index 2748102df..488a61d1d 100644 --- a/lib/pleroma/plugs/user_is_admin_plug.ex +++ b/lib/pleroma/plugs/user_is_admin_plug.ex @@ -7,37 +7,18 @@ defmodule Pleroma.Plugs.UserIsAdminPlug do import Plug.Conn alias Pleroma.User - alias Pleroma.Web.OAuth def init(options) do options end - def call(%{assigns: %{user: %User{is_admin: true}} = assigns} = conn, _) do - token = assigns[:token] - - cond do - not Pleroma.Config.enforce_oauth_admin_scope_usage?() -> - conn - - token && OAuth.Scopes.contains_admin_scopes?(token.scopes) -> - # Note: checking for _any_ admin scope presence, not necessarily fitting requested action. - # Thus, controller must explicitly invoke OAuthScopesPlug to verify scope requirements. - # Admin might opt out of admin scope for some apps to block any admin actions from them. - conn - - true -> - fail(conn) - end + def call(%{assigns: %{user: %User{is_admin: true}}} = conn, _) do + conn end def call(conn, _) do - fail(conn) - end - - defp fail(conn) do conn - |> render_error(:forbidden, "User is not an admin or OAuth admin scope is not granted.") + |> render_error(:forbidden, "User is not an admin.") |> halt() end end 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 1da8952a3..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, :hibernate} - end - - @impl true - def handle_info({:gun_data, _conn, _stream, _, _}, state) do - {:noreply, state, :hibernate} - end - - @impl true - def handle_info({:gun_up, _conn, _protocol}, state) do - {:noreply, state, :hibernate} - end - - @impl true - def handle_info({:gun_down, _conn, _protocol, _reason, _killed}, state) do - {:noreply, state, :hibernate} - end - - @impl true - def handle_info({:gun_error, _conn, _stream, _error}, state) do - {:noreply, state, :hibernate} - end - - @impl true - def handle_info({:gun_push, _conn, _stream, _new_stream, _method, _uri, _headers}, state) do - {:noreply, state, :hibernate} - end - - @impl true - def handle_info({:gun_response, _conn, _stream, _, _status, _headers}, state) do - {:noreply, state, :hibernate} - end - - @impl true - def handle_info(msg, state) do - Logger.warn("Received unexpected message #{inspect(__MODULE__)} #{inspect(msg)}") - {:noreply, state, :hibernate} - 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..d5a339681 100644 --- a/lib/pleroma/reverse_proxy/client/tesla.ex +++ b/lib/pleroma/reverse_proxy/client/tesla.ex @@ -5,6 +5,8 @@ defmodule Pleroma.ReverseProxy.Client.Tesla do @behaviour Pleroma.ReverseProxy.Client + alias Pleroma.Gun.ConnectionPool + @type headers() :: [{String.t(), String.t()}] @type status() :: pos_integer() @@ -31,6 +33,8 @@ def request(method, url, headers, body, opts \\ []) do if is_map(response.body) and method != :head do {:ok, response.status, response.headers, response.body} else + conn_pid = response.opts[:adapter][:conn] + ConnectionPool.release_conn(conn_pid) {:ok, response.status, response.headers} end else @@ -41,15 +45,8 @@ def request(method, url, headers, body, opts \\ []) do @impl true @spec stream_body(map()) :: {:ok, binary(), map()} | {:error, atom() | String.t()} | :done | no_return() - def stream_body(%{pid: pid, opts: opts, fin: true}) do - # if connection was reused, but in tesla were redirects, - # tesla returns new opened connection, which must be closed manually - if opts[:old_conn], do: Tesla.Adapter.Gun.close(pid) - # 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) - + def stream_body(%{pid: pid, fin: true}) do + ConnectionPool.release_conn(pid) :done end @@ -74,8 +71,7 @@ defp read_chunk!(%{pid: pid, stream: stream, opts: opts}) do @impl true @spec close(map) :: :ok | no_return() def close(%{pid: pid}) do - adapter = check_adapter() - adapter.close(pid) + ConnectionPool.release_conn(pid) end defp check_adapter do diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy/reverse_proxy.ex index 4bbeb493c..0de4e2309 100644 --- a/lib/pleroma/reverse_proxy/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy/reverse_proxy.ex @@ -3,12 +3,13 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.ReverseProxy do + @range_headers ~w(range if-range) @keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++ - ~w(if-unmodified-since if-none-match if-range range) + ~w(if-unmodified-since if-none-match) ++ @range_headers @resp_cache_headers ~w(etag date last-modified) @keep_resp_headers @resp_cache_headers ++ - ~w(content-type content-disposition content-encoding content-range) ++ - ~w(accept-ranges vary) + ~w(content-length content-type content-disposition content-encoding) ++ + ~w(content-range accept-ranges vary) @default_cache_control_header "public, max-age=1209600" @valid_resp_codes [200, 206, 304] @max_read_duration :timer.seconds(30) @@ -164,12 +165,17 @@ defp request(method, url, headers, opts) do {:ok, code, _, _} -> {:error, {:invalid_http_response, code}} + {:ok, code, _} -> + {:error, {:invalid_http_response, code}} + {:error, error} -> {:error, error} end end defp response(conn, client, url, status, headers, opts) do + Logger.debug("#{__MODULE__} #{status} #{url} #{inspect(headers)}") + result = conn |> put_resp_headers(build_resp_headers(headers, opts)) @@ -220,7 +226,9 @@ defp chunk_reply(conn, client, opts, sent_so_far, duration) do end end - defp head_response(conn, _url, code, headers, opts) do + defp head_response(conn, url, code, headers, opts) do + Logger.debug("#{__MODULE__} #{code} #{url} #{inspect(headers)}") + conn |> put_resp_headers(build_resp_headers(headers, opts)) |> send_resp(code, "") @@ -262,20 +270,33 @@ defp build_req_headers(headers, opts) do headers |> downcase_headers() |> Enum.filter(fn {k, _} -> k in @keep_req_headers end) - |> (fn headers -> - headers = headers ++ Keyword.get(opts, :req_headers, []) + |> build_req_range_or_encoding_header(opts) + |> build_req_user_agent_header(opts) + |> Keyword.merge(Keyword.get(opts, :req_headers, [])) + end - if Keyword.get(opts, :keep_user_agent, false) do - List.keystore( - headers, - "user-agent", - 0, - {"user-agent", Pleroma.Application.user_agent()} - ) - else - headers - end - end).() + # Disable content-encoding if any @range_headers are requested (see #1823). + defp build_req_range_or_encoding_header(headers, _opts) do + range? = Enum.any?(headers, fn {header, _} -> Enum.member?(@range_headers, header) end) + + if range? && List.keymember?(headers, "accept-encoding", 0) do + List.keydelete(headers, "accept-encoding", 0) + else + headers + end + end + + defp build_req_user_agent_header(headers, opts) do + if Keyword.get(opts, :keep_user_agent, false) do + List.keystore( + headers, + "user-agent", + 0, + {"user-agent", Pleroma.Application.user_agent()} + ) + else + headers + end end defp build_resp_headers(headers, opts) do @@ -283,7 +304,7 @@ defp build_resp_headers(headers, opts) do |> Enum.filter(fn {k, _} -> k in @keep_resp_headers end) |> build_resp_cache_headers(opts) |> build_resp_content_disposition_header(opts) - |> (fn headers -> headers ++ Keyword.get(opts, :resp_headers, []) end).() + |> Keyword.merge(Keyword.get(opts, :resp_headers, [])) end defp build_resp_cache_headers(headers, _opts) do 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/upload/filter/exiftool.ex b/lib/pleroma/upload/filter/exiftool.ex new file mode 100644 index 000000000..c7fb6aefa --- /dev/null +++ b/lib/pleroma/upload/filter/exiftool.ex @@ -0,0 +1,18 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Upload.Filter.Exiftool do + @moduledoc """ + Strips GPS related EXIF tags and overwrites the file in place. + Also strips or replaces filesystem metadata e.g., timestamps. + """ + @behaviour Pleroma.Upload.Filter + + def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do + System.cmd("exiftool", ["-overwrite_original", "-gps:all=", file], parallelism: true) + :ok + end + + def filter(_), do: :ok +end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 0078f9831..dcf6ebee2 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -42,7 +42,12 @@ defmodule Pleroma.User do require Logger @type t :: %__MODULE__{} - @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending + @type account_status :: + :active + | :deactivated + | :password_reset_pending + | :confirmation_pending + | :approval_pending @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true} # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength @@ -106,6 +111,8 @@ defmodule Pleroma.User do field(:locked, :boolean, default: false) field(:confirmation_pending, :boolean, default: false) field(:password_reset_pending, :boolean, default: false) + field(:approval_pending, :boolean, default: false) + field(:registration_reason, :string, default: nil) field(:confirmation_token, :string, default: nil) field(:default_scope, :string, default: "public") field(:domain_blocks, {:array, :string}, default: []) @@ -138,6 +145,7 @@ defmodule Pleroma.User do field(:also_known_as, {:array, :string}, default: []) field(:inbox, :string) field(:shared_inbox, :string) + field(:accepts_chat_messages, :boolean, default: nil) embeds_one( :notification_settings, @@ -261,6 +269,7 @@ def binary_id(%User{} = user), do: binary_id(user.id) @spec account_status(User.t()) :: account_status() def account_status(%User{deactivated: true}), do: :deactivated def account_status(%User{password_reset_pending: true}), do: :password_reset_pending + def account_status(%User{approval_pending: true}), do: :approval_pending def account_status(%User{confirmation_pending: true}) do if Config.get([:instance, :account_activation_required]) do @@ -436,7 +445,8 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do :discoverable, :invisible, :actor_type, - :also_known_as + :also_known_as, + :accepts_chat_messages ] ) |> validate_required([:name, :ap_id]) @@ -481,7 +491,8 @@ def update_changeset(struct, params \\ %{}) do :pleroma_settings_store, :discoverable, :actor_type, - :also_known_as + :also_known_as, + :accepts_chat_messages ] ) |> unique_constraint(:nickname) @@ -527,11 +538,21 @@ defp parse_fields(value) do end defp put_emoji(changeset) do - bio = get_change(changeset, :bio) - name = get_change(changeset, :name) + emojified_fields = [:bio, :name, :raw_fields] + + if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do + bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio)) + name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name)) + + emoji = Map.merge(bio, name) + + emoji = + changeset + |> get_field(:raw_fields) + |> Enum.reduce(emoji, fn x, acc -> + Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"])) + end) - if bio || name do - emoji = Map.merge(Emoji.Formatter.get_emoji_map(bio), Emoji.Formatter.get_emoji_map(name)) put_change(changeset, :emoji, emoji) else changeset @@ -620,6 +641,8 @@ def force_password_reset(user), do: update_password_reset_pending(user, true) def register_changeset(struct, params \\ %{}, opts \\ []) do bio_limit = Config.get([:instance, :user_bio_length], 5000) name_limit = Config.get([:instance, :user_name_length], 100) + reason_limit = Config.get([:instance, :registration_reason_length], 500) + params = Map.put_new(params, :accepts_chat_messages, true) need_confirmation? = if is_nil(opts[:need_confirmation]) do @@ -628,8 +651,16 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do opts[:need_confirmation] end + need_approval? = + if is_nil(opts[:need_approval]) do + Config.get([:instance, :account_approval_required]) + else + opts[:need_approval] + end + struct |> confirmation_changeset(need_confirmation: need_confirmation?) + |> approval_changeset(need_approval: need_approval?) |> cast(params, [ :bio, :raw_bio, @@ -638,7 +669,9 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do :nickname, :password, :password_confirmation, - :emoji + :emoji, + :accepts_chat_messages, + :registration_reason ]) |> validate_required([:name, :nickname, :password, :password_confirmation]) |> validate_confirmation(:password) @@ -649,6 +682,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do |> validate_format(:email, @email_regex) |> validate_length(:bio, max: bio_limit) |> validate_length(:name, min: 1, max: name_limit) + |> validate_length(:registration_reason, max: reason_limit) |> maybe_validate_required_email(opts[:external]) |> put_password_hash |> put_ap_id() @@ -698,27 +732,52 @@ def register(%Ecto.Changeset{} = changeset) do def post_register_action(%User{} = user) do with {:ok, user} <- autofollow_users(user), {:ok, user} <- set_cache(user), - {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user), + {:ok, _} <- send_welcome_email(user), + {:ok, _} <- send_welcome_message(user), {:ok, _} <- try_send_confirmation_email(user) do {:ok, user} 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() - + def send_welcome_message(user) do + if User.WelcomeMessage.enabled?() do + User.WelcomeMessage.post_message(user) {:ok, :enqueued} else {:ok, :noop} end end - def try_send_confirmation_email(users) do - Enum.each(users, &try_send_confirmation_email/1) + def send_welcome_email(%User{email: email} = user) when is_binary(email) do + if User.WelcomeEmail.enabled?() do + User.WelcomeEmail.send_email(user) + {:ok, :enqueued} + else + {:ok, :noop} + end + end + + def send_welcome_email(_), do: {:ok, :noop} + + @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(_), 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 @@ -1454,6 +1513,19 @@ def deactivate(%User{} = user, status) do end end + def approve(users) when is_list(users) do + Repo.transaction(fn -> + Enum.map(users, fn user -> + with {:ok, user} <- approve(user), do: user + end) + end) + end + + def approve(%User{} = user) do + change(user, approval_pending: false) + |> update_and_set_cache() + end + def update_notification_settings(%User{} = user, settings) do user |> cast(%{notification_settings: settings}, []) @@ -1480,12 +1552,17 @@ defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate defp delete_or_deactivate(%User{local: true} = user) do status = account_status(user) - if status == :confirmation_pending do - delete_and_invalidate_cache(user) - else - user - |> change(%{deactivated: true, email: nil}) - |> update_and_set_cache() + case status do + :confirmation_pending -> + delete_and_invalidate_cache(user) + + :approval_pending -> + delete_and_invalidate_cache(user) + + _ -> + user + |> change(%{deactivated: true, email: nil}) + |> update_and_set_cache() end end @@ -2138,6 +2215,12 @@ def confirmation_changeset(user, need_confirmation: need_confirmation?) do cast(user, params, [:confirmation_pending, :confirmation_token]) end + @spec approval_changeset(User.t(), keyword()) :: Changeset.t() + def approval_changeset(user, need_approval: need_approval?) do + params = if need_approval?, do: %{approval_pending: true}, else: %{approval_pending: false} + cast(user, params, [:approval_pending]) + end + def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do if id not in user.pinned_activities do max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0) 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/user/query.ex b/lib/pleroma/user/query.ex index 66ffe9090..45553cb6c 100644 --- a/lib/pleroma/user/query.ex +++ b/lib/pleroma/user/query.ex @@ -42,6 +42,7 @@ defmodule Pleroma.User.Query do external: boolean(), active: boolean(), deactivated: boolean(), + need_approval: boolean(), is_admin: boolean(), is_moderator: boolean(), super_users: boolean(), @@ -146,6 +147,10 @@ defp compose_query({:deactivated, true}, query) do |> where([u], not is_nil(u.nickname)) end + defp compose_query({:need_approval, _}, query) do + where(query, [u], u.approval_pending) + end + defp compose_query({:followers, %User{id: id}}, query) do query |> where([u], u.id != ^id) diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index 42ff1de78..d4fd31069 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -69,11 +69,15 @@ defp fts_search(query, query_string) do u in query, where: fragment( + # The fragment must _exactly_ match `users_fts_index`, otherwise the index won't work """ - (to_tsvector('simple', ?) || to_tsvector('simple', ?)) @@ to_tsquery('simple', ?) + ( + setweight(to_tsvector('simple', regexp_replace(?, '\\W', ' ', 'g')), 'A') || + setweight(to_tsvector('simple', regexp_replace(coalesce(?, ''), '\\W', ' ', 'g')), 'B') + ) @@ to_tsquery('simple', ?) """, - u.name, u.nickname, + u.name, ^query_string ) ) @@ -88,15 +92,23 @@ defp to_tsquery(query_string) do |> Enum.join(" | ") end + # Considers nickname match, localized nickname match, name match; preferences nickname match defp trigram_rank(query, query_string) do from( u in query, select_merge: %{ search_rank: fragment( - "similarity(?, trim(? || ' ' || coalesce(?, '')))", + """ + similarity(?, ?) + + similarity(?, regexp_replace(?, '@.+', '')) + + similarity(?, trim(coalesce(?, ''))) + """, ^query_string, u.nickname, + ^query_string, + u.nickname, + ^query_string, u.name ) } diff --git a/lib/pleroma/user/welcome_email.ex b/lib/pleroma/user/welcome_email.ex new file mode 100644 index 000000000..5322000d4 --- /dev/null +++ b/lib/pleroma/user/welcome_email.ex @@ -0,0 +1,62 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.User.WelcomeEmail do + @moduledoc """ + The module represents the functions to send welcome email. + """ + + alias Pleroma.Config + alias Pleroma.Emails + alias Pleroma.User + + import Pleroma.Config.Helpers, only: [instance_name: 0] + + @spec enabled?() :: boolean() + def enabled?, do: Config.get([:welcome, :email, :enabled], false) + + @spec send_email(User.t()) :: {:ok, Oban.Job.t()} + def send_email(%User{} = user) do + user + |> Emails.UserEmail.welcome(email_options(user)) + |> Emails.Mailer.deliver_async() + end + + defp email_options(user) do + bindings = [user: user, instance_name: instance_name()] + + %{} + |> add_sender(Config.get([:welcome, :email, :sender], nil)) + |> add_option(:subject, bindings) + |> add_option(:html, bindings) + |> add_option(:text, bindings) + end + + defp add_option(opts, option, bindings) do + [:welcome, :email, option] + |> Config.get(nil) + |> eval_string(bindings) + |> merge_options(opts, option) + end + + defp add_sender(opts, {_name, _email} = sender) do + merge_options(sender, opts, :sender) + end + + defp add_sender(opts, sender) when is_binary(sender) do + add_sender(opts, {instance_name(), sender}) + end + + defp add_sender(opts, _), do: opts + + defp merge_options(nil, options, _option), do: options + + defp merge_options(value, options, option) do + Map.merge(options, %{option => value}) + end + + defp eval_string(nil, _), do: nil + defp eval_string("", _), do: nil + defp eval_string(str, bindings), do: EEx.eval_string(str, bindings) +end diff --git a/lib/pleroma/user/welcome_message.ex b/lib/pleroma/user/welcome_message.ex index f8f520285..86e1c0678 100644 --- a/lib/pleroma/user/welcome_message.ex +++ b/lib/pleroma/user/welcome_message.ex @@ -3,32 +3,45 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.User.WelcomeMessage do + alias Pleroma.Config alias Pleroma.User alias Pleroma.Web.CommonAPI - def post_welcome_message_to_user(user) do - with %User{} = sender_user <- welcome_user(), - message when is_binary(message) <- welcome_message() do - CommonAPI.post(sender_user, %{ - visibility: "direct", - status: "@#{user.nickname}\n#{message}" - }) - else - _ -> {:ok, nil} - end + @spec enabled?() :: boolean() + def enabled?, do: Config.get([:welcome, :direct_message, :enabled], false) + + @spec post_message(User.t()) :: {:ok, Pleroma.Activity.t() | nil} + def post_message(user) do + [:welcome, :direct_message, :sender_nickname] + |> Config.get(nil) + |> fetch_sender() + |> do_post(user, welcome_message()) end - defp welcome_user do - with nickname when is_binary(nickname) <- - Pleroma.Config.get([:instance, :welcome_user_nickname]), - %User{local: true} = user <- User.get_cached_by_nickname(nickname) do + defp do_post(%User{} = sender, %User{nickname: nickname}, message) + when is_binary(message) do + CommonAPI.post( + sender, + %{ + visibility: "direct", + status: "@#{nickname}\n#{message}" + } + ) + end + + defp do_post(_sender, _recipient, _message), do: {:ok, nil} + + defp fetch_sender(nickname) when is_binary(nickname) do + with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do user else _ -> nil end end + defp fetch_sender(_), do: nil + defp welcome_message do - Pleroma.Config.get([:instance, :welcome_message]) + Config.get([:welcome, :direct_message, :message], nil) end end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 1c2908805..a4db1d87c 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1226,6 +1226,8 @@ defp object_to_user_data(data) do end) locked = data["manuallyApprovesFollowers"] || false + capabilities = data["capabilities"] || %{} + accepts_chat_messages = capabilities["acceptsChatMessages"] data = Transmogrifier.maybe_fix_user_object(data) discoverable = data["discoverable"] || false invisible = data["invisible"] || false @@ -1264,7 +1266,8 @@ defp object_to_user_data(data) do also_known_as: Map.get(data, "alsoKnownAs", []), public_key: public_key, inbox: data["inbox"], - shared_inbox: shared_inbox + shared_inbox: shared_inbox, + accepts_chat_messages: accepts_chat_messages } # nickname can be nil because of virtual actors @@ -1367,19 +1370,38 @@ 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} end end - def maybe_handle_clashing_nickname(nickname) do - with %User{} = old_user <- User.get_by_nickname(nickname) do - Logger.info("Found an old user for #{nickname}, ap id is #{old_user.ap_id}, renaming.") + def maybe_handle_clashing_nickname(data) do + nickname = data[:nickname] + + with %User{} = old_user <- User.get_by_nickname(nickname), + {_, false} <- {:ap_id_comparison, data[:ap_id] == old_user.ap_id} do + Logger.info( + "Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{ + data[:ap_id] + }, renaming." + ) old_user |> User.remote_user_changeset(%{nickname: "#{old_user.id}.#{old_user.nickname}"}) |> User.update_and_set_cache() + else + {:ap_id_comparison, true} -> + Logger.info( + "Found an old user for #{nickname}, but the ap id #{data[:ap_id]} is the same as the new user. Race condition? Not changing anything." + ) + + _ -> + nil end end @@ -1395,7 +1417,7 @@ def make_user_from_ap_id(ap_id) do |> User.remote_user_changeset(data) |> User.update_and_set_cache() else - maybe_handle_clashing_nickname(data[:nickname]) + maybe_handle_clashing_nickname(data) data |> User.remote_user_changeset() 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/ensure_re_prepended.ex b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex index 2627a0007..3bf70b894 100644 --- a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex +++ b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex @@ -27,7 +27,8 @@ def filter_by_summary( def filter_by_summary(_in_reply_to, child), do: child - def filter(%{"type" => "Create", "object" => child_object} = object) do + def filter(%{"type" => "Create", "object" => child_object} = object) + when is_map(child_object) do child = child_object["inReplyTo"] |> Object.normalize(child_object["inReplyTo"]) 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/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index df926829c..0dcc7be4d 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do the system. """ + alias Pleroma.Activity alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Object alias Pleroma.User @@ -71,6 +72,12 @@ def validate(%{"type" => "Undo"} = object, meta) do |> UndoValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do object = stringify_keys(object) + undone_object = Activity.get_by_ap_id(object["object"]) + + meta = + meta + |> Keyword.put(:object_data, undone_object.data) + {:ok, object, meta} end end diff --git a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex index c481d79e0..91b475393 100644 --- a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex @@ -93,12 +93,14 @@ def validate_content_or_attachment(cng) do - If both users are in our system - If at least one of the users in this ChatMessage is a local user - If the recipient is not blocking the actor + - If the recipient is explicitly not accepting chat messages """ def validate_local_concern(cng) do with actor_ap <- get_field(cng, :actor), {_, %User{} = actor} <- {:find_actor, User.get_cached_by_ap_id(actor_ap)}, {_, %User{} = recipient} <- {:find_recipient, User.get_cached_by_ap_id(get_field(cng, :to) |> hd())}, + {_, false} <- {:not_accepting_chats?, recipient.accepts_chat_messages == false}, {_, false} <- {:blocking_actor?, User.blocks?(recipient, actor)}, {_, true} <- {:local?, Enum.any?([actor, recipient], & &1.local)} do cng @@ -107,6 +109,10 @@ def validate_local_concern(cng) do cng |> add_error(:actor, "actor is blocked by recipient") + {:not_accepting_chats?, true} -> + cng + |> add_error(:to, "recipient does not accept chat messages") + {:local?, false} -> cng |> add_error(:actor, "actor and recipient are both remote") diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex index 6875c47f6..36e325c37 100644 --- a/lib/pleroma/web/activity_pub/pipeline.ex +++ b/lib/pleroma/web/activity_pub/pipeline.ex @@ -52,6 +52,13 @@ defp maybe_federate(%Activity{} = activity, meta) do do_not_federate = meta[:do_not_federate] || !Config.get([:instance, :federating]) if !do_not_federate && local do + activity = + if object = Keyword.get(meta, :object_data) do + %{activity | data: Map.put(activity.data, "object", object)} + else + activity + end + Federator.publish(activity) {:ok, :federated} else 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 884646ceb..35aa05eb5 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -62,15 +62,17 @@ def fix_summary(%{"summary" => _} = object) do def fix_summary(object), do: Map.put(object, "summary", "") def fix_addressing_list(map, field) do - cond do - is_binary(map[field]) -> - Map.put(map, field, [map[field]]) + addrs = map[field] - is_nil(map[field]) -> - Map.put(map, field, []) + cond do + is_list(addrs) -> + Map.put(map, field, Enum.filter(addrs, &is_binary/1)) + + is_binary(addrs) -> + Map.put(map, field, [addrs]) true -> - map + Map.put(map, field, []) end end @@ -176,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/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 4a02b09a1..3a4564912 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -81,6 +81,15 @@ def render("user.json", %{user: user}) do fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue")) + capabilities = + if is_boolean(user.accepts_chat_messages) do + %{ + "acceptsChatMessages" => user.accepts_chat_messages + } + else + %{} + end + %{ "id" => user.ap_id, "type" => user.actor_type, @@ -101,7 +110,8 @@ def render("user.json", %{user: user}) do "endpoints" => endpoints, "attachment" => fields, "tag" => emoji_tags, - "discoverable" => user.discoverable + "discoverable" => user.discoverable, + "capabilities" => capabilities } |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user)) |> Map.merge(maybe_make_image(&User.banner_url/2, "image", user)) 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 f9545d895..aa2af1ab5 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -44,6 +44,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do :user_toggle_activation, :user_activate, :user_deactivate, + :user_approve, :tag_users, :untag_users, :right_add, @@ -206,8 +207,8 @@ def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do end end - def user_show(conn, %{"nickname" => nickname}) do - with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do + def user_show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do + with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do conn |> put_view(AccountView) |> render("show.json", %{user: user}) @@ -233,11 +234,11 @@ def list_instance_statuses(conn, %{"instance" => instance} = params) do |> render("index.json", %{activities: activities, as: :activity}) end - def list_user_statuses(conn, %{"nickname" => nickname} = params) do + def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true godmode = params["godmode"] == "true" || params["godmode"] == true - with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do + with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do {_, page_size} = page_params(params) activities = @@ -303,6 +304,21 @@ def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nickname |> render("index.json", %{users: Keyword.values(updated_users)}) end + def user_approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do + users = Enum.map(nicknames, &User.get_cached_by_nickname/1) + {:ok, updated_users} = User.approve(users) + + ModerationLog.insert_log(%{ + actor: admin, + subject: users, + action: "approve" + }) + + conn + |> put_view(AccountView) + |> render("index.json", %{users: updated_users}) + end + def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do with {:ok, _} <- User.tag(nicknames, tags) do ModerationLog.insert_log(%{ @@ -345,12 +361,16 @@ 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 - @filters ~w(local external active deactivated is_admin is_moderator) + @filters ~w(local external active deactivated need_approval is_admin is_moderator) @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{} defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{} @@ -526,7 +546,7 @@ def disable_mfa(conn, %{"nickname" => nickname}) do @doc "Show a given user's credentials" def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do - with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do + with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do conn |> put_view(AccountView) |> render("credentials.json", %{user: user, for: admin}) @@ -616,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/controllers/config_controller.ex b/lib/pleroma/web/admin_api/controllers/config_controller.ex index 7f60470cb..0df13007f 100644 --- a/lib/pleroma/web/admin_api/controllers/config_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/config_controller.ex @@ -9,8 +9,6 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do alias Pleroma.ConfigDB alias Pleroma.Plugs.OAuthScopesPlug - @descriptions Pleroma.Docs.JSON.compile() - plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :update) @@ -25,7 +23,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ConfigOperation def descriptions(conn, _params) do - descriptions = Enum.filter(@descriptions, &whitelisted_config?/1) + descriptions = Enum.filter(Pleroma.Docs.JSON.compiled_descriptions(), &whitelisted_config?/1) json(conn, descriptions) end diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex index e1e929632..333e72e42 100644 --- a/lib/pleroma/web/admin_api/views/account_view.ex +++ b/lib/pleroma/web/admin_api/views/account_view.ex @@ -77,7 +77,9 @@ def render("show.json", %{user: user}) do "roles" => User.roles(user), "tags" => user.tags || [], "confirmation_pending" => user.confirmation_pending, - "url" => user.uri || user.ap_id + "approval_pending" => user.approval_pending, + "url" => user.uri || user.ap_id, + "registration_reason" => user.registration_reason } end @@ -105,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/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex index a258e8421..2a7f1a706 100644 --- a/lib/pleroma/web/api_spec/helpers.ex +++ b/lib/pleroma/web/api_spec/helpers.ex @@ -29,6 +29,10 @@ def request_body(description, schema_ref, opts \\ []) do } end + def admin_api_params do + [Operation.parameter(:admin_token, :query, :string, "Allows authorization via admin token.")] + end + def pagination_params do [ Operation.parameter(:max_id, :query, :string, "Return items older than this ID"), diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index b8c527606..aaebc9b5c 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -61,7 +61,7 @@ def update_credentials_operation do description: "Update the user's display and preferences.", operationId: "AccountController.update_credentials", security: [%{"oAuth" => ["write:accounts"]}], - requestBody: request_body("Parameters", update_creadentials_request(), required: true), + requestBody: request_body("Parameters", update_credentials_request(), required: true), responses: %{ 200 => Operation.response("Account", "application/json", Account), 403 => Operation.response("Error", "application/json", ApiError) @@ -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())} @@ -447,21 +449,32 @@ defp create_request do } end - # TODO: This is actually a token respone, but there's no oauth operation file yet. + # Note: this is a token response (if login succeeds!), but there's no oauth operation file yet. defp create_response do %Schema{ title: "AccountCreateResponse", description: "Response schema for an account", type: :object, properties: %{ + # The response when auto-login on create succeeds (token is issued): token_type: %Schema{type: :string}, access_token: %Schema{type: :string}, refresh_token: %Schema{type: :string}, scope: %Schema{type: :string}, created_at: %Schema{type: :integer, format: :"date-time"}, me: %Schema{type: :string}, - expires_in: %Schema{type: :integer} + expires_in: %Schema{type: :integer}, + # + # The response when registration succeeds but auto-login fails (no token): + identifier: %Schema{type: :string}, + message: %Schema{type: :string} }, + required: [], + # Note: example of successful registration with failed login response: + # example: %{ + # "identifier" => "missing_confirmed_email", + # "message" => "You have been registered. Please check your email for further instructions." + # }, example: %{ "token_type" => "Bearer", "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk", @@ -474,7 +487,7 @@ defp create_response do } end - defp update_creadentials_request do + defp update_credentials_request do %Schema{ title: "AccountUpdateCredentialsRequest", description: "POST body for creating an account", @@ -508,6 +521,11 @@ defp update_creadentials_request do nullable: true, description: "Whether manual approval of follow requests is required." }, + accepts_chat_messages: %Schema{ + allOf: [BooleanLike], + nullable: true, + description: "Whether the user accepts receiving chat messages." + }, fields_attributes: %Schema{ nullable: true, oneOf: [ diff --git a/lib/pleroma/web/api_spec/operations/admin/config_operation.ex b/lib/pleroma/web/api_spec/operations/admin/config_operation.ex index 7b38a2ef4..3a8380797 100644 --- a/lib/pleroma/web/api_spec/operations/admin/config_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/config_operation.ex @@ -26,6 +26,7 @@ def show_operation do %Schema{type: :boolean, default: false}, "Get only saved in database settings" ) + | admin_api_params() ], security: [%{"oAuth" => ["read"]}], responses: %{ @@ -41,6 +42,7 @@ def update_operation do summary: "Update config settings", operationId: "AdminAPI.ConfigController.update", security: [%{"oAuth" => ["write"]}], + parameters: admin_api_params(), requestBody: request_body("Parameters", %Schema{ type: :object, @@ -73,6 +75,7 @@ def descriptions_operation do summary: "Get JSON with config descriptions.", operationId: "AdminAPI.ConfigController.descriptions", security: [%{"oAuth" => ["read"]}], + parameters: admin_api_params(), responses: %{ 200 => Operation.response("Config Descriptions", "application/json", %Schema{ diff --git a/lib/pleroma/web/api_spec/operations/admin/invite_operation.ex b/lib/pleroma/web/api_spec/operations/admin/invite_operation.ex index d3af9db49..801024d75 100644 --- a/lib/pleroma/web/api_spec/operations/admin/invite_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/invite_operation.ex @@ -20,6 +20,7 @@ def index_operation do summary: "Get a list of generated invites", operationId: "AdminAPI.InviteController.index", security: [%{"oAuth" => ["read:invites"]}], + parameters: admin_api_params(), responses: %{ 200 => Operation.response("Invites", "application/json", %Schema{ @@ -51,6 +52,7 @@ def create_operation do summary: "Create an account registration invite token", operationId: "AdminAPI.InviteController.create", security: [%{"oAuth" => ["write:invites"]}], + parameters: admin_api_params(), requestBody: request_body("Parameters", %Schema{ type: :object, @@ -71,6 +73,7 @@ def revoke_operation do summary: "Revoke invite by token", operationId: "AdminAPI.InviteController.revoke", security: [%{"oAuth" => ["write:invites"]}], + parameters: admin_api_params(), requestBody: request_body( "Parameters", @@ -97,6 +100,7 @@ def email_operation do summary: "Sends registration invite via email", operationId: "AdminAPI.InviteController.email", security: [%{"oAuth" => ["write:invites"]}], + parameters: admin_api_params(), requestBody: request_body( "Parameters", diff --git a/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex b/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex index 0358cfbad..20d033f66 100644 --- a/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex @@ -33,6 +33,7 @@ def index_operation do %Schema{type: :integer, default: 50}, "Number of statuses to return" ) + | admin_api_params() ], responses: %{ 200 => success_response() @@ -46,6 +47,7 @@ def delete_operation do summary: "Remove a banned MediaProxy URL from Cachex", operationId: "AdminAPI.MediaProxyCacheController.delete", security: [%{"oAuth" => ["write:media_proxy_caches"]}], + parameters: admin_api_params(), requestBody: request_body( "Parameters", @@ -71,6 +73,7 @@ def purge_operation do summary: "Purge and optionally ban a MediaProxy URL", operationId: "AdminAPI.MediaProxyCacheController.purge", security: [%{"oAuth" => ["write:media_proxy_caches"]}], + parameters: admin_api_params(), requestBody: request_body( "Parameters", diff --git a/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex b/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex index fbc9f80d7..a75f3e622 100644 --- a/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex @@ -36,6 +36,7 @@ def index_operation do %Schema{type: :integer, default: 50}, "Number of apps to return" ) + | admin_api_params() ], responses: %{ 200 => @@ -72,6 +73,7 @@ def create_operation do summary: "Create OAuth App", operationId: "AdminAPI.OAuthAppController.create", requestBody: request_body("Parameters", create_request()), + parameters: admin_api_params(), security: [%{"oAuth" => ["write"]}], responses: %{ 200 => Operation.response("App", "application/json", oauth_app()), @@ -85,7 +87,7 @@ def update_operation do tags: ["Admin", "oAuth Apps"], summary: "Update OAuth App", operationId: "AdminAPI.OAuthAppController.update", - parameters: [id_param()], + parameters: [id_param() | admin_api_params()], security: [%{"oAuth" => ["write"]}], requestBody: request_body("Parameters", update_request()), responses: %{ @@ -103,7 +105,7 @@ def delete_operation do tags: ["Admin", "oAuth Apps"], summary: "Delete OAuth App", operationId: "AdminAPI.OAuthAppController.delete", - parameters: [id_param()], + parameters: [id_param() | admin_api_params()], security: [%{"oAuth" => ["write"]}], responses: %{ 204 => no_content_response(), diff --git a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex index 7672cb467..67ee5eee0 100644 --- a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex @@ -19,6 +19,7 @@ def index_operation do summary: "List Relays", operationId: "AdminAPI.RelayController.index", security: [%{"oAuth" => ["read"]}], + parameters: admin_api_params(), responses: %{ 200 => Operation.response("Response", "application/json", %Schema{ @@ -41,6 +42,7 @@ def follow_operation do summary: "Follow a Relay", operationId: "AdminAPI.RelayController.follow", security: [%{"oAuth" => ["write:follows"]}], + parameters: admin_api_params(), requestBody: request_body("Parameters", %Schema{ type: :object, @@ -64,6 +66,7 @@ def unfollow_operation do summary: "Unfollow a Relay", operationId: "AdminAPI.RelayController.unfollow", security: [%{"oAuth" => ["write:follows"]}], + parameters: admin_api_params(), requestBody: request_body("Parameters", %Schema{ type: :object, diff --git a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex index 15e78bfaf..3bb7ec49e 100644 --- a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex @@ -48,6 +48,7 @@ def index_operation do %Schema{type: :integer, default: 50}, "Number number of log entries per page" ) + | admin_api_params() ], responses: %{ 200 => @@ -71,7 +72,7 @@ def show_operation do tags: ["Admin", "Reports"], summary: "Get an individual report", operationId: "AdminAPI.ReportController.show", - parameters: [id_param()], + parameters: [id_param() | admin_api_params()], security: [%{"oAuth" => ["read:reports"]}], responses: %{ 200 => Operation.response("Report", "application/json", report()), @@ -86,6 +87,7 @@ def update_operation do summary: "Change the state of one or multiple reports", operationId: "AdminAPI.ReportController.update", security: [%{"oAuth" => ["write:reports"]}], + parameters: admin_api_params(), requestBody: request_body("Parameters", update_request(), required: true), responses: %{ 204 => no_content_response(), @@ -100,7 +102,7 @@ def notes_create_operation do tags: ["Admin", "Reports"], summary: "Create report note", operationId: "AdminAPI.ReportController.notes_create", - parameters: [id_param()], + parameters: [id_param() | admin_api_params()], requestBody: request_body("Parameters", %Schema{ type: :object, @@ -124,6 +126,7 @@ def notes_delete_operation do parameters: [ Operation.parameter(:report_id, :path, :string, "Report ID"), Operation.parameter(:id, :path, :string, "Note ID") + | admin_api_params() ], security: [%{"oAuth" => ["write:reports"]}], responses: %{ diff --git a/lib/pleroma/web/api_spec/operations/admin/status_operation.ex b/lib/pleroma/web/api_spec/operations/admin/status_operation.ex index 745399b4b..c105838a4 100644 --- a/lib/pleroma/web/api_spec/operations/admin/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/status_operation.ex @@ -55,6 +55,7 @@ def index_operation do %Schema{type: :integer, default: 50}, "Number of statuses to return" ) + | admin_api_params() ], responses: %{ 200 => @@ -71,7 +72,7 @@ def show_operation do tags: ["Admin", "Statuses"], summary: "Show Status", operationId: "AdminAPI.StatusController.show", - parameters: [id_param()], + parameters: [id_param() | admin_api_params()], security: [%{"oAuth" => ["read:statuses"]}], responses: %{ 200 => Operation.response("Status", "application/json", status()), @@ -85,7 +86,7 @@ def update_operation do tags: ["Admin", "Statuses"], summary: "Change the scope of an individual reported status", operationId: "AdminAPI.StatusController.update", - parameters: [id_param()], + parameters: [id_param() | admin_api_params()], security: [%{"oAuth" => ["write:statuses"]}], requestBody: request_body("Parameters", update_request(), required: true), responses: %{ @@ -100,7 +101,7 @@ def delete_operation do tags: ["Admin", "Statuses"], summary: "Delete an individual reported status", operationId: "AdminAPI.StatusController.delete", - parameters: [id_param()], + parameters: [id_param() | admin_api_params()], security: [%{"oAuth" => ["write:statuses"]}], responses: %{ 200 => empty_object_response(), 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 e6f163cb7..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, @@ -103,6 +100,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do description: "A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`" }, + accepts_chat_messages: %Schema{type: :boolean, nullable: true}, favicon: %Schema{ type: :string, format: :uri, @@ -175,16 +173,14 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do "is_admin" => false, "is_moderator" => false, "skip_thread_containment" => false, + "accepts_chat_messages" => true, "chat_token" => "SFMyNTY.g3QAAAACZAAEZGF0YW0AAAASOXRLaTNlc2JHN09RZ1oyOTIwZAAGc2lnbmVkbgYARNplS3EB.Mb_Iaqew2bN1I1o79B_iP7encmVCpTKC4OtHZRxdjKc", "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/api_spec/schemas/chat_message.ex b/lib/pleroma/web/api_spec/schemas/chat_message.ex index 3ee85aa76..bbf2a4427 100644 --- a/lib/pleroma/web/api_spec/schemas/chat_message.ex +++ b/lib/pleroma/web/api_spec/schemas/chat_message.ex @@ -19,13 +19,46 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do content: %Schema{type: :string, nullable: true}, created_at: %Schema{type: :string, format: :"date-time"}, emojis: %Schema{type: :array}, - attachment: %Schema{type: :object, nullable: true} + attachment: %Schema{type: :object, nullable: true}, + card: %Schema{ + type: :object, + nullable: true, + description: "Preview card for links included within status content", + required: [:url, :title, :description, :type], + properties: %{ + type: %Schema{ + type: :string, + enum: ["link", "photo", "video", "rich"], + description: "The type of the preview card" + }, + provider_name: %Schema{ + type: :string, + nullable: true, + description: "The provider of the original resource" + }, + provider_url: %Schema{ + type: :string, + format: :uri, + description: "A link to the provider of the original resource" + }, + url: %Schema{type: :string, format: :uri, description: "Location of linked resource"}, + image: %Schema{ + type: :string, + nullable: true, + format: :uri, + description: "Preview thumbnail" + }, + title: %Schema{type: :string, description: "Title of linked resource"}, + description: %Schema{type: :string, description: "Description of preview"} + } + } }, example: %{ "account_id" => "someflakeid", "chat_id" => "1", "content" => "hey you again", "created_at" => "2020-04-21T15:06:45.000Z", + "card" => nil, "emojis" => [ %{ "static_url" => "https://dontbulling.me/emoji/Firefox.gif", 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/endpoint.ex b/lib/pleroma/web/endpoint.ex index 226d42c2c..527fb288d 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -28,6 +28,17 @@ defmodule Pleroma.Web.Endpoint do } ) + # Careful! No `only` restriction here, as we don't know what frontends contain. + plug(Pleroma.Plugs.FrontendStatic, + at: "/", + frontend_type: :primary, + gzip: true, + cache_control_for_etags: @static_cache_control, + headers: %{ + "cache-control" => @static_cache_control + } + ) + # Serve at "/" the static files from "priv/static" directory. # # You should set gzip to true if you are running phoenix.digest diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex index d56f43818..9cd334a33 100644 --- a/lib/pleroma/web/feed/user_controller.ex +++ b/lib/pleroma/web/feed/user_controller.ex @@ -47,7 +47,7 @@ def feed(conn, %{"nickname" => nickname} = params) do "atom" end - with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do + with {_, %User{local: true} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do activities = %{ type: ["Create"], @@ -71,6 +71,7 @@ def errors(conn, {:error, :not_found}) do render_error(conn, :not_found, "Not found") end + def errors(conn, {:fetch_user, %User{local: false}}), do: errors(conn, {:error, :not_found}) def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found}) def errors(conn, _) do diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 743442855..f45678184 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -27,8 +27,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do alias Pleroma.Web.MastodonAPI.MastodonAPI alias Pleroma.Web.MastodonAPI.MastodonAPIController alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.OAuth.OAuthController alias Pleroma.Web.OAuth.OAuthView - alias Pleroma.Web.OAuth.Token alias Pleroma.Web.TwitterAPI.TwitterAPI plug(Pleroma.Web.ApiSpec.CastAndValidate) @@ -100,11 +100,34 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do with :ok <- validate_email_param(params), :ok <- TwitterAPI.validate_captcha(app, params), - {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true), - {:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do + {:ok, user} <- TwitterAPI.register_user(params), + {_, {:ok, token}} <- + {:login, OAuthController.login(user, app, app.scopes)} do json(conn, OAuthView.render("token.json", %{user: user, token: token})) else - {:error, error} -> json_response(conn, :bad_request, %{error: error}) + {:login, {:account_status, :confirmation_pending}} -> + json_response(conn, :ok, %{ + message: "You have been registered. Please check your email for further instructions.", + identifier: "missing_confirmed_email" + }) + + {:login, {:account_status, :approval_pending}} -> + json_response(conn, :ok, %{ + message: + "You have been registered. You'll be able to log in once your account is approved.", + identifier: "awaiting_approval" + }) + + {:login, _} -> + json_response(conn, :ok, %{ + message: + "You have been registered. Some post-registration steps may be pending. " <> + "Please log in manually.", + identifier: "manual_login_required" + }) + + {:error, error} -> + json_response(conn, :bad_request, %{error: error}) end end @@ -163,7 +186,8 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p :show_role, :skip_thread_containment, :allow_following_move, - :discoverable + :discoverable, + :accepts_chat_messages ] |> Enum.reduce(%{}, fn key, acc -> Maps.put_if_present(acc, key, params[key], &{:ok, truthy_param?(&1)}) 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..ecfa38489 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) @@ -309,7 +314,8 @@ def card(%{assigns: %{user: user}} = conn, %{id: status_id}) do @doc "GET /api/v1/statuses/:id/favourited_by" def favourited_by(%{assigns: %{user: user}} = conn, %{id: id}) do - with %Activity{} = activity <- Activity.get_by_id_with_object(id), + with true <- Pleroma.Config.get([:instance, :show_reactions]), + %Activity{} = activity <- Activity.get_by_id_with_object(id), {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, %Object{data: %{"likes" => likes}} <- Object.normalize(activity) do users = diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 2feba4778..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), @@ -258,6 +277,7 @@ defp do_render("show.json", %{user: user} = opts) do relationship: relationship, skip_thread_containment: user.skip_thread_containment, background_image: image_url(user.background) |> MediaProxy.url(), + accepts_chat_messages: user.accepts_chat_messages, favicon: favicon } } 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 5deb0d7ed..ea2d3aa9c 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -26,6 +26,7 @@ def render("show.json", _) do thumbnail: Keyword.get(instance, :instance_thumbnail), languages: ["en"], registrations: Keyword.get(instance, :registrations_open), + approval_required: Keyword.get(instance, :account_approval_required), # Extra (not present in Mastodon): max_toot_chars: Keyword.get(instance, :limit), poll_limits: Keyword.get(instance, :poll_limits), @@ -41,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/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex index 6f35826da..dfbfcea6b 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy/media_proxy.ex @@ -60,22 +60,28 @@ defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url()) defp whitelisted?(url) do %{host: domain} = URI.parse(url) - mediaproxy_whitelist = Config.get([:media_proxy, :whitelist]) + mediaproxy_whitelist_domains = + [:media_proxy, :whitelist] + |> Config.get() + |> Enum.map(&maybe_get_domain_from_url/1) - upload_base_url_domain = - if !is_nil(Config.get([Upload, :base_url])) do - [URI.parse(Config.get([Upload, :base_url])).host] + whitelist_domains = + if base_url = Config.get([Upload, :base_url]) do + %{host: base_domain} = URI.parse(base_url) + [base_domain | mediaproxy_whitelist_domains] else - [] + mediaproxy_whitelist_domains end - whitelist = mediaproxy_whitelist ++ upload_base_url_domain - - Enum.any?(whitelist, fn pattern -> - String.equivalent?(domain, pattern) - end) + domain in whitelist_domains end + defp maybe_get_domain_from_url("http" <> _ = url) do + URI.parse(url).host + end + + defp maybe_get_domain_from_url(domain), do: domain + def encode_url(url) do base64 = Base.url_encode64(url, @base64_opts) diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 7683589cf..f29b3cb57 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -260,11 +260,8 @@ def token_exchange( ) do with {:ok, %User{} = user} <- Authenticator.get_user(conn), {:ok, app} <- Token.Utils.fetch_app(conn), - {:account_status, :active} <- {:account_status, User.account_status(user)}, - {:ok, scopes} <- validate_scopes(app, params), - {:ok, auth} <- Authorization.create_authorization(app, user, scopes), - {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)}, - {:ok, token} <- Token.exchange_token(app, auth) do + requested_scopes <- Scopes.fetch_scopes(params, app.scopes), + {:ok, token} <- login(user, app, requested_scopes) do json(conn, OAuthView.render("token.json", %{user: user, token: token})) else error -> @@ -337,6 +334,16 @@ defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :confirm ) end + defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :approval_pending}) do + render_error( + conn, + :forbidden, + "Your account is awaiting approval.", + %{}, + "awaiting_approval" + ) + end + defp handle_token_exchange_error(%Plug.Conn{} = conn, _error) do render_invalid_credentials_error(conn) end @@ -512,6 +519,8 @@ def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "register"} = end end + defp do_create_authorization(conn, auth_attrs, user \\ nil) + defp do_create_authorization( %Plug.Conn{} = conn, %{ @@ -521,19 +530,37 @@ defp do_create_authorization( "redirect_uri" => redirect_uri } = auth_attrs }, - user \\ nil + user ) do with {_, {:ok, %User{} = user}} <- {:get_user, (user && {:ok, user}) || Authenticator.get_user(conn)}, %App{} = app <- Repo.get_by(App, client_id: client_id), true <- redirect_uri in String.split(app.redirect_uris), - {:ok, scopes} <- validate_scopes(app, auth_attrs), - {:account_status, :active} <- {:account_status, User.account_status(user)}, - {:ok, auth} <- Authorization.create_authorization(app, user, scopes) do + requested_scopes <- Scopes.fetch_scopes(auth_attrs, app.scopes), + {:ok, auth} <- do_create_authorization(user, app, requested_scopes) do {:ok, auth, user} end end + defp do_create_authorization(%User{} = user, %App{} = app, requested_scopes) + when is_list(requested_scopes) do + with {:account_status, :active} <- {:account_status, User.account_status(user)}, + {:ok, scopes} <- validate_scopes(app, requested_scopes), + {:ok, auth} <- Authorization.create_authorization(app, user, scopes) do + {:ok, auth} + end + end + + # Note: intended to be a private function but opened for AccountController that logs in on signup + @doc "If checks pass, creates authorization and token for given user, app and requested scopes." + def login(%User{} = user, %App{} = app, requested_scopes) when is_list(requested_scopes) do + with {:ok, auth} <- do_create_authorization(user, app, requested_scopes), + {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)}, + {:ok, token} <- Token.exchange_token(app, auth) do + {:ok, token} + end + end + # Special case: Local MastodonFE defp redirect_uri(%Plug.Conn{} = conn, "."), do: auth_url(conn, :login) @@ -550,12 +577,15 @@ defp build_and_response_mfa_token(user, auth) do end end - @spec validate_scopes(App.t(), map()) :: + @spec validate_scopes(App.t(), map() | list()) :: {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} - defp validate_scopes(%App{} = app, params) do - params - |> Scopes.fetch_scopes(app.scopes) - |> Scopes.validate(app.scopes) + defp validate_scopes(%App{} = app, params) when is_map(params) do + requested_scopes = Scopes.fetch_scopes(params, app.scopes) + validate_scopes(app, requested_scopes) + end + + defp validate_scopes(%App{} = app, requested_scopes) when is_list(requested_scopes) do + Scopes.validate(requested_scopes, app.scopes) end def default_redirect_uri(%App{} = app) do 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/controllers/emoji_reaction_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex index 19dcffdf3..7f9254c13 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex @@ -25,7 +25,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) def index(%{assigns: %{user: user}} = conn, %{id: activity_id} = params) do - with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id), + with true <- Pleroma.Config.get([:instance, :show_reactions]), + %Activity{} = activity <- Activity.get_by_id_with_object(activity_id), %Object{data: %{"reactions" => reactions}} when is_list(reactions) <- Object.normalize(activity) do reactions = filter(reactions, params) diff --git a/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex b/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex index f2112a86e..d4e08b50d 100644 --- a/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex +++ b/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex @@ -14,7 +14,7 @@ def render( %{ chat_message_reference: %{ id: id, - object: %{data: chat_message}, + object: %{data: chat_message} = object, chat_id: chat_id, unread: unread } @@ -30,7 +30,12 @@ def render( attachment: chat_message["attachment"] && StatusView.render("attachment.json", attachment: chat_message["attachment"]), - unread: unread + unread: unread, + card: + StatusView.render( + "card.json", + Pleroma.Web.RichMedia.Helpers.fetch_data_for_object(object) + ) } end 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..5c7daf1a5 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 @@ -49,11 +49,11 @@ defp get_tld(host) do |> hd end - def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do + def fetch_data_for_object(object) do with true <- Config.get([:rich_media, :enabled]), - %Object{} = object <- Object.normalize(activity), false <- object.data["sensitive"] || false, - {:ok, page_url} <- HTML.extract_first_external_url(object, object.data["content"]), + {:ok, page_url} <- + HTML.extract_first_external_url(object, object.data["content"]), :ok <- validate_page_url(page_url), {:ok, rich_media} <- Parser.parse(page_url) do %{page_url: page_url, rich_media: rich_media} @@ -62,6 +62,15 @@ def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) d end end + def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do + with true <- Config.get([:rich_media, :enabled]), + %Object{} = object <- Object.normalize(activity) do + fetch_data_for_object(object) + else + _ -> %{} + end + end + def fetch_data_for_activity(_), do: %{} def perform(:fetch, %Activity{} = activity) do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 386308362..c6433cc53 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -138,6 +138,7 @@ defmodule Pleroma.Web.Router do patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation) patch("/users/activate", AdminAPIController, :user_activate) patch("/users/deactivate", AdminAPIController, :user_deactivate) + patch("/users/approve", AdminAPIController, :user_approve) put("/users/tag", AdminAPIController, :tag_users) delete("/users/tag", AdminAPIController, :untag_users) 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/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 5cfb385ac..2294d9d0d 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -19,6 +19,7 @@ def register_user(params, opts \\ []) do |> Map.put(:nickname, params[:username]) |> Map.put(:name, Map.get(params, :fullname, params[:username])) |> Map.put(:password_confirmation, params[:password]) + |> Map.put(:registration_reason, params[:reason]) if Pleroma.Config.get([:instance, :registrations_open]) do create_user(params, opts) @@ -44,6 +45,7 @@ defp create_user(params, opts) do case User.register(changeset) do {:ok, user} -> + maybe_notify_admins(user) {:ok, user} {:error, changeset} -> @@ -56,6 +58,18 @@ defp create_user(params, opts) do end end + defp maybe_notify_admins(%User{} = account) do + if Pleroma.Config.get([:instance, :account_approval_required]) do + User.all_superusers() + |> Enum.filter(fn user -> not is_nil(user.email) end) + |> Enum.each(fn superuser -> + superuser + |> Pleroma.Emails.AdminEmail.new_unapproved_registration(account) + |> Pleroma.Emails.Mailer.deliver_async() + end) + end + end + def password_reset(nickname_or_email) do with true <- is_binary(nickname_or_email), %User{local: true, email: email} = user when is_binary(email) <- diff --git a/lib/pleroma/web/views/masto_fe_view.ex b/lib/pleroma/web/views/masto_fe_view.ex index f739dacb6..b1669d198 100644 --- a/lib/pleroma/web/views/masto_fe_view.ex +++ b/lib/pleroma/web/views/masto_fe_view.ex @@ -9,36 +9,6 @@ defmodule Pleroma.Web.MastoFEView do alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.CustomEmojiView - @default_settings %{ - onboarded: true, - home: %{ - shows: %{ - reblog: true, - reply: true - } - }, - notifications: %{ - alerts: %{ - follow: true, - favourite: true, - reblog: true, - mention: true - }, - shows: %{ - follow: true, - favourite: true, - reblog: true, - mention: true - }, - sounds: %{ - follow: true, - favourite: true, - reblog: true, - mention: true - } - } - } - def initial_state(token, user, custom_emojis) do limit = Config.get([:instance, :limit]) @@ -86,7 +56,7 @@ def initial_state(token, user, custom_emojis) do "video\/mp4" ] }, - settings: user.mastofe_settings || @default_settings, + settings: user.mastofe_settings || %{}, push_subscription: nil, accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)}, custom_emojis: render(CustomEmojiView, "index.json", custom_emojis: custom_emojis), diff --git a/lib/pleroma/workers/attachments_cleanup_worker.ex b/lib/pleroma/workers/attachments_cleanup_worker.ex index 8deeabda0..58226b395 100644 --- a/lib/pleroma/workers/attachments_cleanup_worker.ex +++ b/lib/pleroma/workers/attachments_cleanup_worker.ex @@ -11,13 +11,12 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do use Pleroma.Workers.WorkerHelper, queue: "attachments_cleanup" @impl Oban.Worker - def perform( - %{ + def perform(%Job{ + args: %{ "op" => "cleanup_attachments", "object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}} - }, - _job - ) do + } + }) do attachments |> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end) |> fetch_objects @@ -28,7 +27,7 @@ def perform( {:ok, :success} end - def perform(%{"op" => "cleanup_attachments", "object" => _object}, _job), do: {:ok, :skip} + def perform(%Job{args: %{"op" => "cleanup_attachments", "object" => _object}}), do: {:ok, :skip} defp do_clean({object_ids, attachment_urls}) do uploader = Pleroma.Config.get([Pleroma.Upload, :uploader]) diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index 57c3a9c3a..cec5a7462 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -11,59 +11,59 @@ defmodule Pleroma.Workers.BackgroundWorker do @impl Oban.Worker - def perform(%{"op" => "deactivate_user", "user_id" => user_id, "status" => status}, _job) do + def perform(%Job{args: %{"op" => "deactivate_user", "user_id" => user_id, "status" => status}}) do user = User.get_cached_by_id(user_id) User.perform(:deactivate_async, user, status) end - def perform(%{"op" => "delete_user", "user_id" => user_id}, _job) do + def perform(%Job{args: %{"op" => "delete_user", "user_id" => user_id}}) do user = User.get_cached_by_id(user_id) User.perform(:delete, user) end - def perform(%{"op" => "force_password_reset", "user_id" => user_id}, _job) do + def perform(%Job{args: %{"op" => "force_password_reset", "user_id" => user_id}}) do user = User.get_cached_by_id(user_id) User.perform(:force_password_reset, user) end - def perform( - %{ + def perform(%Job{ + args: %{ "op" => "blocks_import", "blocker_id" => blocker_id, "blocked_identifiers" => blocked_identifiers - }, - _job - ) do + } + }) do blocker = User.get_cached_by_id(blocker_id) {:ok, User.perform(:blocks_import, blocker, blocked_identifiers)} end - def perform( - %{ + def perform(%Job{ + args: %{ "op" => "follow_import", "follower_id" => follower_id, "followed_identifiers" => followed_identifiers - }, - _job - ) do + } + }) do follower = User.get_cached_by_id(follower_id) {:ok, User.perform(:follow_import, follower, followed_identifiers)} end - def perform(%{"op" => "media_proxy_preload", "message" => message}, _job) do + def perform(%Job{args: %{"op" => "media_proxy_preload", "message" => message}}) do MediaProxyWarmingPolicy.perform(:preload, message) end - def perform(%{"op" => "media_proxy_prefetch", "url" => url}, _job) do + def perform(%Job{args: %{"op" => "media_proxy_prefetch", "url" => url}}) do MediaProxyWarmingPolicy.perform(:prefetch, url) end - def perform(%{"op" => "fetch_data_for_activity", "activity_id" => activity_id}, _job) do + def perform(%Job{args: %{"op" => "fetch_data_for_activity", "activity_id" => activity_id}}) do activity = Activity.get_by_id(activity_id) Pleroma.Web.RichMedia.Helpers.perform(:fetch, activity) end - def perform(%{"op" => "move_following", "origin_id" => origin_id, "target_id" => target_id}, _) do + def perform(%Job{ + args: %{"op" => "move_following", "origin_id" => origin_id, "target_id" => target_id} + }) do origin = User.get_cached_by_id(origin_id) target = User.get_cached_by_id(target_id) diff --git a/lib/pleroma/workers/cron/clear_oauth_token_worker.ex b/lib/pleroma/workers/cron/clear_oauth_token_worker.ex index a4c3b9516..d41be4e87 100644 --- a/lib/pleroma/workers/cron/clear_oauth_token_worker.ex +++ b/lib/pleroma/workers/cron/clear_oauth_token_worker.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Workers.Cron.ClearOauthTokenWorker do alias Pleroma.Web.OAuth.Token @impl Oban.Worker - def perform(_opts, _job) do + def perform(_job) do if Config.get([:oauth2, :clean_expired_tokens], false) do Token.delete_expired_tokens() else diff --git a/lib/pleroma/workers/cron/digest_emails_worker.ex b/lib/pleroma/workers/cron/digest_emails_worker.ex index 7f09ff3cf..ee646229f 100644 --- a/lib/pleroma/workers/cron/digest_emails_worker.ex +++ b/lib/pleroma/workers/cron/digest_emails_worker.ex @@ -19,7 +19,7 @@ defmodule Pleroma.Workers.Cron.DigestEmailsWorker do require Logger @impl Oban.Worker - def perform(_opts, _job) do + def perform(_job) do config = Config.get([:email_notifications, :digest]) if config[:active] do diff --git a/lib/pleroma/workers/cron/new_users_digest_worker.ex b/lib/pleroma/workers/cron/new_users_digest_worker.ex index 5c816b3fe..abc8a5e95 100644 --- a/lib/pleroma/workers/cron/new_users_digest_worker.ex +++ b/lib/pleroma/workers/cron/new_users_digest_worker.ex @@ -12,7 +12,7 @@ defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do use Pleroma.Workers.WorkerHelper, queue: "new_users_digest" @impl Oban.Worker - def perform(_args, _job) do + def perform(_job) do if Pleroma.Config.get([Pleroma.Emails.NewUsersDigestEmail, :enabled]) do today = NaiveDateTime.utc_now() |> Timex.beginning_of_day() diff --git a/lib/pleroma/workers/cron/purge_expired_activities_worker.ex b/lib/pleroma/workers/cron/purge_expired_activities_worker.ex index 84b3b84de..e926c5dc8 100644 --- a/lib/pleroma/workers/cron/purge_expired_activities_worker.ex +++ b/lib/pleroma/workers/cron/purge_expired_activities_worker.ex @@ -20,7 +20,7 @@ defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker do @interval :timer.minutes(1) @impl Oban.Worker - def perform(_opts, _job) do + def perform(_job) do if Config.get([ActivityExpiration, :enabled]) do Enum.each(ActivityExpiration.due_expirations(@interval), &delete_activity/1) else diff --git a/lib/pleroma/workers/cron/stats_worker.ex b/lib/pleroma/workers/cron/stats_worker.ex index e9b8d59c4..e54bd9a7f 100644 --- a/lib/pleroma/workers/cron/stats_worker.ex +++ b/lib/pleroma/workers/cron/stats_worker.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Workers.Cron.StatsWorker do use Oban.Worker, queue: "background" @impl Oban.Worker - def perform(_opts, _job) do + def perform(_job) do Pleroma.Stats.do_collect() end end diff --git a/lib/pleroma/workers/mailer_worker.ex b/lib/pleroma/workers/mailer_worker.ex index 6955338a5..32273cfa5 100644 --- a/lib/pleroma/workers/mailer_worker.ex +++ b/lib/pleroma/workers/mailer_worker.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Workers.MailerWorker do use Pleroma.Workers.WorkerHelper, queue: "mailer" @impl Oban.Worker - def perform(%{"op" => "email", "encoded_email" => encoded_email, "config" => config}, _job) do + def perform(%Job{args: %{"op" => "email", "encoded_email" => encoded_email, "config" => config}}) do encoded_email |> Base.decode64!() |> :erlang.binary_to_term() diff --git a/lib/pleroma/workers/publisher_worker.ex b/lib/pleroma/workers/publisher_worker.ex index daf79efc0..e739c3cd0 100644 --- a/lib/pleroma/workers/publisher_worker.ex +++ b/lib/pleroma/workers/publisher_worker.ex @@ -8,17 +8,17 @@ defmodule Pleroma.Workers.PublisherWorker do use Pleroma.Workers.WorkerHelper, queue: "federator_outgoing" - def backoff(attempt) when is_integer(attempt) do + def backoff(%Job{attempt: attempt}) when is_integer(attempt) do Pleroma.Workers.WorkerHelper.sidekiq_backoff(attempt, 5) end @impl Oban.Worker - def perform(%{"op" => "publish", "activity_id" => activity_id}, _job) do + def perform(%Job{args: %{"op" => "publish", "activity_id" => activity_id}}) do activity = Activity.get_by_id(activity_id) Federator.perform(:publish, activity) end - def perform(%{"op" => "publish_one", "module" => module_name, "params" => params}, _job) do + def perform(%Job{args: %{"op" => "publish_one", "module" => module_name, "params" => params}}) do params = Map.new(params, fn {k, v} -> {String.to_atom(k), v} end) Federator.perform(:publish_one, String.to_atom(module_name), params) end diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex index f7a7124f3..1b97af1a8 100644 --- a/lib/pleroma/workers/receiver_worker.ex +++ b/lib/pleroma/workers/receiver_worker.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Workers.ReceiverWorker do use Pleroma.Workers.WorkerHelper, queue: "federator_incoming" @impl Oban.Worker - def perform(%{"op" => "incoming_ap_doc", "params" => params}, _job) do + def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do Federator.perform(:incoming_ap_doc, params) end end diff --git a/lib/pleroma/workers/remote_fetcher_worker.ex b/lib/pleroma/workers/remote_fetcher_worker.ex index ec6534f21..27e2e3386 100644 --- a/lib/pleroma/workers/remote_fetcher_worker.ex +++ b/lib/pleroma/workers/remote_fetcher_worker.ex @@ -8,13 +8,7 @@ defmodule Pleroma.Workers.RemoteFetcherWorker do use Pleroma.Workers.WorkerHelper, queue: "remote_fetcher" @impl Oban.Worker - def perform( - %{ - "op" => "fetch_remote", - "id" => id - } = args, - _job - ) do + def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do {:ok, _object} = Fetcher.fetch_object_from_id(id, depth: args["depth"]) end end diff --git a/lib/pleroma/workers/scheduled_activity_worker.ex b/lib/pleroma/workers/scheduled_activity_worker.ex index 97d1efbfb..dd9986fe4 100644 --- a/lib/pleroma/workers/scheduled_activity_worker.ex +++ b/lib/pleroma/workers/scheduled_activity_worker.ex @@ -17,7 +17,7 @@ defmodule Pleroma.Workers.ScheduledActivityWorker do require Logger @impl Oban.Worker - def perform(%{"activity_id" => activity_id}, _job) do + def perform(%Job{args: %{"activity_id" => activity_id}}) do if Config.get([ScheduledActivity, :enabled]) do case Pleroma.Repo.get(ScheduledActivity, activity_id) do %ScheduledActivity{} = scheduled_activity -> diff --git a/lib/pleroma/workers/transmogrifier_worker.ex b/lib/pleroma/workers/transmogrifier_worker.ex index 11239ca5e..15f36375c 100644 --- a/lib/pleroma/workers/transmogrifier_worker.ex +++ b/lib/pleroma/workers/transmogrifier_worker.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Workers.TransmogrifierWorker do use Pleroma.Workers.WorkerHelper, queue: "transmogrifier" @impl Oban.Worker - def perform(%{"op" => "user_upgrade", "user_id" => user_id}, _job) do + def perform(%Job{args: %{"op" => "user_upgrade", "user_id" => user_id}}) do user = User.get_cached_by_id(user_id) Pleroma.Web.ActivityPub.Transmogrifier.perform(:user_upgrade, user) end diff --git a/lib/pleroma/workers/web_pusher_worker.ex b/lib/pleroma/workers/web_pusher_worker.ex index 58ad25e39..0cfdc6a6f 100644 --- a/lib/pleroma/workers/web_pusher_worker.ex +++ b/lib/pleroma/workers/web_pusher_worker.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Workers.WebPusherWorker do use Pleroma.Workers.WorkerHelper, queue: "web_push" @impl Oban.Worker - def perform(%{"op" => "web_push", "notification_id" => notification_id}, _job) do + def perform(%Job{args: %{"op" => "web_push", "notification_id" => notification_id}}) do notification = Notification |> Repo.get(notification_id) diff --git a/lib/pleroma/workers/worker_helper.ex b/lib/pleroma/workers/worker_helper.ex index d1f90c35b..7d1289be2 100644 --- a/lib/pleroma/workers/worker_helper.ex +++ b/lib/pleroma/workers/worker_helper.ex @@ -32,6 +32,8 @@ defmacro __using__(opts) do queue: unquote(queue), max_attempts: 1 + alias Oban.Job + def enqueue(op, params, worker_args \\ []) do params = Map.merge(%{"op" => op}, params) queue_atom = String.to_atom(unquote(queue)) @@ -39,7 +41,7 @@ def enqueue(op, params, worker_args \\ []) do unquote(caller_module) |> apply(:new, [params, worker_args]) - |> Pleroma.Repo.insert() + |> Oban.insert() end end end diff --git a/mix.exs b/mix.exs index e2ab53bde..0e723c15f 100644 --- a/mix.exs +++ b/mix.exs @@ -90,8 +90,6 @@ defp elixirc_paths(:test), do: ["lib", "test/support"] defp elixirc_paths(_), do: ["lib"] defp warnings_as_errors(:prod), do: false - # Uncomment this if you need testing configurable_from_database logic - # defp warnings_as_errors(:dev), do: false defp warnings_as_errors(_), do: true # Specifies OAuth dependencies. @@ -116,68 +114,55 @@ defp oauth_deps do # Type `mix help deps` for examples and options. defp deps do [ - {:phoenix, "~> 1.4.8"}, + {:phoenix, "~> 1.4.17"}, {:tzdata, "~> 1.0.3"}, - {:plug_cowboy, "~> 2.0"}, + {:plug_cowboy, "~> 2.3"}, {:phoenix_pubsub, "~> 1.1"}, {:phoenix_ecto, "~> 4.0"}, {:ecto_enum, "~> 1.4"}, - {:ecto_sql, "~> 3.3.2"}, - {:postgrex, ">= 0.13.5"}, - {:oban, "~> 1.2"}, - {:gettext, "~> 0.15"}, - {:pbkdf2_elixir, "~> 1.0"}, - {:bcrypt_elixir, "~> 2.0"}, + {:ecto_sql, "~> 3.4.4"}, + {:postgrex, ">= 0.15.5"}, + {:oban, "~> 2.0.0"}, + {:gettext, "~> 0.18"}, + {:pbkdf2_elixir, "~> 1.2"}, + {:bcrypt_elixir, "~> 2.2"}, {:trailing_format_plug, "~> 0.0.7"}, {:fast_sanitize, "~> 0.1"}, {:html_entities, "~> 0.5", override: true}, - {:phoenix_html, "~> 2.10"}, - {:calendar, "~> 0.17.4"}, + {:phoenix_html, "~> 2.14"}, + {:calendar, "~> 1.0"}, {:cachex, "~> 3.2"}, {: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}, + {:cowlib, "~> 2.9", override: true}, {:gun, - github: "ninenines/gun", ref: "e1a69b36b180a574c0ac314ced9613fdd52312cc", override: true}, - {:jason, "~> 1.0"}, - {:mogrify, "~> 0.6.1"}, + github: "ninenines/gun", ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", override: true}, + {:jason, "~> 1.2"}, + {:mogrify, "~> 0.7.4"}, {:ex_aws, "~> 2.1"}, {:ex_aws_s3, "~> 2.0"}, {:sweet_xml, "~> 0.6.6"}, - {:earmark, "~> 1.3"}, + {:earmark, "1.4.3"}, {:bbcode_pleroma, "~> 0.2.0"}, - {:ex_machina, "~> 2.3", only: :test}, - {:credo, "~> 1.1.0", only: [:dev, :test], runtime: false}, - {:mock, "~> 0.3.3", only: :test}, {:crypt, - git: "https://github.com/msantos/crypt", 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", - ref: "c96e0ca8a00d8f211ec1f042a4626b09f249caa5", - override: true}, - {:phoenix_swoosh, "~> 0.2"}, + git: "https://github.com/msantos/crypt.git", + ref: "f63a705f92c26955977ee62a313012e309a4d77a"}, + {:cors_plug, "~> 2.0"}, + {:web_push_encryption, "~> 0.3"}, + {:swoosh, "~> 1.0"}, + {:phoenix_swoosh, "~> 0.3"}, {:gen_smtp, "~> 0.13"}, - {:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test}, {:ex_syslogger, "~> 1.4"}, - {:floki, "~> 0.25"}, - {:timex, "~> 3.5"}, + {:floki, "~> 0.27"}, + {:timex, "~> 3.6"}, {:ueberauth, "~> 0.4"}, - {:auto_linker, - git: "https://git.pleroma.social/pleroma/auto_linker.git", - ref: "95e8188490e97505c56636c1379ffdf036c1fdde"}, - {:http_signatures, - git: "https://git.pleroma.social/pleroma/http_signatures.git", - ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"}, + {:linkify, "~> 0.2.0"}, + {:http_signatures, "~> 0.1.0"}, {:telemetry, "~> 0.3"}, {:poolboy, "~> 1.5"}, + {:prometheus, "~> 4.6"}, {:prometheus_ex, "~> 3.0"}, {:prometheus_plugs, "~> 1.1"}, {:prometheus_phoenix, "~> 1.3"}, @@ -186,23 +171,33 @@ defp deps do {:quack, "~> 0.1.1"}, {:joken, "~> 2.0"}, {:benchee, "~> 1.0"}, - {:pot, "~> 0.10.2"}, + {:pot, "~> 0.11"}, {:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)}, {:ex_const, "~> 0.2"}, {: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: "55e92f84b4ed531bd487952a71040a9c69dc2807"}, {:remote_ip, git: "https://git.pleroma.social/pleroma/remote_ip.git", ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"}, {:captcha, git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"}, - {:mox, "~> 0.5", only: :test}, {:restarter, path: "./restarter"}, {:open_api_spex, git: "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", - ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"} + ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"}, + + ## dev & test + {:ex_doc, "~> 0.22", only: :dev, runtime: false}, + {:ex_machina, "~> 2.4", only: :test}, + {:credo, "~> 1.4", only: [:dev, :test], runtime: false}, + {:mock, "~> 0.3.5", only: :test}, + {:excoveralls, "~> 0.13.1", only: :test}, + {:mox, "~> 0.5", only: :test}, + {:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test} ] ++ oauth_deps() end diff --git a/mix.lock b/mix.lock index 4f2777fa7..55c3c59c6 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"]}, @@ -9,52 +8,54 @@ "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "cachex": {:hex, :cachex, "3.2.0", "a596476c781b0646e6cb5cd9751af2e2974c3e0d5498a8cab71807618b74fe2f", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "aef93694067a43697ae0531727e097754a9e992a1e7946296f5969d6dd9ac986"}, - "calendar": {:hex, :calendar, "0.17.6", "ec291cb2e4ba499c2e8c0ef5f4ace974e2f9d02ae9e807e711a9b0c7850b9aee", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "738d0e17a93c2ccfe4ddc707bdc8e672e9074c8569498483feb1c4530fb91b2b"}, + "calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"}, "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e0f16822d578866e186a0974d65ad58cddc1e2ab", [ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"]}, - "castore": {:hex, :castore, "0.1.5", "591c763a637af2cc468a72f006878584bc6c306f8d111ef8ba1d4c10e0684010", [:mix], [], "hexpm", "6db356b2bc6cc22561e051ff545c20ad064af57647e436650aa24d7d06cd941a"}, + "castore": {:hex, :castore, "0.1.7", "1ca19eee705cde48c9e809e37fdd0730510752cc397745e550f6065a56a701e9", [:mix], [], "hexpm", "a2ae2c13d40e9c308387f1aceb14786dca019ebc2a11484fb2a9f797ea0aa0d8"}, "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", "55e92f84b4ed531bd487952a71040a9c69dc2807", [ref: "55e92f84b4ed531bd487952a71040a9c69dc2807"]}, "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"}, + "cors_plug": {:hex, :cors_plug, "2.0.2", "2b46083af45e4bc79632bd951550509395935d3e7973275b2b743bd63cc942ce", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f0d0e13f71c51fd4ef8b2c7e051388e4dfb267522a83a22392c856de7e46465f"}, + "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"}, + "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"}, + "credo": {:hex, :credo, "1.4.0", "92339d4cbadd1e88b5ee43d427b639b68a11071b6f73854e33638e30a0ea11f5", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1fd3b70dce216574ce3c18bdf510b57e7c4c85c2ec9cad4bff854abaf7e58658"}, "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.1", "caee17725495f5129cb7faebde001dc4406796f12a62b8949f4ac69315080566", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "2b02ece62d9f983fcd40954e443b7d9e6589664380e5546b2b9b523cd0fb59e1"}, + "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"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"}, - "ecto": {:hex, :ecto, "3.4.4", "a2c881e80dc756d648197ae0d936216c0308370332c5e77a2325a10293eef845", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4bd3ad62abc3b21fb629f0f7a3dab23a192fca837d257dd08449fba7373561"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"}, + "ecto": {:hex, :ecto, "3.4.5", "2bcd262f57b2c888b0bd7f7a28c8a48aa11dc1a2c6a858e45dd8f8426d504265", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c6d1d4d524559e9b7a062f0498e2c206122552d63eacff0a6567ffe7a8e8691"}, "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"}, - "ecto_sql": {:hex, :ecto_sql, "3.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5eccbdbf92e3c6f213007a82d5dbba4cd9bb659d1a21331f89f408e4c0efd7a8"}, + "ecto_sql": {:hex, :ecto_sql, "3.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"}, "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"}, "esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"}, "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"}, "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"}, - "ex_aws": {:hex, :ex_aws, "2.1.1", "1e4de2106cfbf4e837de41be41cd15813eabc722315e388f0d6bb3732cec47cd", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "06b6fde12b33bb6d65d5d3493e903ba5a56d57a72350c15285a4298338089e10"}, + "ex_aws": {:hex, :ex_aws, "2.1.3", "26b6f036f0127548706aade4a509978fc7c26bd5334b004fba9bfe2687a525df", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0bdbe2aed9f326922fc5a6a80417e32f0c895f4b3b2b0b9676ebf23dd16c5da4"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.0.2", "c0258bbdfea55de4f98f0b2f0ca61fe402cc696f573815134beb1866e778f47b", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0569f5b211b1a3b12b705fe2a9d0e237eb1360b9d76298028df2346cad13097a"}, "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"}, - "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"}, - "ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "b84f6af156264530b312a8ab98ac6088f6b77ae5fe2058305c81434aa01fbaf9"}, + "ex_doc": {:hex, :ex_doc, "0.22.2", "03a2a58bdd2ba0d83d004507c4ee113b9c521956938298eba16e55cc4aba4a6c", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "cf60e1b3e2efe317095b6bb79651f83a2c1b3edcb4d319c421d7fcda8b3aff26"}, + "ex_machina": {:hex, :ex_machina, "2.4.0", "09a34c5d371bfb5f78399029194a8ff67aff340ebe8ba19040181af35315eabb", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "a20bc9ddc721b33ea913b93666c5d0bdca5cbad7a67540784ae277228832d72c"}, "ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"}, - "excoveralls": {:hex, :excoveralls, "0.12.2", "a513defac45c59e310ac42fcf2b8ae96f1f85746410f30b1ff2b710a4b6cd44b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "151c476331d49b45601ffc45f43cb3a8beb396b02a34e3777fea0ad34ae57d89"}, + "excoveralls": {:hex, :excoveralls, "0.13.1", "b9f1697f7c9e0cfe15d1a1d737fb169c398803ffcbc57e672aa007e9fd42864c", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b4bb550e045def1b4d531a37fb766cbbe1307f7628bf8f0414168b3f52021cce"}, "fast_html": {:hex, :fast_html, "1.0.3", "2cc0d4b68496266a1530e0c852cafeaede0bd10cfdee26fda50dc696c203162f", [:make, :mix], [], "hexpm", "ab3d782b639d3c4655fbaec0f9d032c91f8cab8dd791ac7469c2381bc7c32f85"}, "fast_sanitize": {:hex, :fast_sanitize, "0.1.7", "2a7cd8734c88a2de6de55022104f8a3b87f1fdbe8bbf131d9049764b53d50d0d", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f39fe8ea08fbac17487c30bf09b7d9f3e12472e51fb07a88ffeb8fd17da8ab67"}, "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"}, - "floki": {:hex, :floki, "0.25.0", "b1c9ddf5f32a3a90b43b76f3386ca054325dc2478af020e87b5111c19f2284ac", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "631f4e627c46d5ecd347df5a2accdaf0621c77c3693c5b75a8ad58e84c61f242"}, + "floki": {:hex, :floki, "0.27.0", "6b29a14283f1e2e8fad824bc930eaa9477c462022075df6bea8f0ad811c13599", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "583b8c13697c37179f1f82443bcc7ad2f76fbc0bf4c186606eebd658f7f2631b"}, "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, "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"]}, + "gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"}, + "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"}, - "http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]}, - "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"}, + "http_signatures": {:hex, :http_signatures, "0.1.0", "4e4b501a936dbf4cb5222597038a89ea10781776770d2e185849fa829686b34c", [:mix], [], "hexpm", "f8a7b3731e3fd17d38fa6e343fcad7b03d6874a3b0a108c8568a71ed9c2cf824"}, + "httpoison": {:hex, :httpoison, "1.7.0", "abba7d086233c2d8574726227b6c2c4f6e53c4deae7fe5f6de531162ce9929a0", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "975cc87c845a103d3d1ea1ccfd68a2700c211a434d8428b10c323dc95dc5b980"}, "idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"}, "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"}, "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"}, @@ -62,58 +63,59 @@ "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"}, - "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"}, + "linkify": {:hex, :linkify, "0.2.0", "2518bbbea21d2caa9d372424e1ad845b640c6630e2d016f1bd1f518f9ebcca28", [:mix], [], "hexpm", "b8ca8a68b79e30b7938d6c996085f3db14939f29538a59ca5101988bb7f917f6"}, + "makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"}, "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"}, - "mock": {:hex, :mock, "0.3.4", "c5862eb3b8c64237f45f586cf00c9d892ba07bb48305a43319d428ce3c2897dd", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "e6d886252f1a41f4ba06ecf2b4c8d38760b34b1c08a11c28f7397b2e03995964"}, - "mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm", "3bc928d817974fa10cc11e6c89b9a9361e37e96dbbf3d868c41094ec05745dcd"}, - "mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm", "052346cf322311c49a0f22789f3698eea030eec09b8c47367f0686ef2634ae14"}, + "mock": {:hex, :mock, "0.3.5", "feb81f52b8dcf0a0d65001d2fec459f6b6a8c22562d94a965862f6cc066b5431", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "6fae404799408300f863550392635d8f7e3da6b71abdd5c393faf41b131c8728"}, + "mogrify": {:hex, :mogrify, "0.7.4", "9b2496dde44b1ce12676f85d7dc531900939e6367bc537c7243a1b089435b32d", [:mix], [], "hexpm", "50d79e337fba6bc95bfbef918058c90f50b17eed9537771e61d4619488f099c3"}, + "mox": {:hex, :mox, "0.5.2", "55a0a5ba9ccc671518d068c8dddd20eeb436909ea79d1799e2209df7eaa98b6c", [:mix], [], "hexpm", "df4310628cd628ee181df93f50ddfd07be3e5ecc30232d3b6aadf30bdfe6092b"}, "myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]}, - "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"}, "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, - "oban": {:hex, :oban, "1.2.0", "7cca94d341be43d220571e28f69131c4afc21095b25257397f50973d3fc59b07", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba5f8b3f7d76967b3e23cf8014f6a13e4ccb33431e4808f036709a7f822362ee"}, + "oban": {:hex, :oban, "2.0.0", "e6ce70d94dd46815ec0882a1ffb7356df9a9d5b8a40a64ce5c2536617a447379", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cf574813bd048b98a698aa587c21367d2e06842d4e1b1993dcd6a696e9e633bd"}, "open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "f296ac0924ba3cf79c7a588c4c252889df4c2edd", [ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"]}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"}, - "phoenix": {:hex, :phoenix, "1.4.13", "67271ad69b51f3719354604f4a3f968f83aa61c19199343656c9caee057ff3b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ab765a0feddb81fc62e2116c827b5f068df85159c162bee760745276ad7ddc1b"}, + "phoenix": {:hex, :phoenix, "1.4.17", "1b1bd4cff7cfc87c94deaa7d60dd8c22e04368ab95499483c50640ef3bd838d8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a8e5d7a3d76d452bb5fb86e8b7bd115f737e4f8efe202a463d4aeb4a5809611"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"}, - "phoenix_html": {:hex, :phoenix_html, "2.14.0", "d8c6bc28acc8e65f8ea0080ee05aa13d912c8758699283b8d3427b655aabe284", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b0bb30eda478a06dbfbe96728061a93833db3861a49ccb516f839ecb08493fbb"}, + "phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm", "1f13f9f0f3e769a667a6b6828d29dec37497a082d195cc52dbef401a9b69bf38"}, - "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "ebf1bfa7b3c1c850c04929afe02e2e0d7ab135e0706332c865de03e761676b1f"}, - "plug": {:hex, :plug, "1.9.0", "8d7c4e26962283ff9f8f3347bd73838e2413fbc38b7bb5467d5924f68f3a5a4a", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "9902eda2c52ada2a096434682e99a2493f5d06a94d6ac6bcfff9805f952350f1"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.1.2", "8b0addb5908c5238fac38e442e81b6fcd32788eaa03246b4d55d147c47c5805e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "7d722581ce865a237e14da6d946f92704101740a256bd13ec91e63c0b122fc70"}, + "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.3.0", "2acfa0db038a7649e0a4614eee970e6ed9a39d191ccd79a03583b51d0da98165", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.0", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "b8bbae4b59a676de6b8bd8675eda37bc8b4424812ae429d6fdcb2b039e00003b"}, + "plug": {:hex, :plug, "1.10.3", "c9cebe917637d8db0e759039cc106adca069874e1a9034fd6e3fdd427fd3c283", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01f9037a2a1de1d633b5a881101e6a444bcabb1d386ca1e00bb273a1f1d9d939"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.3.0", "149a50e05cb73c12aad6506a371cd75750c0b19a32f81866e1a323dda9e0e99d", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bc595a1870cef13f9c1e03df56d96804db7f702175e4ccacdb8fc75c02a7b97e"}, "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, - "postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4737ce62a31747b4c63c12b20c62307e51bb4fcd730ca0c32c280991e0606c90"}, - "pot": {:hex, :pot, "0.10.2", "9895c83bcff8cd22d9f5bc79dfc88a188176b261b618ad70d93faf5c5ca36e67", [:rebar3], [], "hexpm", "ac589a8e296b7802681e93cd0a436faec117ea63e9916709c628df31e17e91e2"}, - "prometheus": {:hex, :prometheus, "4.5.0", "8f4a2246fe0beb50af0f77c5e0a5bb78fe575c34a9655d7f8bc743aad1c6bf76", [:mix, :rebar3], [], "hexpm", "679b5215480fff612b8351f45c839d995a07ce403e42ff02f1c6b20960d41a4e"}, + "postgrex": {:hex, :postgrex, "0.15.5", "aec40306a622d459b01bff890fa42f1430dac61593b122754144ad9033a2152f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ed90c81e1525f65a2ba2279dbcebf030d6d13328daa2f8088b9661eb9143af7f"}, + "pot": {:hex, :pot, "0.11.0", "61bad869a94534739dd4614a25a619bc5c47b9970e9a0ea5bef4628036fc7a16", [:rebar3], [], "hexpm", "57ee6ee6bdeb639661ffafb9acefe3c8f966e45394de6a766813bb9e1be4e54b"}, + "prometheus": {:hex, :prometheus, "4.6.0", "20510f381db1ccab818b4cf2fac5fa6ab5cc91bc364a154399901c001465f46f", [:mix, :rebar3], [], "hexpm", "4905fd2992f8038eccd7aa0cd22f40637ed618c0bed1f75c05aacec15b7545de"}, "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"}, "prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm", "9fd13404a48437e044b288b41f76e64acd9735fb8b0e3809f494811dfa66d0fb"}, "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "c4d1404ac4e9d3d963da601db2a7d8ea31194f0017057fabf0cfb9bf5a6c8c75"}, "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm", "0273a6483ccb936d79ca19b0ab629aef0dba958697c94782bb728b920dfc6a79"}, "quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "d736bfa7444112eb840027bb887832a0e403a4a3437f48028c3b29a2dbbd2543"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, - "recon": {:hex, :recon, "2.5.0", "2f7fcbec2c35034bade2f9717f77059dc54eb4e929a3049ca7ba6775c0bd66cd", [:mix, :rebar3], [], "hexpm", "72f3840fedd94f06315c523f6cecf5b4827233bed7ae3fe135b2a0ebeab5e196"}, + "recon": {:hex, :recon, "2.5.1", "430ffa60685ac1efdfb1fe4c97b8767c92d0d92e6e7c3e8621559ba77598678a", [:mix, :rebar3], [], "hexpm", "5721c6b6d50122d8f68cccac712caa1231f97894bab779eff5ff0f886cb44648"}, "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]}, "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": {:hex, :swoosh, "1.0.0", "c547cfc83f30e12d5d1fdcb623d7de2c2e29a5becfc68bf8f42ba4d23d2c2756", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "b3b08e463f876cb6167f7168e9ad99a069a724e124bcee61847e0e1ed13f4a0d"}, "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"]}, - "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"}, + "tesla": {:git, "https://github.com/teamon/tesla.git", "af3707078b10793f6a534938e56b963aff82fe3c", [ref: "af3707078b10793f6a534938e56b963aff82fe3c"]}, + "timex": {:hex, :timex, "3.6.2", "845cdeb6119e2fef10751c0b247b6c59d86d78554c83f78db612e3290f819bc2", [: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", "26030b46199d02a590be61c2394b37ea25a3664c02fafbeca0b24c972025d47a"}, "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"}, - "ueberauth": {:hex, :ueberauth, "0.6.2", "25a31111249d60bad8b65438b2306a4dc91f3208faa62f5a8c33e8713989b2e8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "db9fbfb5ac707bc4f85a297758406340bf0358b4af737a88113c1a9eee120ac7"}, + "ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"}, "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"}, - "web_push_encryption": {:hex, :web_push_encryption, "0.2.3", "a0ceab85a805a30852f143d22d71c434046fbdbafbc7292e7887cec500826a80", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "9315c8f37c108835cf3f8e9157d7a9b8f420a34f402d1b1620a31aed5b93ecdf"}, + "web_push_encryption": {:hex, :web_push_encryption, "0.3.0", "598b5135e696fd1404dc8d0d7c0fa2c027244a4e5d5e5a98ba267f14fdeaabc8", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "f10bdd1afe527ede694749fb77a2f22f146a51b054c7fa541c9fd920fba7c875"}, "websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []}, } diff --git a/priv/gettext/errors.pot b/priv/gettext/errors.pot index 0e1cf37eb..e337226a7 100644 --- a/priv/gettext/errors.pot +++ b/priv/gettext/errors.pot @@ -90,110 +90,100 @@ msgid "must be equal to %{number}" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/common_api.ex:421 +#: lib/pleroma/web/common_api/common_api.ex:505 msgid "Account not found" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/common_api.ex:249 +#: lib/pleroma/web/common_api/common_api.ex:339 msgid "Already voted" msgstr "" #, elixir-format -#: lib/pleroma/web/oauth/oauth_controller.ex:360 +#: lib/pleroma/web/oauth/oauth_controller.ex:359 msgid "Bad request" msgstr "" #, elixir-format -#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:426 msgid "Can't delete object" msgstr "" #, elixir-format -#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:196 -msgid "Can't delete this post" -msgstr "" - -#, elixir-format -#: lib/pleroma/web/controller_helper.ex:95 -#: lib/pleroma/web/controller_helper.ex:101 +#: lib/pleroma/web/controller_helper.ex:105 +#: lib/pleroma/web/controller_helper.ex:111 msgid "Can't display this activity" msgstr "" #, elixir-format -#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:227 -#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:254 +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:285 msgid "Can't find user" msgstr "" #, elixir-format -#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:114 +#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61 msgid "Can't get favorites" msgstr "" #, elixir-format -#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:437 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:438 msgid "Can't like object" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/utils.ex:556 +#: lib/pleroma/web/common_api/utils.ex:563 msgid "Cannot post an empty status without attachments" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/utils.ex:504 +#: lib/pleroma/web/common_api/utils.ex:511 msgid "Comment must be up to %{max_size} characters" msgstr "" #, elixir-format -#: lib/pleroma/config/config_db.ex:222 +#: lib/pleroma/config/config_db.ex:191 msgid "Config with params %{params} not found" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/common_api.ex:95 +#: lib/pleroma/web/common_api/common_api.ex:181 +#: lib/pleroma/web/common_api/common_api.ex:185 msgid "Could not delete" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/common_api.ex:141 +#: lib/pleroma/web/common_api/common_api.ex:231 msgid "Could not favorite" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/common_api.ex:370 +#: lib/pleroma/web/common_api/common_api.ex:453 msgid "Could not pin" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/common_api.ex:112 -msgid "Could not repeat" -msgstr "" - -#, elixir-format -#: lib/pleroma/web/common_api/common_api.ex:188 +#: lib/pleroma/web/common_api/common_api.ex:278 msgid "Could not unfavorite" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/common_api.ex:380 +#: lib/pleroma/web/common_api/common_api.ex:463 msgid "Could not unpin" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/common_api.ex:126 +#: lib/pleroma/web/common_api/common_api.ex:216 msgid "Could not unrepeat" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/common_api.ex:428 -#: lib/pleroma/web/common_api/common_api.ex:437 +#: lib/pleroma/web/common_api/common_api.ex:512 +#: lib/pleroma/web/common_api/common_api.ex:521 msgid "Could not update state" msgstr "" #, elixir-format -#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202 +#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:207 msgid "Error." msgstr "" @@ -203,8 +193,8 @@ msgid "Invalid CAPTCHA" msgstr "" #, elixir-format -#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:117 -#: lib/pleroma/web/oauth/oauth_controller.ex:569 +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:116 +#: lib/pleroma/web/oauth/oauth_controller.ex:568 msgid "Invalid credentials" msgstr "" @@ -214,22 +204,22 @@ msgid "Invalid credentials." msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/common_api.ex:265 +#: lib/pleroma/web/common_api/common_api.ex:355 msgid "Invalid indices" msgstr "" #, elixir-format -#: lib/pleroma/web/admin_api/admin_api_controller.ex:1147 +#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29 msgid "Invalid parameters" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/utils.ex:411 +#: lib/pleroma/web/common_api/utils.ex:414 msgid "Invalid password." msgstr "" #, elixir-format -#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:187 +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:220 msgid "Invalid request" msgstr "" @@ -239,44 +229,44 @@ msgid "Kocaptcha service unavailable" msgstr "" #, elixir-format -#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:113 +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:112 msgid "Missing parameters" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/utils.ex:540 +#: lib/pleroma/web/common_api/utils.ex:547 msgid "No such conversation" msgstr "" #, elixir-format -#: lib/pleroma/web/admin_api/admin_api_controller.ex:439 -#: lib/pleroma/web/admin_api/admin_api_controller.ex:465 lib/pleroma/web/admin_api/admin_api_controller.ex:507 +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388 +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456 msgid "No such permission_group" msgstr "" #, elixir-format -#: lib/pleroma/plugs/uploaded_media.ex:74 -#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:485 lib/pleroma/web/admin_api/admin_api_controller.ex:1135 -#: lib/pleroma/web/feed/user_controller.ex:73 lib/pleroma/web/ostatus/ostatus_controller.ex:143 +#: lib/pleroma/plugs/uploaded_media.ex:84 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11 +#: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143 msgid "Not found" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/common_api.ex:241 +#: lib/pleroma/web/common_api/common_api.ex:331 msgid "Poll's author can't vote" msgstr "" #, elixir-format #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20 #: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49 -#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:290 +#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:306 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71 msgid "Record not found" msgstr "" #, elixir-format -#: lib/pleroma/web/admin_api/admin_api_controller.ex:1153 -#: lib/pleroma/web/feed/user_controller.ex:79 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:32 +#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35 +#: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36 #: lib/pleroma/web/ostatus/ostatus_controller.ex:149 msgid "Something went wrong" msgstr "" @@ -287,7 +277,7 @@ msgid "The message visibility must be direct" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/utils.ex:566 +#: lib/pleroma/web/common_api/utils.ex:573 msgid "The status is over the character limit" msgstr "" @@ -302,65 +292,65 @@ msgid "Throttled" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/common_api.ex:266 +#: lib/pleroma/web/common_api/common_api.ex:356 msgid "Too many choices" msgstr "" #, elixir-format -#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:442 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443 msgid "Unhandled activity type" msgstr "" #, elixir-format -#: lib/pleroma/web/admin_api/admin_api_controller.ex:536 +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485 msgid "You can't revoke your own admin status." msgstr "" #, elixir-format -#: lib/pleroma/web/oauth/oauth_controller.ex:218 -#: lib/pleroma/web/oauth/oauth_controller.ex:309 +#: lib/pleroma/web/oauth/oauth_controller.ex:221 +#: lib/pleroma/web/oauth/oauth_controller.ex:308 msgid "Your account is currently disabled" msgstr "" #, elixir-format -#: lib/pleroma/web/oauth/oauth_controller.ex:180 -#: lib/pleroma/web/oauth/oauth_controller.ex:332 +#: lib/pleroma/web/oauth/oauth_controller.ex:183 +#: lib/pleroma/web/oauth/oauth_controller.ex:331 msgid "Your login is missing a confirmed e-mail address" msgstr "" #, elixir-format -#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390 msgid "can't read inbox of %{nickname} as %{as_nickname}" msgstr "" #, elixir-format -#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473 msgid "can't update outbox of %{nickname} as %{as_nickname}" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/common_api.ex:388 +#: lib/pleroma/web/common_api/common_api.ex:471 msgid "conversation is already muted" msgstr "" #, elixir-format -#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:316 -#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492 msgid "error" msgstr "" #, elixir-format -#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:29 +#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32 msgid "mascots can only be images" msgstr "" #, elixir-format -#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:60 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62 msgid "not found" msgstr "" #, elixir-format -#: lib/pleroma/web/oauth/oauth_controller.ex:395 +#: lib/pleroma/web/oauth/oauth_controller.ex:394 msgid "Bad OAuth request." msgstr "" @@ -375,17 +365,17 @@ msgid "CAPTCHA expired" msgstr "" #, elixir-format -#: lib/pleroma/plugs/uploaded_media.ex:55 +#: lib/pleroma/plugs/uploaded_media.ex:57 msgid "Failed" msgstr "" #, elixir-format -#: lib/pleroma/web/oauth/oauth_controller.ex:411 +#: lib/pleroma/web/oauth/oauth_controller.ex:410 msgid "Failed to authenticate: %{message}." msgstr "" #, elixir-format -#: lib/pleroma/web/oauth/oauth_controller.ex:442 +#: lib/pleroma/web/oauth/oauth_controller.ex:441 msgid "Failed to set up user account." msgstr "" @@ -395,7 +385,7 @@ msgid "Insufficient permissions: %{permissions}." msgstr "" #, elixir-format -#: lib/pleroma/plugs/uploaded_media.ex:94 +#: lib/pleroma/plugs/uploaded_media.ex:104 msgid "Internal Error" msgstr "" @@ -411,12 +401,12 @@ msgid "Invalid answer data" msgstr "" #, elixir-format -#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:128 +#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33 msgid "Nodeinfo schema version not handled" msgstr "" #, elixir-format -#: lib/pleroma/web/oauth/oauth_controller.ex:169 +#: lib/pleroma/web/oauth/oauth_controller.ex:172 msgid "This action is outside the authorized scopes" msgstr "" @@ -426,13 +416,13 @@ msgid "Unknown error, please check the details and try again." msgstr "" #, elixir-format -#: lib/pleroma/web/oauth/oauth_controller.ex:116 -#: lib/pleroma/web/oauth/oauth_controller.ex:155 +#: lib/pleroma/web/oauth/oauth_controller.ex:119 +#: lib/pleroma/web/oauth/oauth_controller.ex:158 msgid "Unlisted redirect_uri." msgstr "" #, elixir-format -#: lib/pleroma/web/oauth/oauth_controller.ex:391 +#: lib/pleroma/web/oauth/oauth_controller.ex:390 msgid "Unsupported OAuth provider: %{provider}." msgstr "" @@ -452,12 +442,12 @@ msgid "CAPTCHA Error" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/common_api.ex:200 +#: lib/pleroma/web/common_api/common_api.ex:290 msgid "Could not add reaction emoji" msgstr "" #, elixir-format -#: lib/pleroma/web/common_api/common_api.ex:211 +#: lib/pleroma/web/common_api/common_api.ex:301 msgid "Could not remove reaction emoji" msgstr "" @@ -472,39 +462,45 @@ msgid "List not found" msgstr "" #, elixir-format -#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:124 +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123 msgid "Missing parameter: %{name}" msgstr "" #, elixir-format -#: lib/pleroma/web/oauth/oauth_controller.ex:207 -#: lib/pleroma/web/oauth/oauth_controller.ex:322 +#: lib/pleroma/web/oauth/oauth_controller.ex:210 +#: lib/pleroma/web/oauth/oauth_controller.ex:321 msgid "Password reset is required" msgstr "" #, elixir-format #: lib/pleroma/tests/auth_test_controller.ex:9 -#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/admin_api_controller.ex:6 -#: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/fallback_redirect_controller.ex:6 -#: lib/pleroma/web/feed/tag_controller.ex:6 lib/pleroma/web/feed/user_controller.ex:6 -#: lib/pleroma/web/mailer/subscription_controller.ex:2 lib/pleroma/web/masto_fe_controller.ex:6 -#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6 -#: lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6 -#: lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6 -#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6 -#: lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6 -#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6 -#: lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14 lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6 -#: lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6 -#: lib/pleroma/web/mastodon_api/controllers/report_controller.ex:8 lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6 -#: lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6 -#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7 lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6 -#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6 lib/pleroma/web/media_proxy/media_proxy_controller.ex:6 -#: lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6 lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6 -#: lib/pleroma/web/oauth/fallback_controller.ex:6 lib/pleroma/web/oauth/mfa_controller.ex:10 -#: lib/pleroma/web/oauth/oauth_controller.ex:6 lib/pleroma/web/ostatus/ostatus_controller.ex:6 -#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:2 -#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex:6 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/config_controller.ex:6 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6 lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex:6 lib/pleroma/web/admin_api/controllers/relay_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/report_controller.ex:6 lib/pleroma/web/admin_api/controllers/status_controller.ex:6 +#: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/embed_controller.ex:6 +#: lib/pleroma/web/fallback_redirect_controller.ex:6 lib/pleroma/web/feed/tag_controller.ex:6 +#: lib/pleroma/web/feed/user_controller.ex:6 lib/pleroma/web/mailer/subscription_controller.ex:2 +#: lib/pleroma/web/masto_fe_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14 +#: lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/report_controller.ex:8 +#: lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7 +#: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6 +#: lib/pleroma/web/media_proxy/media_proxy_controller.ex:6 lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6 +#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6 lib/pleroma/web/oauth/fallback_controller.ex:6 +#: lib/pleroma/web/oauth/mfa_controller.ex:10 lib/pleroma/web/oauth/oauth_controller.ex:6 +#: lib/pleroma/web/ostatus/ostatus_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/chat_controller.ex:5 lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:2 lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6 #: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6 #: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/static_fe/static_fe_controller.ex:6 #: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6 @@ -519,46 +515,56 @@ msgid "Two-factor authentication enabled, you must use a access token." msgstr "" #, elixir-format -#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:210 +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210 msgid "Unexpected error occurred while adding file to pack." msgstr "" #, elixir-format -#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:138 +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138 msgid "Unexpected error occurred while creating pack." msgstr "" #, elixir-format -#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:278 +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278 msgid "Unexpected error occurred while removing file from pack." msgstr "" #, elixir-format -#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:250 +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250 msgid "Unexpected error occurred while updating file in pack." msgstr "" #, elixir-format -#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:179 +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179 msgid "Unexpected error occurred while updating pack metadata." msgstr "" -#, elixir-format -#: lib/pleroma/plugs/user_is_admin_plug.ex:40 -msgid "User is not an admin or OAuth admin scope is not granted." -msgstr "" - #, elixir-format #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 msgid "Web push subscription is disabled on this Pleroma instance" msgstr "" #, elixir-format -#: lib/pleroma/web/admin_api/admin_api_controller.ex:502 +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451 msgid "You can't revoke your own admin/moderator status." msgstr "" #, elixir-format -#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:105 +#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126 msgid "authorization required for timeline view" msgstr "" + +#, elixir-format +#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24 +msgid "Access denied" +msgstr "" + +#, elixir-format +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282 +msgid "This API requires an authenticated user" +msgstr "" + +#, elixir-format +#: lib/pleroma/plugs/user_is_admin_plug.ex:21 +msgid "User is not an admin." +msgstr "" diff --git a/priv/gettext/it/LC_MESSAGES/errors.po b/priv/gettext/it/LC_MESSAGES/errors.po index 726be628b..cd0cd6c65 100644 --- a/priv/gettext/it/LC_MESSAGES/errors.po +++ b/priv/gettext/it/LC_MESSAGES/errors.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-06-19 14:33+0000\n" -"PO-Revision-Date: 2020-06-19 20:38+0000\n" +"PO-Revision-Date: 2020-07-09 14:40+0000\n" "Last-Translator: Ben Is \n" "Language-Team: Italian \n" @@ -29,258 +29,258 @@ msgstr "non può essere nullo" ## From Ecto.Changeset.unique_constraint/3 msgid "has already been taken" -msgstr "" +msgstr "è stato già creato" ## From Ecto.Changeset.put_change/3 msgid "is invalid" -msgstr "" +msgstr "non è valido" ## From Ecto.Changeset.validate_format/3 msgid "has invalid format" -msgstr "" +msgstr "è in un formato invalido" ## From Ecto.Changeset.validate_subset/3 msgid "has an invalid entry" -msgstr "" +msgstr "ha una voce invalida" ## From Ecto.Changeset.validate_exclusion/3 msgid "is reserved" -msgstr "" +msgstr "è vietato" ## From Ecto.Changeset.validate_confirmation/3 msgid "does not match confirmation" -msgstr "" +msgstr "non corrisponde alla verifica" ## From Ecto.Changeset.no_assoc_constraint/3 msgid "is still associated with this entry" -msgstr "" +msgstr "è ancora associato con questa voce" msgid "are still associated with this entry" -msgstr "" +msgstr "sono ancora associati con questa voce" ## From Ecto.Changeset.validate_length/3 msgid "should be %{count} character(s)" msgid_plural "should be %{count} character(s)" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "dovrebbe essere %{count} carattere" +msgstr[1] "dovrebbero essere %{count} caratteri" msgid "should have %{count} item(s)" msgid_plural "should have %{count} item(s)" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "dovrebbe avere %{count} voce" +msgstr[1] "dovrebbe avere %{count} voci" msgid "should be at least %{count} character(s)" msgid_plural "should be at least %{count} character(s)" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "dovrebbe contenere almeno %{count} carattere" +msgstr[1] "dovrebbe contenere almeno %{count} caratteri" msgid "should have at least %{count} item(s)" msgid_plural "should have at least %{count} item(s)" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "dovrebbe avere almeno %{count} voce" +msgstr[1] "dovrebbe avere almeno %{count} voci" msgid "should be at most %{count} character(s)" msgid_plural "should be at most %{count} character(s)" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "dovrebbe avere al massimo %{count} carattere" +msgstr[1] "dovrebbe avere al massimo %{count} caratteri" msgid "should have at most %{count} item(s)" msgid_plural "should have at most %{count} item(s)" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "dovrebbe avere al massimo %{count} voce" +msgstr[1] "dovrebbe avere al massimo %{count} voci" ## From Ecto.Changeset.validate_number/3 msgid "must be less than %{number}" -msgstr "" +msgstr "dev'essere minore di %{number}" msgid "must be greater than %{number}" -msgstr "" +msgstr "dev'essere maggiore di %{number}" msgid "must be less than or equal to %{number}" -msgstr "" +msgstr "dev'essere minore o uguale a %{number}" msgid "must be greater than or equal to %{number}" -msgstr "" +msgstr "dev'essere maggiore o uguale a %{number}" msgid "must be equal to %{number}" -msgstr "" +msgstr "dev'essere uguale a %{number}" #: lib/pleroma/web/common_api/common_api.ex:421 #, elixir-format msgid "Account not found" -msgstr "" +msgstr "Profilo non trovato" #: lib/pleroma/web/common_api/common_api.ex:249 #, elixir-format msgid "Already voted" -msgstr "" +msgstr "Hai già votato" #: lib/pleroma/web/oauth/oauth_controller.ex:360 #, elixir-format msgid "Bad request" -msgstr "" +msgstr "Richiesta invalida" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425 #, elixir-format msgid "Can't delete object" -msgstr "" +msgstr "Non puoi eliminare quest'oggetto" #: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:196 #, elixir-format msgid "Can't delete this post" -msgstr "" +msgstr "Non puoi eliminare questo messaggio" #: lib/pleroma/web/controller_helper.ex:95 #: lib/pleroma/web/controller_helper.ex:101 #, elixir-format msgid "Can't display this activity" -msgstr "" +msgstr "Non puoi vedere questo elemento" #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:227 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:254 #, elixir-format msgid "Can't find user" -msgstr "" +msgstr "Non trovo questo utente" #: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:114 #, elixir-format msgid "Can't get favorites" -msgstr "" +msgstr "Non posso ricevere i gradimenti" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:437 #, elixir-format msgid "Can't like object" -msgstr "" +msgstr "Non posso gradire quest'oggetto" #: lib/pleroma/web/common_api/utils.ex:556 #, elixir-format msgid "Cannot post an empty status without attachments" -msgstr "" +msgstr "Non puoi pubblicare un messaggio vuoto senza allegati" #: lib/pleroma/web/common_api/utils.ex:504 #, elixir-format msgid "Comment must be up to %{max_size} characters" -msgstr "" +msgstr "I commenti posso al massimo consistere di %{max_size} caratteri" #: lib/pleroma/config/config_db.ex:222 #, elixir-format msgid "Config with params %{params} not found" -msgstr "" +msgstr "Configurazione con parametri %{max_size} non trovata" #: lib/pleroma/web/common_api/common_api.ex:95 #, elixir-format msgid "Could not delete" -msgstr "" +msgstr "Non eliminato" #: lib/pleroma/web/common_api/common_api.ex:141 #, elixir-format msgid "Could not favorite" -msgstr "" +msgstr "Non gradito" #: lib/pleroma/web/common_api/common_api.ex:370 #, elixir-format msgid "Could not pin" -msgstr "" +msgstr "Non intestato" #: lib/pleroma/web/common_api/common_api.ex:112 #, elixir-format msgid "Could not repeat" -msgstr "" +msgstr "Non ripetuto" #: lib/pleroma/web/common_api/common_api.ex:188 #, elixir-format msgid "Could not unfavorite" -msgstr "" +msgstr "Non sgradito" #: lib/pleroma/web/common_api/common_api.ex:380 #, elixir-format msgid "Could not unpin" -msgstr "" +msgstr "Non de-intestato" #: lib/pleroma/web/common_api/common_api.ex:126 #, elixir-format msgid "Could not unrepeat" -msgstr "" +msgstr "Non de-ripetuto" #: lib/pleroma/web/common_api/common_api.ex:428 #: lib/pleroma/web/common_api/common_api.ex:437 #, elixir-format msgid "Could not update state" -msgstr "" +msgstr "Non aggiornato" #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202 #, elixir-format msgid "Error." -msgstr "" +msgstr "Errore." #: lib/pleroma/web/twitter_api/twitter_api.ex:106 #, elixir-format msgid "Invalid CAPTCHA" -msgstr "" +msgstr "CAPTCHA invalido" #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:117 #: lib/pleroma/web/oauth/oauth_controller.ex:569 #, elixir-format msgid "Invalid credentials" -msgstr "" +msgstr "Credenziali invalide" #: lib/pleroma/plugs/ensure_authenticated_plug.ex:38 #, elixir-format msgid "Invalid credentials." -msgstr "" +msgstr "Credenziali invalide." #: lib/pleroma/web/common_api/common_api.ex:265 #, elixir-format msgid "Invalid indices" -msgstr "" +msgstr "Indici invalidi" #: lib/pleroma/web/admin_api/admin_api_controller.ex:1147 #, elixir-format msgid "Invalid parameters" -msgstr "" +msgstr "Parametri invalidi" #: lib/pleroma/web/common_api/utils.ex:411 #, elixir-format msgid "Invalid password." -msgstr "" +msgstr "Parola d'ordine invalida." #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:187 #, elixir-format msgid "Invalid request" -msgstr "" +msgstr "Richiesta invalida" #: lib/pleroma/web/twitter_api/twitter_api.ex:109 #, elixir-format msgid "Kocaptcha service unavailable" -msgstr "" +msgstr "Servizio Kocaptcha non disponibile" #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:113 #, elixir-format msgid "Missing parameters" -msgstr "" +msgstr "Parametri mancanti" #: lib/pleroma/web/common_api/utils.ex:540 #, elixir-format msgid "No such conversation" -msgstr "" +msgstr "Conversazione inesistente" #: lib/pleroma/web/admin_api/admin_api_controller.ex:439 #: lib/pleroma/web/admin_api/admin_api_controller.ex:465 lib/pleroma/web/admin_api/admin_api_controller.ex:507 #, elixir-format msgid "No such permission_group" -msgstr "" +msgstr "permission_group non esistente" #: lib/pleroma/plugs/uploaded_media.ex:74 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:485 lib/pleroma/web/admin_api/admin_api_controller.ex:1135 #: lib/pleroma/web/feed/user_controller.ex:73 lib/pleroma/web/ostatus/ostatus_controller.ex:143 #, elixir-format msgid "Not found" -msgstr "" +msgstr "Non trovato" #: lib/pleroma/web/common_api/common_api.ex:241 #, elixir-format msgid "Poll's author can't vote" -msgstr "" +msgstr "L'autore del sondaggio non può votare" #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20 #: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49 @@ -288,215 +288,215 @@ msgstr "" #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71 #, elixir-format msgid "Record not found" -msgstr "" +msgstr "Voce non trovata" #: lib/pleroma/web/admin_api/admin_api_controller.ex:1153 #: lib/pleroma/web/feed/user_controller.ex:79 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:32 #: lib/pleroma/web/ostatus/ostatus_controller.ex:149 #, elixir-format msgid "Something went wrong" -msgstr "" +msgstr "C'è stato un problema" #: lib/pleroma/web/common_api/activity_draft.ex:107 #, elixir-format msgid "The message visibility must be direct" -msgstr "" +msgstr "Il messaggio dev'essere privato" #: lib/pleroma/web/common_api/utils.ex:566 #, elixir-format msgid "The status is over the character limit" -msgstr "" +msgstr "Il messaggio ha superato la lunghezza massima" #: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31 #, elixir-format msgid "This resource requires authentication." -msgstr "" +msgstr "Accedi per leggere." #: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206 #, elixir-format msgid "Throttled" -msgstr "" +msgstr "Strozzato" #: lib/pleroma/web/common_api/common_api.ex:266 #, elixir-format msgid "Too many choices" -msgstr "" +msgstr "Troppe alternative" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:442 #, elixir-format msgid "Unhandled activity type" -msgstr "" +msgstr "Tipo di attività non gestibile" #: lib/pleroma/web/admin_api/admin_api_controller.ex:536 #, elixir-format msgid "You can't revoke your own admin status." -msgstr "" +msgstr "Non puoi divestirti da solo." #: lib/pleroma/web/oauth/oauth_controller.ex:218 #: lib/pleroma/web/oauth/oauth_controller.ex:309 #, elixir-format msgid "Your account is currently disabled" -msgstr "" +msgstr "Il tuo profilo è attualmente disabilitato" #: lib/pleroma/web/oauth/oauth_controller.ex:180 #: lib/pleroma/web/oauth/oauth_controller.ex:332 #, elixir-format msgid "Your login is missing a confirmed e-mail address" -msgstr "" +msgstr "Devi aggiungere un indirizzo email valido" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389 #, elixir-format msgid "can't read inbox of %{nickname} as %{as_nickname}" -msgstr "" +msgstr "non puoi leggere i messaggi privati di %{nickname} come %{as_nickname}" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472 #, elixir-format msgid "can't update outbox of %{nickname} as %{as_nickname}" -msgstr "" +msgstr "non puoi aggiornare gli inviati di %{nickname} come %{as_nickname}" #: lib/pleroma/web/common_api/common_api.ex:388 #, elixir-format msgid "conversation is already muted" -msgstr "" +msgstr "la conversazione è già zittita" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:316 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491 #, elixir-format msgid "error" -msgstr "" +msgstr "errore" #: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:29 #, elixir-format msgid "mascots can only be images" -msgstr "" +msgstr "le mascotte possono solo essere immagini" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:60 #, elixir-format msgid "not found" -msgstr "" +msgstr "non trovato" #: lib/pleroma/web/oauth/oauth_controller.ex:395 #, elixir-format msgid "Bad OAuth request." -msgstr "" +msgstr "Richiesta OAuth malformata." #: lib/pleroma/web/twitter_api/twitter_api.ex:115 #, elixir-format msgid "CAPTCHA already used" -msgstr "" +msgstr "CAPTCHA già utilizzato" #: lib/pleroma/web/twitter_api/twitter_api.ex:112 #, elixir-format msgid "CAPTCHA expired" -msgstr "" +msgstr "CAPTCHA scaduto" #: lib/pleroma/plugs/uploaded_media.ex:55 #, elixir-format msgid "Failed" -msgstr "" +msgstr "Fallito" #: lib/pleroma/web/oauth/oauth_controller.ex:411 #, elixir-format msgid "Failed to authenticate: %{message}." -msgstr "" +msgstr "Autenticazione fallita per: %{message}." #: lib/pleroma/web/oauth/oauth_controller.ex:442 #, elixir-format msgid "Failed to set up user account." -msgstr "" +msgstr "Profilo utente non creato." #: lib/pleroma/plugs/oauth_scopes_plug.ex:38 #, elixir-format msgid "Insufficient permissions: %{permissions}." -msgstr "" +msgstr "Permessi insufficienti: %{permissions}." #: lib/pleroma/plugs/uploaded_media.ex:94 #, elixir-format msgid "Internal Error" -msgstr "" +msgstr "Errore interno" #: lib/pleroma/web/oauth/fallback_controller.ex:22 #: lib/pleroma/web/oauth/fallback_controller.ex:29 #, elixir-format msgid "Invalid Username/Password" -msgstr "" +msgstr "Nome utente/parola d'ordine invalidi" #: lib/pleroma/web/twitter_api/twitter_api.ex:118 #, elixir-format msgid "Invalid answer data" -msgstr "" +msgstr "Risposta malformata" #: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:128 #, elixir-format msgid "Nodeinfo schema version not handled" -msgstr "" +msgstr "Versione schema nodeinfo non compatibile" #: lib/pleroma/web/oauth/oauth_controller.ex:169 #, elixir-format msgid "This action is outside the authorized scopes" -msgstr "" +msgstr "Quest'azione non è consentita in questa visibilità" #: lib/pleroma/web/oauth/fallback_controller.ex:14 #, elixir-format msgid "Unknown error, please check the details and try again." -msgstr "" +msgstr "Errore sconosciuto, controlla i dettagli e riprova." #: lib/pleroma/web/oauth/oauth_controller.ex:116 #: lib/pleroma/web/oauth/oauth_controller.ex:155 #, elixir-format msgid "Unlisted redirect_uri." -msgstr "" +msgstr "redirect_uri nascosto." #: lib/pleroma/web/oauth/oauth_controller.ex:391 #, elixir-format msgid "Unsupported OAuth provider: %{provider}." -msgstr "" +msgstr "Gestore OAuth non supportato: %{provider}." #: lib/pleroma/uploaders/uploader.ex:72 #, elixir-format msgid "Uploader callback timeout" -msgstr "" +msgstr "Callback caricatmento scaduta" #: lib/pleroma/web/uploader_controller.ex:23 #, elixir-format msgid "bad request" -msgstr "" +msgstr "richiesta malformata" #: lib/pleroma/web/twitter_api/twitter_api.ex:103 #, elixir-format msgid "CAPTCHA Error" -msgstr "" +msgstr "Errore CAPTCHA" #: lib/pleroma/web/common_api/common_api.ex:200 #, elixir-format msgid "Could not add reaction emoji" -msgstr "" +msgstr "Reazione emoji non riuscita" #: lib/pleroma/web/common_api/common_api.ex:211 #, elixir-format msgid "Could not remove reaction emoji" -msgstr "" +msgstr "Rimozione reazione non riuscita" #: lib/pleroma/web/twitter_api/twitter_api.ex:129 #, elixir-format msgid "Invalid CAPTCHA (Missing parameter: %{name})" -msgstr "" +msgstr "CAPTCHA invalido (Parametro mancante: %{name})" #: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92 #, elixir-format msgid "List not found" -msgstr "" +msgstr "Lista non trovata" #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:124 #, elixir-format msgid "Missing parameter: %{name}" -msgstr "" +msgstr "Parametro mancante: %{name}" #: lib/pleroma/web/oauth/oauth_controller.ex:207 #: lib/pleroma/web/oauth/oauth_controller.ex:322 #, elixir-format msgid "Password reset is required" -msgstr "" +msgstr "Necessario reimpostare parola d'ordine" #: lib/pleroma/tests/auth_test_controller.ex:9 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/admin_api_controller.ex:6 @@ -528,53 +528,58 @@ msgstr "" #, elixir-format msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped." msgstr "" +"Sicurezza violata: il controllo autorizzazioni di OAuth non è stato svolto " +"né saltato." #: lib/pleroma/plugs/ensure_authenticated_plug.ex:28 #, elixir-format msgid "Two-factor authentication enabled, you must use a access token." msgstr "" +"Autenticazione bifattoriale abilitata, devi utilizzare una chiave d'accesso." #: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:210 #, elixir-format msgid "Unexpected error occurred while adding file to pack." -msgstr "" +msgstr "Errore inaspettato durante l'aggiunta del file al pacchetto." #: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:138 #, elixir-format msgid "Unexpected error occurred while creating pack." -msgstr "" +msgstr "Errore inaspettato durante la creazione del pacchetto." #: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:278 #, elixir-format msgid "Unexpected error occurred while removing file from pack." -msgstr "" +msgstr "Errore inaspettato durante la rimozione del file dal pacchetto." #: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:250 #, elixir-format msgid "Unexpected error occurred while updating file in pack." -msgstr "" +msgstr "Errore inaspettato durante l'aggiornamento del file nel pacchetto." #: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:179 #, elixir-format msgid "Unexpected error occurred while updating pack metadata." -msgstr "" +msgstr "Errore inaspettato durante l'aggiornamento dei metadati del pacchetto." -#: lib/pleroma/plugs/user_is_admin_plug.ex:40 +#: lib/pleroma/plugs/user_is_admin_plug.ex:21 #, elixir-format -msgid "User is not an admin or OAuth admin scope is not granted." +msgid "User is not an admin." msgstr "" +"L'utente non è un amministratore." +"OAuth." #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 #, elixir-format msgid "Web push subscription is disabled on this Pleroma instance" -msgstr "" +msgstr "Gli aggiornamenti web push non sono disponibili in questa stanza" #: lib/pleroma/web/admin_api/admin_api_controller.ex:502 #, elixir-format msgid "You can't revoke your own admin/moderator status." -msgstr "" +msgstr "Non puoi divestire te stesso." #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:105 #, elixir-format msgid "authorization required for timeline view" -msgstr "" +msgstr "autorizzazione richiesta per vedere la sequenza" diff --git a/priv/gettext/nl/LC_MESSAGES/errors.po b/priv/gettext/nl/LC_MESSAGES/errors.po index 3118f6b5d..cfcb05fe6 100644 --- a/priv/gettext/nl/LC_MESSAGES/errors.po +++ b/priv/gettext/nl/LC_MESSAGES/errors.po @@ -559,9 +559,9 @@ msgstr "" msgid "Unexpected error occurred while updating pack metadata." msgstr "" -#: lib/pleroma/plugs/user_is_admin_plug.ex:40 +#: lib/pleroma/plugs/user_is_admin_plug.ex:21 #, elixir-format -msgid "User is not an admin or OAuth admin scope is not granted." +msgid "User is not an admin." msgstr "" #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 diff --git a/priv/gettext/pl/LC_MESSAGES/errors.po b/priv/gettext/pl/LC_MESSAGES/errors.po index 7bc39c52a..653ea00a1 100644 --- a/priv/gettext/pl/LC_MESSAGES/errors.po +++ b/priv/gettext/pl/LC_MESSAGES/errors.po @@ -3,8 +3,8 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-05-13 16:37+0000\n" -"PO-Revision-Date: 2020-05-16 17:13+0000\n" -"Last-Translator: Jędrzej Tomaszewski \n" +"PO-Revision-Date: 2020-07-09 14:40+0000\n" +"Last-Translator: Ben Is \n" "Language-Team: Polish \n" "Language: pl\n" @@ -50,7 +50,7 @@ msgstr "jest zarezerwowany" ## From Ecto.Changeset.validate_confirmation/3 msgid "does not match confirmation" -msgstr "" +msgstr "nie pasuje do potwierdzenia" ## From Ecto.Changeset.no_assoc_constraint/3 msgid "is still associated with this entry" @@ -566,9 +566,9 @@ msgstr "Nieoczekiwany błąd podczas zmieniania pliku w paczce." msgid "Unexpected error occurred while updating pack metadata." msgstr "Nieoczekiwany błąd podczas zmieniania metadanych paczki." -#: lib/pleroma/plugs/user_is_admin_plug.ex:40 +#: lib/pleroma/plugs/user_is_admin_plug.ex:21 #, elixir-format -msgid "User is not an admin or OAuth admin scope is not granted." +msgid "User is not an admin." msgstr "" #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 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/20200703101031_add_chat_acceptance_to_users.exs b/priv/repo/migrations/20200703101031_add_chat_acceptance_to_users.exs new file mode 100644 index 000000000..8dfda89f1 --- /dev/null +++ b/priv/repo/migrations/20200703101031_add_chat_acceptance_to_users.exs @@ -0,0 +1,17 @@ +defmodule Pleroma.Repo.Migrations.AddChatAcceptanceToUsers do + use Ecto.Migration + + def up do + alter table(:users) do + add(:accepts_chat_messages, :boolean, nullable: true) + end + + execute("update users set accepts_chat_messages = true where local = true") + end + + def down do + alter table(:users) do + remove(:accepts_chat_messages) + end + end +end diff --git a/priv/repo/migrations/20200708193702_drop_user_trigram_index.exs b/priv/repo/migrations/20200708193702_drop_user_trigram_index.exs new file mode 100644 index 000000000..94efe323a --- /dev/null +++ b/priv/repo/migrations/20200708193702_drop_user_trigram_index.exs @@ -0,0 +1,18 @@ +defmodule Pleroma.Repo.Migrations.DropUserTrigramIndex do + @moduledoc "Drops unused trigram index on `users` (FTS index is being used instead)" + + use Ecto.Migration + + def up do + drop_if_exists(index(:users, [], name: :users_trigram_index)) + end + + def down do + create_if_not_exists( + index(:users, ["(trim(nickname || ' ' || coalesce(name, ''))) gist_trgm_ops"], + name: :users_trigram_index, + using: :gist + ) + ) + end +end diff --git a/priv/repo/migrations/20200712234852_add_approval_fields_to_users.exs b/priv/repo/migrations/20200712234852_add_approval_fields_to_users.exs new file mode 100644 index 000000000..43f741a5b --- /dev/null +++ b/priv/repo/migrations/20200712234852_add_approval_fields_to_users.exs @@ -0,0 +1,10 @@ +defmodule Pleroma.Repo.Migrations.AddApprovalFieldsToUsers do + use Ecto.Migration + + def change do + alter table(:users) do + add(:approval_pending, :boolean) + add(:registration_reason, :text) + end + end +end diff --git a/priv/repo/migrations/20200714081657_oban_2_0_config_changes.exs b/priv/repo/migrations/20200714081657_oban_2_0_config_changes.exs new file mode 100644 index 000000000..c54bb2511 --- /dev/null +++ b/priv/repo/migrations/20200714081657_oban_2_0_config_changes.exs @@ -0,0 +1,27 @@ +defmodule Elixir.Pleroma.Repo.Migrations.Oban20ConfigChanges do + use Ecto.Migration + import Ecto.Query + alias Pleroma.ConfigDB + alias Pleroma.Repo + + def change do + config_entry = + from(c in ConfigDB, where: c.group == ^":pleroma" and c.key == ^"Oban") + |> select([c], struct(c, [:value, :id])) + |> Repo.one() + + if config_entry do + %{value: value} = config_entry + + value = + case Keyword.fetch(value, :verbose) do + {:ok, log} -> Keyword.put_new(value, :log, log) + _ -> value + end + |> Keyword.drop([:verbose, :prune]) + + Ecto.Changeset.change(config_entry, %{value: value}) + |> Repo.update() + end + end +end 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/repo/migrations/20200724133313_move_welcome_settings.exs b/priv/repo/migrations/20200724133313_move_welcome_settings.exs new file mode 100644 index 000000000..323a8fcee --- /dev/null +++ b/priv/repo/migrations/20200724133313_move_welcome_settings.exs @@ -0,0 +1,94 @@ +defmodule Pleroma.Repo.Migrations.MoveWelcomeSettings do + use Ecto.Migration + + alias Pleroma.ConfigDB + + @old_keys [:welcome_user_nickname, :welcome_message] + + def up do + with {:ok, config, {keep_values, move_values}} <- get_old_values() do + insert_welcome_settings(move_values) + update_instance_config(config, keep_values) + end + end + + def down do + with {:ok, welcome_config, revert_values} <- get_revert_values() do + revert_instance_config(revert_values) + Pleroma.Repo.delete(welcome_config) + end + end + + defp insert_welcome_settings([_ | _] = values) do + unless String.trim(values[:welcome_message]) == "" do + config_values = [ + direct_message: %{ + enabled: true, + sender_nickname: values[:welcome_user_nickname], + message: values[:welcome_message] + }, + email: %{ + enabled: false, + sender: nil, + subject: "Welcome to <%= instance_name %>", + html: "Welcome to <%= instance_name %>", + text: "Welcome to <%= instance_name %>" + } + ] + + {:ok, _} = + %ConfigDB{} + |> ConfigDB.changeset(%{group: :pleroma, key: :welcome, value: config_values}) + |> Pleroma.Repo.insert() + end + + :ok + end + + defp insert_welcome_settings(_), do: :noop + + defp revert_instance_config(%{} = revert_values) do + values = [ + welcome_user_nickname: revert_values[:sender_nickname], + welcome_message: revert_values[:message] + ] + + ConfigDB.update_or_create(%{group: :pleroma, key: :instance, value: values}) + end + + defp revert_instance_config(_), do: :noop + + defp update_instance_config(config, values) do + {:ok, _} = + config + |> ConfigDB.changeset(%{value: values}) + |> Pleroma.Repo.update() + + :ok + end + + defp get_revert_values do + config = ConfigDB.get_by_params(%{group: :pleroma, key: :welcome}) + + cond do + is_nil(config) -> {:noop, nil, nil} + true -> {:ok, config, config.value[:direct_message]} + end + end + + defp get_old_values do + config = ConfigDB.get_by_params(%{group: :pleroma, key: :instance}) + + cond do + is_nil(config) -> + {:noop, config, {}} + + is_binary(config.value[:welcome_message]) -> + {:ok, config, + {Keyword.drop(config.value, @old_keys), Keyword.take(config.value, @old_keys)}} + + true -> + {:ok, config, {Keyword.drop(config.value, @old_keys), []}} + end + end +end diff --git a/priv/static/index.html b/priv/static/index.html index 3ef4baa26..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/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld index 7cc3fee40..e7722cf72 100644 --- a/priv/static/schemas/litepub-0.1.jsonld +++ b/priv/static/schemas/litepub-0.1.jsonld @@ -13,6 +13,7 @@ }, "discoverable": "toot:discoverable", "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "capabilities": "litepub:capabilities", "ostatus": "http://ostatus.org#", "schema": "http://schema.org#", "toot": "http://joinmastodon.org/ns#", diff --git a/priv/static/static/css/app.493b9b5acee37ba97824.css.map b/priv/static/static/css/app.493b9b5acee37ba97824.css.map deleted file mode 100644 index 91399d605..000000000 --- a/priv/static/static/css/app.493b9b5acee37ba97824.css.map +++ /dev/null @@ -1 +0,0 @@ -{"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,C","file":"static/css/app.493b9b5acee37ba97824.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}"],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/css/app.493b9b5acee37ba97824.css b/priv/static/static/css/app.6dbc7dea4fc148c85860.css similarity index 98% rename from priv/static/static/css/app.493b9b5acee37ba97824.css rename to priv/static/static/css/app.6dbc7dea4fc148c85860.css index f30033af6..3927e3b77 100644 Binary files a/priv/static/static/css/app.493b9b5acee37ba97824.css and b/priv/static/static/css/app.6dbc7dea4fc148c85860.css differ diff --git a/priv/static/static/css/app.6dbc7dea4fc148c85860.css.map b/priv/static/static/css/app.6dbc7dea4fc148c85860.css.map new file mode 100644 index 000000000..963d5b3b8 --- /dev/null +++ b/priv/static/static/css/app.6dbc7dea4fc148c85860.css.map @@ -0,0 +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.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.1594134783339.woff b/priv/static/static/font/fontello.1594134783339.woff deleted file mode 100644 index 89a337131..000000000 Binary files a/priv/static/static/font/fontello.1594134783339.woff and /dev/null differ diff --git a/priv/static/static/font/fontello.1594134783339.woff2 b/priv/static/static/font/fontello.1594134783339.woff2 deleted file mode 100644 index 054169bd2..000000000 Binary files a/priv/static/static/font/fontello.1594134783339.woff2 and /dev/null differ diff --git a/priv/static/static/font/fontello.1594134783339.eot b/priv/static/static/font/fontello.1594823398494.eot similarity index 89% rename from priv/static/static/font/fontello.1594134783339.eot rename to priv/static/static/font/fontello.1594823398494.eot index bc98d606d..12e6beabf 100644 Binary files a/priv/static/static/font/fontello.1594134783339.eot and b/priv/static/static/font/fontello.1594823398494.eot differ diff --git a/priv/static/static/font/fontello.1594134783339.svg b/priv/static/static/font/fontello.1594823398494.svg similarity index 98% rename from priv/static/static/font/fontello.1594134783339.svg rename to priv/static/static/font/fontello.1594823398494.svg index a5342209d..71b5d70af 100644 --- a/priv/static/static/font/fontello.1594134783339.svg +++ b/priv/static/static/font/fontello.1594823398494.svg @@ -90,6 +90,8 @@ + + diff --git a/priv/static/static/font/fontello.1594134783339.ttf b/priv/static/static/font/fontello.1594823398494.ttf similarity index 89% rename from priv/static/static/font/fontello.1594134783339.ttf rename to priv/static/static/font/fontello.1594823398494.ttf index 458e88f9e..6f21845a8 100644 Binary files a/priv/static/static/font/fontello.1594134783339.ttf and b/priv/static/static/font/fontello.1594823398494.ttf differ diff --git a/priv/static/static/font/fontello.1594823398494.woff b/priv/static/static/font/fontello.1594823398494.woff new file mode 100644 index 000000000..a7cd098f4 Binary files /dev/null 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.1594823398494.css similarity index 89% rename from priv/static/static/fontello.1594134783339.css rename to priv/static/static/fontello.1594823398494.css index ff35edaba..fe61b94c6 100644 Binary files a/priv/static/static/fontello.1594134783339.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 index 5ef8544e2..706800cdb --- a/priv/static/static/fontello.json +++ b/priv/static/static/fontello.json @@ -399,6 +399,12 @@ "css": "doc", "code": 59433, "src": "fontawesome" + }, + { + "uid": "98d9c83c1ee7c2c25af784b518c522c5", + "css": "block", + "code": 59434, + "src": "fontawesome" } ] } \ No newline at end of file diff --git a/priv/static/static/js/10.4a22c77e34edcd678d2f.js b/priv/static/static/js/10.4a22c77e34edcd678d2f.js deleted file mode 100644 index a1c395c42..000000000 Binary files a/priv/static/static/js/10.4a22c77e34edcd678d2f.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.4a22c77e34edcd678d2f.js.map b/priv/static/static/js/10.5ef4671883649cf93524.js.map similarity index 56% rename from priv/static/static/js/10.4a22c77e34edcd678d2f.js.map rename to priv/static/static/js/10.5ef4671883649cf93524.js.map index 9c8b5e658..95fa2207e 100644 Binary files a/priv/static/static/js/10.4a22c77e34edcd678d2f.js.map and b/priv/static/static/js/10.5ef4671883649cf93524.js.map differ diff --git a/priv/static/static/js/11.787aa24e4fd5caef9adb.js b/priv/static/static/js/11.787aa24e4fd5caef9adb.js deleted file mode 100644 index 938cbb64e..000000000 Binary files a/priv/static/static/js/11.787aa24e4fd5caef9adb.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.787aa24e4fd5caef9adb.js.map b/priv/static/static/js/11.c5b938b4349f87567338.js.map similarity index 56% rename from priv/static/static/js/11.787aa24e4fd5caef9adb.js.map rename to priv/static/static/js/11.c5b938b4349f87567338.js.map index e376a0bbc..5ccf83b1d 100644 Binary files a/priv/static/static/js/11.787aa24e4fd5caef9adb.js.map and b/priv/static/static/js/11.c5b938b4349f87567338.js.map differ diff --git a/priv/static/static/js/12.35a510cf14233f0c6e1f.js b/priv/static/static/js/12.35a510cf14233f0c6e1f.js deleted file mode 100644 index fe4799a2d..000000000 Binary files a/priv/static/static/js/12.35a510cf14233f0c6e1f.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.35a510cf14233f0c6e1f.js.map b/priv/static/static/js/12.ab82f9512fa85e78c114.js.map similarity index 56% rename from priv/static/static/js/12.35a510cf14233f0c6e1f.js.map rename to priv/static/static/js/12.ab82f9512fa85e78c114.js.map index 21cc55e6f..23335ae23 100644 Binary files a/priv/static/static/js/12.35a510cf14233f0c6e1f.js.map and b/priv/static/static/js/12.ab82f9512fa85e78c114.js.map 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.7931a609d62a42678085.js.map b/priv/static/static/js/13.40e59c5015d3307b94ad.js.map similarity index 56% rename from priv/static/static/js/13.7931a609d62a42678085.js.map rename to priv/static/static/js/13.40e59c5015d3307b94ad.js.map index 8448af376..3931b5ef9 100644 Binary files a/priv/static/static/js/13.7931a609d62a42678085.js.map and b/priv/static/static/js/13.40e59c5015d3307b94ad.js.map differ diff --git a/priv/static/static/js/13.7931a609d62a42678085.js b/priv/static/static/js/13.7931a609d62a42678085.js deleted file mode 100644 index 3fe95c23c..000000000 Binary files a/priv/static/static/js/13.7931a609d62a42678085.js and /dev/null differ diff --git a/priv/static/static/js/14.cc092634462fd2a4cfbc.js b/priv/static/static/js/14.cc092634462fd2a4cfbc.js deleted file mode 100644 index 42a179970..000000000 Binary files a/priv/static/static/js/14.cc092634462fd2a4cfbc.js and /dev/null differ diff --git a/priv/static/static/js/14.cc092634462fd2a4cfbc.js.map b/priv/static/static/js/14.cc092634462fd2a4cfbc.js.map deleted file mode 100644 index 97e151b4e..000000000 Binary files a/priv/static/static/js/14.cc092634462fd2a4cfbc.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.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/15.e9ddc5dfd38426398e00.js b/priv/static/static/js/15.e9ddc5dfd38426398e00.js deleted file mode 100644 index f03e74897..000000000 Binary files a/priv/static/static/js/15.e9ddc5dfd38426398e00.js and /dev/null differ diff --git a/priv/static/static/js/15.e9ddc5dfd38426398e00.js.map b/priv/static/static/js/15.e9ddc5dfd38426398e00.js.map deleted file mode 100644 index 6c0c32949..000000000 Binary files a/priv/static/static/js/15.e9ddc5dfd38426398e00.js.map and /dev/null differ diff --git a/priv/static/static/js/16.476e7809b8593264469e.js b/priv/static/static/js/16.476e7809b8593264469e.js deleted file mode 100644 index 2cd6c9c3e..000000000 Binary files a/priv/static/static/js/16.476e7809b8593264469e.js and /dev/null differ diff --git a/priv/static/static/js/16.476e7809b8593264469e.js.map b/priv/static/static/js/16.476e7809b8593264469e.js.map deleted file mode 100644 index b62e1e0f4..000000000 Binary files a/priv/static/static/js/16.476e7809b8593264469e.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.acbe4c09f05ae56c76a2.js b/priv/static/static/js/17.acbe4c09f05ae56c76a2.js deleted file mode 100644 index 8e4d6181e..000000000 Binary files a/priv/static/static/js/17.acbe4c09f05ae56c76a2.js and /dev/null differ diff --git a/priv/static/static/js/17.acbe4c09f05ae56c76a2.js.map b/priv/static/static/js/17.acbe4c09f05ae56c76a2.js.map deleted file mode 100644 index 92bc141e5..000000000 Binary files a/priv/static/static/js/17.acbe4c09f05ae56c76a2.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.a8ccd7f2a47c5c94b3b9.js b/priv/static/static/js/18.a8ccd7f2a47c5c94b3b9.js deleted file mode 100644 index d52319d30..000000000 Binary files a/priv/static/static/js/18.a8ccd7f2a47c5c94b3b9.js and /dev/null differ diff --git a/priv/static/static/js/18.a8ccd7f2a47c5c94b3b9.js.map b/priv/static/static/js/18.a8ccd7f2a47c5c94b3b9.js.map deleted file mode 100644 index e751cf19c..000000000 Binary files a/priv/static/static/js/18.a8ccd7f2a47c5c94b3b9.js.map and /dev/null differ diff --git a/priv/static/static/js/19.5894e9c12b4fd5e45872.js b/priv/static/static/js/19.5894e9c12b4fd5e45872.js deleted file mode 100644 index f30cebacf..000000000 Binary files a/priv/static/static/js/19.5894e9c12b4fd5e45872.js and /dev/null differ diff --git a/priv/static/static/js/19.5894e9c12b4fd5e45872.js.map b/priv/static/static/js/19.5894e9c12b4fd5e45872.js.map deleted file mode 100644 index 3e00e0045..000000000 Binary files a/priv/static/static/js/19.5894e9c12b4fd5e45872.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.f8dee9318a6f84ea92c3.js b/priv/static/static/js/2.f8dee9318a6f84ea92c3.js deleted file mode 100644 index b9f190615..000000000 Binary files a/priv/static/static/js/2.f8dee9318a6f84ea92c3.js and /dev/null differ diff --git a/priv/static/static/js/2.f8dee9318a6f84ea92c3.js.map b/priv/static/static/js/2.f8dee9318a6f84ea92c3.js.map deleted file mode 100644 index 8f4e8920a..000000000 Binary files a/priv/static/static/js/2.f8dee9318a6f84ea92c3.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.43b5b27b0f68474f3b72.js b/priv/static/static/js/20.43b5b27b0f68474f3b72.js deleted file mode 100644 index 2b2b5bf60..000000000 Binary files a/priv/static/static/js/20.43b5b27b0f68474f3b72.js and /dev/null differ diff --git a/priv/static/static/js/20.43b5b27b0f68474f3b72.js.map b/priv/static/static/js/20.43b5b27b0f68474f3b72.js.map deleted file mode 100644 index 224627821..000000000 Binary files a/priv/static/static/js/20.43b5b27b0f68474f3b72.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.72b45b01be9d0f4c62ce.js b/priv/static/static/js/21.72b45b01be9d0f4c62ce.js deleted file mode 100644 index 87292772b..000000000 Binary files a/priv/static/static/js/21.72b45b01be9d0f4c62ce.js and /dev/null differ diff --git a/priv/static/static/js/21.72b45b01be9d0f4c62ce.js.map b/priv/static/static/js/21.72b45b01be9d0f4c62ce.js.map deleted file mode 100644 index f7c2b5352..000000000 Binary files a/priv/static/static/js/21.72b45b01be9d0f4c62ce.js.map and /dev/null differ diff --git a/priv/static/static/js/22.26f13a22ad57a0d14670.js b/priv/static/static/js/22.26f13a22ad57a0d14670.js deleted file mode 100644 index a12b55b1f..000000000 Binary files a/priv/static/static/js/22.26f13a22ad57a0d14670.js and /dev/null differ diff --git a/priv/static/static/js/22.26f13a22ad57a0d14670.js.map b/priv/static/static/js/22.26f13a22ad57a0d14670.js.map deleted file mode 100644 index fa09661dc..000000000 Binary files a/priv/static/static/js/22.26f13a22ad57a0d14670.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/23.91a60b775352a806f887.js b/priv/static/static/js/23.91a60b775352a806f887.js deleted file mode 100644 index c4f18071c..000000000 Binary files a/priv/static/static/js/23.91a60b775352a806f887.js and /dev/null differ diff --git a/priv/static/static/js/23.91a60b775352a806f887.js.map b/priv/static/static/js/23.91a60b775352a806f887.js.map deleted file mode 100644 index 656b87b51..000000000 Binary files a/priv/static/static/js/23.91a60b775352a806f887.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.c8d8438aac954d4707ac.js b/priv/static/static/js/24.c8d8438aac954d4707ac.js deleted file mode 100644 index 0029d5b8a..000000000 Binary files a/priv/static/static/js/24.c8d8438aac954d4707ac.js and /dev/null differ diff --git a/priv/static/static/js/24.c8d8438aac954d4707ac.js.map b/priv/static/static/js/24.c8d8438aac954d4707ac.js.map deleted file mode 100644 index 1a2bb1dfd..000000000 Binary files a/priv/static/static/js/24.c8d8438aac954d4707ac.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.79ac9e020d571b67f02a.js b/priv/static/static/js/25.79ac9e020d571b67f02a.js deleted file mode 100644 index 7798e9e7e..000000000 Binary files a/priv/static/static/js/25.79ac9e020d571b67f02a.js and /dev/null differ diff --git a/priv/static/static/js/25.79ac9e020d571b67f02a.js.map b/priv/static/static/js/25.79ac9e020d571b67f02a.js.map deleted file mode 100644 index 5cd7d6b0c..000000000 Binary files a/priv/static/static/js/25.79ac9e020d571b67f02a.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.3af8f54349f672f2c7c8.js b/priv/static/static/js/26.3af8f54349f672f2c7c8.js deleted file mode 100644 index ea37ad7d1..000000000 Binary files a/priv/static/static/js/26.3af8f54349f672f2c7c8.js and /dev/null differ diff --git a/priv/static/static/js/26.3af8f54349f672f2c7c8.js.map b/priv/static/static/js/26.3af8f54349f672f2c7c8.js.map deleted file mode 100644 index b30d820f8..000000000 Binary files a/priv/static/static/js/26.3af8f54349f672f2c7c8.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.51287d408313da67b0b8.js b/priv/static/static/js/27.51287d408313da67b0b8.js deleted file mode 100644 index bbed0b854..000000000 Binary files a/priv/static/static/js/27.51287d408313da67b0b8.js and /dev/null differ diff --git a/priv/static/static/js/27.51287d408313da67b0b8.js.map b/priv/static/static/js/27.51287d408313da67b0b8.js.map deleted file mode 100644 index 074c63e2e..000000000 Binary files a/priv/static/static/js/27.51287d408313da67b0b8.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/28.be5118beb1098a81332d.js b/priv/static/static/js/28.be5118beb1098a81332d.js deleted file mode 100644 index 30a6546eb..000000000 Binary files a/priv/static/static/js/28.be5118beb1098a81332d.js and /dev/null differ diff --git a/priv/static/static/js/28.be5118beb1098a81332d.js.map b/priv/static/static/js/28.be5118beb1098a81332d.js.map deleted file mode 100644 index 57e1d7124..000000000 Binary files a/priv/static/static/js/28.be5118beb1098a81332d.js.map and /dev/null differ diff --git a/priv/static/static/js/29.084f6fb0987d3862d410.js b/priv/static/static/js/29.084f6fb0987d3862d410.js deleted file mode 100644 index 0a92f928a..000000000 Binary files a/priv/static/static/js/29.084f6fb0987d3862d410.js and /dev/null differ diff --git a/priv/static/static/js/29.084f6fb0987d3862d410.js.map b/priv/static/static/js/29.084f6fb0987d3862d410.js.map deleted file mode 100644 index c977b4c84..000000000 Binary files a/priv/static/static/js/29.084f6fb0987d3862d410.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/3.e1f7d368d5840e12e850.js b/priv/static/static/js/3.56898c1005d9ba1b8d4a.js similarity index 99% rename from priv/static/static/js/3.e1f7d368d5840e12e850.js rename to priv/static/static/js/3.56898c1005d9ba1b8d4a.js index 18212aa8f..6b20ecb04 100644 Binary files a/priv/static/static/js/3.e1f7d368d5840e12e850.js and b/priv/static/static/js/3.56898c1005d9ba1b8d4a.js differ diff --git a/priv/static/static/js/3.e1f7d368d5840e12e850.js.map b/priv/static/static/js/3.56898c1005d9ba1b8d4a.js.map similarity index 99% rename from priv/static/static/js/3.e1f7d368d5840e12e850.js.map rename to priv/static/static/js/3.56898c1005d9ba1b8d4a.js.map index 1d1dd7f3f..594d9047b 100644 Binary files a/priv/static/static/js/3.e1f7d368d5840e12e850.js.map and b/priv/static/static/js/3.56898c1005d9ba1b8d4a.js.map differ diff --git a/priv/static/static/js/30.6e6d63411def2e175d11.js b/priv/static/static/js/30.6e6d63411def2e175d11.js deleted file mode 100644 index df379aaa7..000000000 Binary files a/priv/static/static/js/30.6e6d63411def2e175d11.js and /dev/null differ diff --git a/priv/static/static/js/30.6e6d63411def2e175d11.js.map b/priv/static/static/js/30.6e6d63411def2e175d11.js.map deleted file mode 100644 index ebd9270dc..000000000 Binary files a/priv/static/static/js/30.6e6d63411def2e175d11.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/4.c3f92d0b6ff90b36e3f5.js b/priv/static/static/js/4.2d3bef896b463484e6eb.js similarity index 77% rename from priv/static/static/js/4.c3f92d0b6ff90b36e3f5.js rename to priv/static/static/js/4.2d3bef896b463484e6eb.js index 98ea02539..4a611feb4 100644 Binary files a/priv/static/static/js/4.c3f92d0b6ff90b36e3f5.js and b/priv/static/static/js/4.2d3bef896b463484e6eb.js differ diff --git a/priv/static/static/js/4.c3f92d0b6ff90b36e3f5.js.map b/priv/static/static/js/4.2d3bef896b463484e6eb.js.map similarity index 99% rename from priv/static/static/js/4.c3f92d0b6ff90b36e3f5.js.map rename to priv/static/static/js/4.2d3bef896b463484e6eb.js.map index 261abbb00..ebcc883e5 100644 Binary files a/priv/static/static/js/4.c3f92d0b6ff90b36e3f5.js.map and b/priv/static/static/js/4.2d3bef896b463484e6eb.js.map 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.d30e50cd5c52d54ffdc9.js.map b/priv/static/static/js/5.84f3dce298bc720719c7.js.map similarity index 57% rename from priv/static/static/js/5.d30e50cd5c52d54ffdc9.js.map rename to priv/static/static/js/5.84f3dce298bc720719c7.js.map index 1eb455744..4fcc32982 100644 Binary files a/priv/static/static/js/5.d30e50cd5c52d54ffdc9.js.map and b/priv/static/static/js/5.84f3dce298bc720719c7.js.map differ diff --git a/priv/static/static/js/5.d30e50cd5c52d54ffdc9.js b/priv/static/static/js/5.d30e50cd5c52d54ffdc9.js deleted file mode 100644 index ce3eb2018..000000000 Binary files a/priv/static/static/js/5.d30e50cd5c52d54ffdc9.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.fa6d5c2d85d44f0ba121.js.map b/priv/static/static/js/6.b9497e1d403b901a664e.js.map similarity index 57% rename from priv/static/static/js/6.fa6d5c2d85d44f0ba121.js.map rename to priv/static/static/js/6.b9497e1d403b901a664e.js.map index 074cf0fe2..5af0576c7 100644 Binary files a/priv/static/static/js/6.fa6d5c2d85d44f0ba121.js.map and b/priv/static/static/js/6.b9497e1d403b901a664e.js.map differ diff --git a/priv/static/static/js/6.fa6d5c2d85d44f0ba121.js b/priv/static/static/js/6.fa6d5c2d85d44f0ba121.js deleted file mode 100644 index a80504f6e..000000000 Binary files a/priv/static/static/js/6.fa6d5c2d85d44f0ba121.js and /dev/null 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.d558a086622f668601a6.js.map b/priv/static/static/js/7.455b574116ce3f004ffb.js.map similarity index 57% rename from priv/static/static/js/7.d558a086622f668601a6.js.map rename to priv/static/static/js/7.455b574116ce3f004ffb.js.map index cd515dac0..971b570e1 100644 Binary files a/priv/static/static/js/7.d558a086622f668601a6.js.map and b/priv/static/static/js/7.455b574116ce3f004ffb.js.map differ diff --git a/priv/static/static/js/7.d558a086622f668601a6.js b/priv/static/static/js/7.d558a086622f668601a6.js deleted file mode 100644 index c948ae6d1..000000000 Binary files a/priv/static/static/js/7.d558a086622f668601a6.js and /dev/null differ diff --git a/priv/static/static/js/8.615136ce6c34a6b96a29.js b/priv/static/static/js/8.615136ce6c34a6b96a29.js deleted file mode 100644 index 255f924d3..000000000 Binary files a/priv/static/static/js/8.615136ce6c34a6b96a29.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.615136ce6c34a6b96a29.js.map b/priv/static/static/js/8.8db9f2dcc5ed429777f7.js.map similarity index 57% rename from priv/static/static/js/8.615136ce6c34a6b96a29.js.map rename to priv/static/static/js/8.8db9f2dcc5ed429777f7.js.map index f2620b135..e193375de 100644 Binary files a/priv/static/static/js/8.615136ce6c34a6b96a29.js.map and b/priv/static/static/js/8.8db9f2dcc5ed429777f7.js.map 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/9.ef4eb9703f9aee67515e.js b/priv/static/static/js/9.ef4eb9703f9aee67515e.js deleted file mode 100644 index 2d1e741d9..000000000 Binary files a/priv/static/static/js/9.ef4eb9703f9aee67515e.js and /dev/null differ diff --git a/priv/static/static/js/9.ef4eb9703f9aee67515e.js.map b/priv/static/static/js/9.ef4eb9703f9aee67515e.js.map deleted file mode 100644 index 3491916ca..000000000 Binary files a/priv/static/static/js/9.ef4eb9703f9aee67515e.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/app.53001fa190f37cf2743e.js b/priv/static/static/js/app.53001fa190f37cf2743e.js deleted file mode 100644 index 45f3a8373..000000000 Binary files a/priv/static/static/js/app.53001fa190f37cf2743e.js and /dev/null differ diff --git a/priv/static/static/js/app.53001fa190f37cf2743e.js.map b/priv/static/static/js/app.53001fa190f37cf2743e.js.map deleted file mode 100644 index 105b669c9..000000000 Binary files a/priv/static/static/js/app.53001fa190f37cf2743e.js.map and /dev/null differ diff --git a/priv/static/static/js/vendors~app.8837fb59589d1dd6acda.js.map b/priv/static/static/js/vendors~app.8837fb59589d1dd6acda.js.map deleted file mode 100644 index da281465a..000000000 Binary files a/priv/static/static/js/vendors~app.8837fb59589d1dd6acda.js.map and /dev/null differ diff --git a/priv/static/static/js/vendors~app.8837fb59589d1dd6acda.js b/priv/static/static/js/vendors~app.9e24ed238da5a8538f50.js similarity index 62% rename from priv/static/static/js/vendors~app.8837fb59589d1dd6acda.js rename to priv/static/static/js/vendors~app.9e24ed238da5a8538f50.js index 365dc3dc4..76c8a8dc1 100644 Binary files a/priv/static/static/js/vendors~app.8837fb59589d1dd6acda.js and b/priv/static/static/js/vendors~app.9e24ed238da5a8538f50.js differ diff --git a/priv/static/static/js/vendors~app.9e24ed238da5a8538f50.js.map b/priv/static/static/js/vendors~app.9e24ed238da5a8538f50.js.map new file mode 100644 index 000000000..f3c067c15 Binary files /dev/null and b/priv/static/static/js/vendors~app.9e24ed238da5a8538f50.js.map differ diff --git a/priv/static/static/terms-of-service.html b/priv/static/static/terms-of-service.html index b2c668151..3b6bbb36b 100644 --- a/priv/static/static/terms-of-service.html +++ b/priv/static/static/terms-of-service.html @@ -2,7 +2,7 @@

This is the default placeholder ToS. You should copy it over to your static folder and edit it to fit the needs of your instance.

-

To do so, place a file at "/instance/static/terms-of-service.html" in your +

To do so, place a file at "/instance/static/static/terms-of-service.html" in your Pleroma install containing the real ToS for your instance.

See the Pleroma documentation for more information.


diff --git a/priv/static/sw-pleroma.js b/priv/static/sw-pleroma.js index 22b99ea22..9b7d127fd 100644 Binary files a/priv/static/sw-pleroma.js and b/priv/static/sw-pleroma.js differ diff --git a/priv/static/sw-pleroma.js.map b/priv/static/sw-pleroma.js.map index 55846489e..5749809d5 100644 Binary files a/priv/static/sw-pleroma.js.map and b/priv/static/sw-pleroma.js.map differ diff --git a/test/activity_expiration_test.exs b/test/activity_expiration_test.exs index e899d4509..d75c06cc7 100644 --- a/test/activity_expiration_test.exs +++ b/test/activity_expiration_test.exs @@ -44,7 +44,7 @@ test "deletes an expiration activity" do %{activity_id: activity.id, scheduled_at: naive_datetime} ) - Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(:ops, :pid) + Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(%Oban.Job{}) refute Pleroma.Repo.get(Pleroma.Activity, activity.id) refute Pleroma.Repo.get(Pleroma.ActivityExpiration, expiration.id) diff --git a/test/application_requirements_test.exs b/test/application_requirements_test.exs index 481cdfd73..21d24ddd0 100644 --- a/test/application_requirements_test.exs +++ b/test/application_requirements_test.exs @@ -9,6 +9,56 @@ defmodule Pleroma.ApplicationRequirementsTest do alias Pleroma.Repo + describe "check_welcome_message_config!/1" do + setup do: clear_config([:welcome]) + setup do: clear_config([Pleroma.Emails.Mailer]) + + test "raises if welcome email enabled but mail disabled" do + Pleroma.Config.put([:welcome, :email, :enabled], true) + Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false) + + assert_raise Pleroma.ApplicationRequirements.VerifyError, "The mail disabled.", fn -> + capture_log(&Pleroma.ApplicationRequirements.verify!/0) + end + end + end + + 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/captcha_test.exs b/test/captcha_test.exs index 1ab9019ab..1b9f4a12f 100644 --- a/test/captcha_test.exs +++ b/test/captcha_test.exs @@ -41,7 +41,8 @@ test "new and validate" do answer_data: answer, token: ^token, url: ^url, - type: :kocaptcha + type: :kocaptcha, + seconds_valid: 300 } = new assert Kocaptcha.validate(token, "7oEy8c", answer) == :ok @@ -56,7 +57,8 @@ test "new and validate" do answer_data: answer, token: token, type: :native, - url: "data:image/png;base64," <> _ + url: "data:image/png;base64," <> _, + seconds_valid: 300 } = new assert is_binary(answer) diff --git a/test/config/deprecation_warnings_test.exs b/test/config/deprecation_warnings_test.exs index 548ee87b0..555661a71 100644 --- a/test/config/deprecation_warnings_test.exs +++ b/test/config/deprecation_warnings_test.exs @@ -54,4 +54,12 @@ test "move_namespace_and_warn/2" do assert Pleroma.Config.get(new_group2) == 2 assert Pleroma.Config.get(new_group3) == 3 end + + test "check_media_proxy_whitelist_config/0" do + clear_config([:media_proxy, :whitelist], ["https://example.com", "example2.com"]) + + assert capture_log(fn -> + Pleroma.Config.DeprecationWarnings.check_media_proxy_whitelist_config() + end) =~ "Your config is using old format (only domain) for MediaProxy whitelist option" + end end diff --git a/test/docs/generator_test.exs b/test/docs/generator_test.exs index 9c9f4357b..b32918a69 100644 --- a/test/docs/generator_test.exs +++ b/test/docs/generator_test.exs @@ -13,21 +13,13 @@ defmodule Pleroma.Docs.GeneratorTest do key: :uploader, type: :module, description: "", - suggestions: - Generator.list_modules_in_dir( - "lib/pleroma/upload/filter", - "Elixir.Pleroma.Upload.Filter." - ) + suggestions: {:list_behaviour_implementations, Pleroma.Upload.Filter} }, %{ key: :filters, type: {:list, :module}, description: "", - suggestions: - Generator.list_modules_in_dir( - "lib/pleroma/web/activity_pub/mrf", - "Elixir.Pleroma.Web.ActivityPub.MRF." - ) + suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF} }, %{ key: Pleroma.Upload, diff --git a/test/emails/admin_email_test.exs b/test/emails/admin_email_test.exs index 9082ae5a7..e24231e27 100644 --- a/test/emails/admin_email_test.exs +++ b/test/emails/admin_email_test.exs @@ -46,4 +46,24 @@ test "it works when the reporter is a remote user without email" do assert res.to == [{to_user.name, to_user.email}] assert res.from == {config[:name], config[:notify_email]} end + + test "new unapproved registration email" do + config = Pleroma.Config.get(:instance) + to_user = insert(:user) + account = insert(:user, registration_reason: "Plz let me in") + + res = AdminEmail.new_unapproved_registration(to_user, account) + + account_url = Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, account.id) + + assert res.to == [{to_user.name, to_user.email}] + assert res.from == {config[:name], config[:notify_email]} + assert res.subject == "New account up for review on #{config[:name]} (@#{account.nickname})" + + assert res.html_body == """ +

New account for review: @#{account.nickname}

+
Plz let me in
+ Visit AdminFE + """ + end end diff --git a/test/fixtures/DSCN0010.jpg b/test/fixtures/DSCN0010.jpg new file mode 100644 index 000000000..4a2c1552b Binary files /dev/null and b/test/fixtures/DSCN0010.jpg differ diff --git a/test/fixtures/tesla_mock/admin@mastdon.example.org.json b/test/fixtures/tesla_mock/admin@mastdon.example.org.json index 9fdd6557c..a911b979a 100644 --- a/test/fixtures/tesla_mock/admin@mastdon.example.org.json +++ b/test/fixtures/tesla_mock/admin@mastdon.example.org.json @@ -26,6 +26,9 @@ "summary": "\u003cp\u003e\u003c/p\u003e", "url": "http://mastodon.example.org/@admin", "manuallyApprovesFollowers": false, + "capabilities": { + "acceptsChatMessages": true + }, "publicKey": { "id": "http://mastodon.example.org/users/admin#main-key", "owner": "http://mastodon.example.org/users/admin", 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/migrations/20200724133313_move_welcome_settings_test.exs b/test/migrations/20200724133313_move_welcome_settings_test.exs new file mode 100644 index 000000000..739f24547 --- /dev/null +++ b/test/migrations/20200724133313_move_welcome_settings_test.exs @@ -0,0 +1,140 @@ +defmodule Pleroma.Repo.Migrations.MoveWelcomeSettingsTest do + use Pleroma.DataCase + import Pleroma.Factory + import Pleroma.Tests.Helpers + alias Pleroma.ConfigDB + + setup_all do: require_migration("20200724133313_move_welcome_settings") + + describe "up/0" do + test "converts welcome settings", %{migration: migration} do + insert(:config, + group: :pleroma, + key: :instance, + value: [ + welcome_message: "Test message", + welcome_user_nickname: "jimm", + name: "Pleroma" + ] + ) + + migration.up() + instance_config = ConfigDB.get_by_params(%{group: :pleroma, key: :instance}) + welcome_config = ConfigDB.get_by_params(%{group: :pleroma, key: :welcome}) + + assert instance_config.value == [name: "Pleroma"] + + assert welcome_config.value == [ + direct_message: %{ + enabled: true, + message: "Test message", + sender_nickname: "jimm" + }, + email: %{ + enabled: false, + html: "Welcome to <%= instance_name %>", + sender: nil, + subject: "Welcome to <%= instance_name %>", + text: "Welcome to <%= instance_name %>" + } + ] + end + + test "does nothing when message empty", %{migration: migration} do + insert(:config, + group: :pleroma, + key: :instance, + value: [ + welcome_message: "", + welcome_user_nickname: "jimm", + name: "Pleroma" + ] + ) + + migration.up() + instance_config = ConfigDB.get_by_params(%{group: :pleroma, key: :instance}) + refute ConfigDB.get_by_params(%{group: :pleroma, key: :welcome}) + assert instance_config.value == [name: "Pleroma"] + end + + test "does nothing when welcome_message not set", %{migration: migration} do + insert(:config, + group: :pleroma, + key: :instance, + value: [welcome_user_nickname: "jimm", name: "Pleroma"] + ) + + migration.up() + instance_config = ConfigDB.get_by_params(%{group: :pleroma, key: :instance}) + refute ConfigDB.get_by_params(%{group: :pleroma, key: :welcome}) + assert instance_config.value == [name: "Pleroma"] + end + end + + describe "down/0" do + test "revert new settings to old when instance setting not exists", %{migration: migration} do + insert(:config, + group: :pleroma, + key: :welcome, + value: [ + direct_message: %{ + enabled: true, + message: "Test message", + sender_nickname: "jimm" + }, + email: %{ + enabled: false, + html: "Welcome to <%= instance_name %>", + sender: nil, + subject: "Welcome to <%= instance_name %>", + text: "Welcome to <%= instance_name %>" + } + ] + ) + + migration.down() + + refute ConfigDB.get_by_params(%{group: :pleroma, key: :welcome}) + instance_config = ConfigDB.get_by_params(%{group: :pleroma, key: :instance}) + + assert instance_config.value == [ + welcome_user_nickname: "jimm", + welcome_message: "Test message" + ] + end + + test "revert new settings to old when instance setting exists", %{migration: migration} do + insert(:config, group: :pleroma, key: :instance, value: [name: "Pleroma App"]) + + insert(:config, + group: :pleroma, + key: :welcome, + value: [ + direct_message: %{ + enabled: true, + message: "Test message", + sender_nickname: "jimm" + }, + email: %{ + enabled: false, + html: "Welcome to <%= instance_name %>", + sender: nil, + subject: "Welcome to <%= instance_name %>", + text: "Welcome to <%= instance_name %>" + } + ] + ) + + migration.down() + + refute ConfigDB.get_by_params(%{group: :pleroma, key: :welcome}) + instance_config = ConfigDB.get_by_params(%{group: :pleroma, key: :instance}) + + assert instance_config.value == [ + name: "Pleroma App", + welcome_user_nickname: "jimm", + welcome_message: "Test message" + ] + end + 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/plugs/admin_secret_authentication_plug_test.exs b/test/plugs/admin_secret_authentication_plug_test.exs index 100016c62..89df03c4b 100644 --- a/test/plugs/admin_secret_authentication_plug_test.exs +++ b/test/plugs/admin_secret_authentication_plug_test.exs @@ -4,9 +4,14 @@ defmodule Pleroma.Plugs.AdminSecretAuthenticationPlugTest do use Pleroma.Web.ConnCase, async: true + + import Mock import Pleroma.Factory alias Pleroma.Plugs.AdminSecretAuthenticationPlug + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Plugs.PlugHelper + alias Pleroma.Plugs.RateLimiter test "does nothing if a user is assigned", %{conn: conn} do user = insert(:user) @@ -25,6 +30,10 @@ test "does nothing if a user is assigned", %{conn: conn} do describe "when secret set it assigns an admin user" do setup do: clear_config([:admin_token]) + setup_with_mocks([{RateLimiter, [:passthrough], []}]) do + :ok + end + test "with `admin_token` query parameter", %{conn: conn} do Pleroma.Config.put(:admin_token, "password123") @@ -33,12 +42,14 @@ test "with `admin_token` query parameter", %{conn: conn} do |> AdminSecretAuthenticationPlug.call(%{}) refute conn.assigns[:user] + assert called(RateLimiter.call(conn, name: :authentication)) conn = %{conn | params: %{"admin_token" => "password123"}} |> AdminSecretAuthenticationPlug.call(%{}) assert conn.assigns[:user].is_admin + assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug) end test "with `x-admin-token` HTTP header", %{conn: conn} do @@ -50,6 +61,7 @@ test "with `x-admin-token` HTTP header", %{conn: conn} do |> AdminSecretAuthenticationPlug.call(%{}) refute conn.assigns[:user] + assert called(RateLimiter.call(conn, name: :authentication)) conn = conn @@ -57,6 +69,7 @@ test "with `x-admin-token` HTTP header", %{conn: conn} do |> AdminSecretAuthenticationPlug.call(%{}) assert conn.assigns[:user].is_admin + assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug) end end end diff --git a/test/plugs/frontend_static_test.exs b/test/plugs/frontend_static_test.exs new file mode 100644 index 000000000..d11d91d78 --- /dev/null +++ b/test/plugs/frontend_static_test.exs @@ -0,0 +1,30 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FrontendStaticPlugTest do + use Pleroma.Web.ConnCase + + @dir "test/tmp/instance_static" + + setup do + File.mkdir_p!(@dir) + on_exit(fn -> File.rm_rf(@dir) end) + end + + setup do: clear_config([:instance, :static_dir], @dir) + + test "overrides existing static files", %{conn: conn} do + name = "pelmora" + ref = "uguu" + + clear_config([:frontends, :primary], %{"name" => name, "ref" => ref}) + path = "#{@dir}/frontends/#{name}/#{ref}" + + File.mkdir_p!(path) + File.write!("#{path}/index.html", "from frontend plug") + + index = get(conn, "/") + assert html_response(index, 200) == "from frontend plug" + end +end diff --git a/test/plugs/http_security_plug_test.exs b/test/plugs/http_security_plug_test.exs index 63b4d3f31..2297e3dac 100644 --- a/test/plugs/http_security_plug_test.exs +++ b/test/plugs/http_security_plug_test.exs @@ -4,17 +4,12 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do use Pleroma.Web.ConnCase + alias Pleroma.Config alias Plug.Conn - setup do: clear_config([:http_securiy, :enabled]) - setup do: clear_config([:http_security, :sts]) - setup do: clear_config([:http_security, :referrer_policy]) - describe "http security enabled" do - setup do - Config.put([:http_security, :enabled], true) - end + setup do: clear_config([:http_security, :enabled], true) test "it sends CSP headers when enabled", %{conn: conn} do conn = get(conn, "/api/v1/instance") @@ -29,7 +24,7 @@ test "it sends CSP headers when enabled", %{conn: conn} do end test "it sends STS headers when enabled", %{conn: conn} do - Config.put([:http_security, :sts], true) + clear_config([:http_security, :sts], true) conn = get(conn, "/api/v1/instance") @@ -38,7 +33,7 @@ test "it sends STS headers when enabled", %{conn: conn} do end test "it does not send STS headers when disabled", %{conn: conn} do - Config.put([:http_security, :sts], false) + clear_config([:http_security, :sts], false) conn = get(conn, "/api/v1/instance") @@ -47,23 +42,19 @@ test "it does not send STS headers when disabled", %{conn: conn} do end test "referrer-policy header reflects configured value", %{conn: conn} do - conn = get(conn, "/api/v1/instance") + resp = get(conn, "/api/v1/instance") - assert Conn.get_resp_header(conn, "referrer-policy") == ["same-origin"] + assert Conn.get_resp_header(resp, "referrer-policy") == ["same-origin"] - Config.put([:http_security, :referrer_policy], "no-referrer") + clear_config([:http_security, :referrer_policy], "no-referrer") - conn = - build_conn() - |> get("/api/v1/instance") + resp = get(conn, "/api/v1/instance") - assert Conn.get_resp_header(conn, "referrer-policy") == ["no-referrer"] + assert Conn.get_resp_header(resp, "referrer-policy") == ["no-referrer"] end - test "it sends `report-to` & `report-uri` CSP response headers" do - conn = - build_conn() - |> get("/api/v1/instance") + test "it sends `report-to` & `report-uri` CSP response headers", %{conn: conn} do + conn = get(conn, "/api/v1/instance") [csp] = Conn.get_resp_header(conn, "content-security-policy") @@ -74,10 +65,67 @@ test "it sends `report-to` & `report-uri` CSP response headers" do assert reply_to == "{\"endpoints\":[{\"url\":\"https://endpoint.com\"}],\"group\":\"csp-endpoint\",\"max-age\":10886400}" end + + test "default values for img-src and media-src with disabled media proxy", %{conn: conn} do + conn = get(conn, "/api/v1/instance") + + [csp] = Conn.get_resp_header(conn, "content-security-policy") + assert csp =~ "media-src 'self' https:;" + assert csp =~ "img-src 'self' data: blob: https:;" + end + end + + describe "img-src and media-src" do + setup do + clear_config([:http_security, :enabled], true) + clear_config([:media_proxy, :enabled], true) + clear_config([:media_proxy, :proxy_opts, :redirect_on_failure], false) + end + + test "media_proxy with base_url", %{conn: conn} do + url = "https://example.com" + clear_config([:media_proxy, :base_url], url) + assert_media_img_src(conn, url) + end + + test "upload with base url", %{conn: conn} do + url = "https://example2.com" + clear_config([Pleroma.Upload, :base_url], url) + assert_media_img_src(conn, url) + end + + test "with S3 public endpoint", %{conn: conn} do + url = "https://example3.com" + clear_config([Pleroma.Uploaders.S3, :public_endpoint], url) + assert_media_img_src(conn, url) + end + + test "with captcha endpoint", %{conn: conn} do + clear_config([Pleroma.Captcha.Mock, :endpoint], "https://captcha.com") + assert_media_img_src(conn, "https://captcha.com") + end + + test "with media_proxy whitelist", %{conn: conn} do + clear_config([:media_proxy, :whitelist], ["https://example6.com", "https://example7.com"]) + assert_media_img_src(conn, "https://example7.com https://example6.com") + end + + # TODO: delete after removing support bare domains for media proxy whitelist + test "with media_proxy bare domains whitelist (deprecated)", %{conn: conn} do + clear_config([:media_proxy, :whitelist], ["example4.com", "example5.com"]) + assert_media_img_src(conn, "example5.com example4.com") + end + end + + defp assert_media_img_src(conn, url) do + conn = get(conn, "/api/v1/instance") + [csp] = Conn.get_resp_header(conn, "content-security-policy") + assert csp =~ "media-src 'self' #{url};" + assert csp =~ "img-src 'self' data: blob: #{url};" end test "it does not send CSP headers when disabled", %{conn: conn} do - Config.put([:http_security, :enabled], false) + clear_config([:http_security, :enabled], false) conn = get(conn, "/api/v1/instance") diff --git a/test/plugs/instance_static_test.exs b/test/plugs/instance_static_test.exs index be2613ad0..d42ba817e 100644 --- a/test/plugs/instance_static_test.exs +++ b/test/plugs/instance_static_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.RuntimeStaticPlugTest do +defmodule Pleroma.Web.InstanceStaticPlugTest do use Pleroma.Web.ConnCase @dir "test/tmp/instance_static" @@ -24,6 +24,28 @@ test "overrides index" do assert html_response(index, 200) == "hello world" end + test "also overrides frontend files", %{conn: conn} do + name = "pelmora" + ref = "uguu" + + clear_config([:frontends, :primary], %{"name" => name, "ref" => ref}) + + bundled_index = get(conn, "/") + refute html_response(bundled_index, 200) == "from frontend plug" + + path = "#{@dir}/frontends/#{name}/#{ref}" + File.mkdir_p!(path) + File.write!("#{path}/index.html", "from frontend plug") + + index = get(conn, "/") + assert html_response(index, 200) == "from frontend plug" + + File.write!(@dir <> "/index.html", "from instance static") + + index = get(conn, "/") + assert html_response(index, 200) == "from instance static" + end + test "overrides any file in static/static" do bundled_index = get(build_conn(), "/static/terms-of-service.html") diff --git a/test/plugs/user_is_admin_plug_test.exs b/test/plugs/user_is_admin_plug_test.exs index fd6a50e53..8bc00e444 100644 --- a/test/plugs/user_is_admin_plug_test.exs +++ b/test/plugs/user_is_admin_plug_test.exs @@ -8,112 +8,30 @@ defmodule Pleroma.Plugs.UserIsAdminPlugTest do alias Pleroma.Plugs.UserIsAdminPlug import Pleroma.Factory - describe "unless [:auth, :enforce_oauth_admin_scope_usage]," do - setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], false) + test "accepts a user that is an admin" do + user = insert(:user, is_admin: true) - test "accepts a user that is an admin" do - user = insert(:user, is_admin: true) + conn = assign(build_conn(), :user, user) - conn = assign(build_conn(), :user, user) + ret_conn = UserIsAdminPlug.call(conn, %{}) - ret_conn = UserIsAdminPlug.call(conn, %{}) - - assert conn == ret_conn - end - - test "denies a user that isn't an admin" do - user = insert(:user) - - conn = - build_conn() - |> assign(:user, user) - |> UserIsAdminPlug.call(%{}) - - assert conn.status == 403 - end - - test "denies when a user isn't set" do - conn = UserIsAdminPlug.call(build_conn(), %{}) - - assert conn.status == 403 - end + assert conn == ret_conn end - describe "with [:auth, :enforce_oauth_admin_scope_usage]," do - setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], true) + test "denies a user that isn't an admin" do + user = insert(:user) - setup do - admin_user = insert(:user, is_admin: true) - non_admin_user = insert(:user, is_admin: false) - blank_user = nil + conn = + build_conn() + |> assign(:user, user) + |> UserIsAdminPlug.call(%{}) - {:ok, %{users: [admin_user, non_admin_user, blank_user]}} - end + assert conn.status == 403 + end - test "if token has any of admin scopes, accepts a user that is an admin", %{conn: conn} do - user = insert(:user, is_admin: true) - token = insert(:oauth_token, user: user, scopes: ["admin:something"]) + test "denies when a user isn't set" do + conn = UserIsAdminPlug.call(build_conn(), %{}) - conn = - conn - |> assign(:user, user) - |> assign(:token, token) - - ret_conn = UserIsAdminPlug.call(conn, %{}) - - assert conn == ret_conn - end - - test "if token has any of admin scopes, denies a user that isn't an admin", %{conn: conn} do - user = insert(:user, is_admin: false) - token = insert(:oauth_token, user: user, scopes: ["admin:something"]) - - conn = - conn - |> assign(:user, user) - |> assign(:token, token) - |> UserIsAdminPlug.call(%{}) - - assert conn.status == 403 - end - - test "if token has any of admin scopes, denies when a user isn't set", %{conn: conn} do - token = insert(:oauth_token, scopes: ["admin:something"]) - - conn = - conn - |> assign(:user, nil) - |> assign(:token, token) - |> UserIsAdminPlug.call(%{}) - - assert conn.status == 403 - end - - test "if token lacks admin scopes, denies users regardless of is_admin flag", - %{users: users} do - for user <- users do - token = insert(:oauth_token, user: user) - - conn = - build_conn() - |> assign(:user, user) - |> assign(:token, token) - |> UserIsAdminPlug.call(%{}) - - assert conn.status == 403 - end - end - - test "if token is missing, denies users regardless of is_admin flag", %{users: users} do - for user <- users do - conn = - build_conn() - |> assign(:user, user) - |> assign(:token, nil) - |> UserIsAdminPlug.call(%{}) - - assert conn.status == 403 - end - end + assert conn.status == 403 end end 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/reverse_proxy/reverse_proxy_test.exs b/test/reverse_proxy/reverse_proxy_test.exs index c677066b3..8df63de65 100644 --- a/test/reverse_proxy/reverse_proxy_test.exs +++ b/test/reverse_proxy/reverse_proxy_test.exs @@ -314,7 +314,7 @@ defp disposition_headers_mock(headers) do test "not atachment", %{conn: conn} do disposition_headers_mock([ {"content-type", "image/gif"}, - {"content-length", 0} + {"content-length", "0"} ]) conn = ReverseProxy.call(conn, "/disposition") @@ -325,7 +325,7 @@ test "not atachment", %{conn: conn} do test "with content-disposition header", %{conn: conn} do disposition_headers_mock([ {"content-disposition", "attachment; filename=\"filename.jpg\""}, - {"content-length", 0} + {"content-length", "0"} ]) conn = ReverseProxy.call(conn, "/disposition") diff --git a/test/support/captcha_mock.ex b/test/support/captcha_mock.ex index 7b0c1d5af..2ed2ba3b4 100644 --- a/test/support/captcha_mock.ex +++ b/test/support/captcha_mock.ex @@ -16,7 +16,8 @@ def new, type: :mock, token: "afa1815e14e29355e6c8f6b143a39fa2", answer_data: @solution, - url: "https://example.org/captcha.png" + url: "https://example.org/captcha.png", + seconds_valid: 300 } @impl Service diff --git a/test/support/cluster.ex b/test/support/cluster.ex index deb37f361..524194cf4 100644 --- a/test/support/cluster.ex +++ b/test/support/cluster.ex @@ -97,7 +97,7 @@ def spawn_cluster(node_configs) do silence_logger_warnings(fn -> node_configs |> Enum.map(&Task.async(fn -> start_slave(&1) end)) - |> Enum.map(&Task.await(&1, 60_000)) + |> Enum.map(&Task.await(&1, 90_000)) 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/support/oban_helpers.ex b/test/support/oban_helpers.ex index e96994c57..9f90a821c 100644 --- a/test/support/oban_helpers.ex +++ b/test/support/oban_helpers.ex @@ -20,7 +20,7 @@ def perform_all do end def perform(%Oban.Job{} = job) do - res = apply(String.to_existing_atom("Elixir." <> job.worker), :perform, [job.args, job]) + res = apply(String.to_existing_atom("Elixir." <> job.worker), :perform, [job]) Repo.delete(job) res end diff --git a/test/tasks/config_test.exs b/test/tasks/config_test.exs index 71f36c0e3..fb12e7fb3 100644 --- a/test/tasks/config_test.exs +++ b/test/tasks/config_test.exs @@ -129,8 +129,6 @@ test "load a settings with large values and pass to file", %{temp_file: temp_fil autofollowed_nicknames: [], max_pinned_statuses: 1, attachment_links: false, - welcome_user_nickname: nil, - welcome_message: nil, max_report_comment_size: 1000, safe_dm_mentions: false, healthcheck: false, @@ -172,7 +170,7 @@ test "load a settings with large values and pass to file", %{temp_file: temp_fil end assert file == - "#{header}\n\nconfig :pleroma, :instance,\n name: \"Pleroma\",\n email: \"example@example.com\",\n notify_email: \"noreply@example.com\",\n description: \"A Pleroma instance, an alternative fediverse server\",\n limit: 5000,\n chat_limit: 5000,\n remote_limit: 100_000,\n upload_limit: 16_000_000,\n avatar_upload_limit: 2_000_000,\n background_upload_limit: 4_000_000,\n banner_upload_limit: 4_000_000,\n poll_limits: %{\n max_expiration: 31_536_000,\n max_option_chars: 200,\n max_options: 20,\n min_expiration: 0\n },\n registrations_open: true,\n federating: true,\n federation_incoming_replies_max_depth: 100,\n federation_reachability_timeout_days: 7,\n federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],\n allow_relay: true,\n public: true,\n quarantined_instances: [],\n managed_config: true,\n static_dir: \"instance/static/\",\n allowed_post_formats: [\"text/plain\", \"text/html\", \"text/markdown\", \"text/bbcode\"],\n autofollowed_nicknames: [],\n max_pinned_statuses: 1,\n attachment_links: false,\n welcome_user_nickname: nil,\n welcome_message: nil,\n max_report_comment_size: 1000,\n safe_dm_mentions: false,\n healthcheck: false,\n remote_post_retention_days: 90,\n skip_thread_containment: true,\n limit_to_local_content: :unauthenticated,\n user_bio_length: 5000,\n user_name_length: 100,\n max_account_fields: 10,\n max_remote_account_fields: 20,\n account_field_name_length: 512,\n account_field_value_length: 2048,\n external_user_synchronization: true,\n extended_nickname_format: true,\n multi_factor_authentication: [\n totp: [digits: 6, period: 30],\n backup_codes: [number: 2, length: 6]\n ]\n" + "#{header}\n\nconfig :pleroma, :instance,\n name: \"Pleroma\",\n email: \"example@example.com\",\n notify_email: \"noreply@example.com\",\n description: \"A Pleroma instance, an alternative fediverse server\",\n limit: 5000,\n chat_limit: 5000,\n remote_limit: 100_000,\n upload_limit: 16_000_000,\n avatar_upload_limit: 2_000_000,\n background_upload_limit: 4_000_000,\n banner_upload_limit: 4_000_000,\n poll_limits: %{\n max_expiration: 31_536_000,\n max_option_chars: 200,\n max_options: 20,\n min_expiration: 0\n },\n registrations_open: true,\n federating: true,\n federation_incoming_replies_max_depth: 100,\n federation_reachability_timeout_days: 7,\n federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],\n allow_relay: true,\n public: true,\n quarantined_instances: [],\n managed_config: true,\n static_dir: \"instance/static/\",\n allowed_post_formats: [\"text/plain\", \"text/html\", \"text/markdown\", \"text/bbcode\"],\n autofollowed_nicknames: [],\n max_pinned_statuses: 1,\n attachment_links: false,\n max_report_comment_size: 1000,\n safe_dm_mentions: false,\n healthcheck: false,\n remote_post_retention_days: 90,\n skip_thread_containment: true,\n limit_to_local_content: :unauthenticated,\n user_bio_length: 5000,\n user_name_length: 100,\n max_account_fields: 10,\n max_remote_account_fields: 20,\n account_field_name_length: 512,\n account_field_value_length: 2048,\n external_user_synchronization: true,\n extended_nickname_format: true,\n multi_factor_authentication: [\n totp: [digits: 6, period: 30],\n backup_codes: [number: 2, length: 6]\n ]\n" end end end diff --git a/test/tasks/release_env_test.exs b/test/tasks/release_env_test.exs new file mode 100644 index 000000000..519f1eba9 --- /dev/null +++ b/test/tasks/release_env_test.exs @@ -0,0 +1,30 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Pleroma.ReleaseEnvTest do + use ExUnit.Case + import ExUnit.CaptureIO, only: [capture_io: 1] + + @path "config/pleroma.test.env" + + def do_clean do + if File.exists?(@path) do + File.rm_rf(@path) + end + end + + setup do + do_clean() + on_exit(fn -> do_clean() end) + :ok + end + + test "generate pleroma.env" do + assert capture_io(fn -> + Mix.Tasks.Pleroma.ReleaseEnv.run(["gen", "--path", @path, "--force"]) + end) =~ "The file generated" + + assert File.read!(@path) =~ "RELEASE_COOKIE=" + end +end diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs index 2a3e62e26..ce43a9cc7 100644 --- a/test/tasks/user_test.exs +++ b/test/tasks/user_test.exs @@ -481,17 +481,17 @@ test "it returns users matching" do moot = insert(:user, nickname: "moot") kawen = insert(:user, nickname: "kawen", name: "fediverse expert moon") - {:ok, user} = User.follow(user, kawen) + {:ok, user} = User.follow(user, moon) assert [moon.id, kawen.id] == User.Search.search("moon") |> Enum.map(& &1.id) - res = User.search("moo") |> Enum.map(& &1.id) - assert moon.id in res - assert moot.id in res - assert kawen.id in res - assert [moon.id, kawen.id] == User.Search.search("moon fediverse") |> Enum.map(& &1.id) - assert [kawen.id, moon.id] == - User.Search.search("moon fediverse", for_user: user) |> Enum.map(& &1.id) + res = User.search("moo") |> Enum.map(& &1.id) + assert Enum.sort([moon.id, moot.id, kawen.id]) == Enum.sort(res) + + assert [kawen.id, moon.id] == User.Search.search("expert fediverse") |> Enum.map(& &1.id) + + assert [moon.id, kawen.id] == + User.Search.search("expert fediverse", for_user: user) |> Enum.map(& &1.id) end end 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/upload/filter/exiftool_test.exs b/test/upload/filter/exiftool_test.exs new file mode 100644 index 000000000..a1b7e46cd --- /dev/null +++ b/test/upload/filter/exiftool_test.exs @@ -0,0 +1,31 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Upload.Filter.ExiftoolTest do + use Pleroma.DataCase + alias Pleroma.Upload.Filter + + test "apply exiftool filter" do + File.cp!( + "test/fixtures/DSCN0010.jpg", + "test/fixtures/DSCN0010_tmp.jpg" + ) + + upload = %Pleroma.Upload{ + name: "image_with_GPS_data.jpg", + content_type: "image/jpg", + path: Path.absname("test/fixtures/DSCN0010.jpg"), + tempfile: Path.absname("test/fixtures/DSCN0010_tmp.jpg") + } + + assert Filter.Exiftool.filter(upload) == :ok + + {exif_original, 0} = System.cmd("exiftool", ["test/fixtures/DSCN0010.jpg"]) + {exif_filtered, 0} = System.cmd("exiftool", ["test/fixtures/DSCN0010_tmp.jpg"]) + + refute exif_original == exif_filtered + assert String.match?(exif_original, ~r/GPS/) + refute String.match?(exif_filtered, ~r/GPS/) + end +end 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/welcome_email_test.exs b/test/user/welcome_email_test.exs new file mode 100644 index 000000000..d005d11b2 --- /dev/null +++ b/test/user/welcome_email_test.exs @@ -0,0 +1,61 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.User.WelcomeEmailTest do + use Pleroma.DataCase + + alias Pleroma.Config + alias Pleroma.Tests.ObanHelpers + alias Pleroma.User.WelcomeEmail + + import Pleroma.Factory + import Swoosh.TestAssertions + + setup do: clear_config([:welcome]) + + describe "send_email/1" do + test "send a welcome email" do + user = insert(:user, name: "Jimm") + + Config.put([:welcome, :email, :enabled], true) + Config.put([:welcome, :email, :sender], "welcome@pleroma.app") + + Config.put( + [:welcome, :email, :subject], + "Hello, welcome to pleroma: <%= instance_name %>" + ) + + Config.put( + [:welcome, :email, :html], + "

Hello <%= user.name %>.

Welcome to <%= instance_name %>

" + ) + + instance_name = Config.get([:instance, :name]) + + {:ok, _job} = WelcomeEmail.send_email(user) + + ObanHelpers.perform_all() + + assert_email_sent( + from: {instance_name, "welcome@pleroma.app"}, + to: {user.name, user.email}, + subject: "Hello, welcome to pleroma: #{instance_name}", + html_body: "

Hello #{user.name}.

Welcome to #{instance_name}

" + ) + + Config.put([:welcome, :email, :sender], {"Pleroma App", "welcome@pleroma.app"}) + + {:ok, _job} = WelcomeEmail.send_email(user) + + ObanHelpers.perform_all() + + assert_email_sent( + from: {"Pleroma App", "welcome@pleroma.app"}, + to: {user.name, user.email}, + subject: "Hello, welcome to pleroma: #{instance_name}", + html_body: "

Hello #{user.name}.

Welcome to #{instance_name}

" + ) + end + end +end diff --git a/test/user/welcome_message_test.exs b/test/user/welcome_message_test.exs new file mode 100644 index 000000000..3cd6f5cb7 --- /dev/null +++ b/test/user/welcome_message_test.exs @@ -0,0 +1,34 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.User.WelcomeMessageTest do + use Pleroma.DataCase + + alias Pleroma.Config + alias Pleroma.User.WelcomeMessage + + import Pleroma.Factory + + setup do: clear_config([:welcome]) + + describe "post_message/1" do + test "send a direct welcome message" do + welcome_user = insert(:user) + user = insert(:user, name: "Jimm") + + Config.put([:welcome, :direct_message, :enabled], true) + Config.put([:welcome, :direct_message, :sender_nickname], welcome_user.nickname) + + Config.put( + [:welcome, :direct_message, :message], + "Hello. Welcome to Pleroma" + ) + + {:ok, %Pleroma.Activity{} = activity} = WelcomeMessage.post_message(user) + assert user.ap_id in activity.recipients + assert activity.data["directMessage"] == true + assert Pleroma.Object.normalize(activity).data["content"] =~ "Hello. Welcome to Pleroma" + end + end +end diff --git a/test/user_search_test.exs b/test/user_search_test.exs index f030523d3..559ba5966 100644 --- a/test/user_search_test.exs +++ b/test/user_search_test.exs @@ -46,30 +46,49 @@ test "accepts offset parameter" do assert length(User.search("john", limit: 3, offset: 3)) == 2 end - test "finds a user by full or partial nickname" do + defp clear_virtual_fields(user) do + Map.merge(user, %{search_rank: nil, search_type: nil}) + end + + test "finds a user by full nickname or its leading fragment" do user = insert(:user, %{nickname: "john"}) Enum.each(["john", "jo", "j"], fn query -> assert user == User.search(query) |> List.first() - |> Map.put(:search_rank, nil) - |> Map.put(:search_type, nil) + |> clear_virtual_fields() end) end - test "finds a user by full or partial name" do + test "finds a user by full name or leading fragment(s) of its words" do user = insert(:user, %{name: "John Doe"}) Enum.each(["John Doe", "JOHN", "doe", "j d", "j", "d"], fn query -> assert user == User.search(query) |> List.first() - |> Map.put(:search_rank, nil) - |> Map.put(:search_type, nil) + |> clear_virtual_fields() end) end + test "matches by leading fragment of user domain" do + user = insert(:user, %{nickname: "arandom@dude.com"}) + insert(:user, %{nickname: "iamthedude"}) + + assert [user.id] == User.search("dud") |> Enum.map(& &1.id) + end + + test "ranks full nickname match higher than full name match" do + nicknamed_user = insert(:user, %{nickname: "hj@shigusegubu.club"}) + named_user = insert(:user, %{nickname: "xyz@sample.com", name: "HJ"}) + + results = User.search("hj") + + assert [nicknamed_user.id, named_user.id] == Enum.map(results, & &1.id) + assert Enum.at(results, 0).search_rank > Enum.at(results, 1).search_rank + end + test "finds users, considering density of matched tokens" do u1 = insert(:user, %{name: "Bar Bar plus Word Word"}) u2 = insert(:user, %{name: "Word Word Bar Bar Bar"}) diff --git a/test/user_test.exs b/test/user_test.exs index 7126bb539..904cea536 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,10 @@ 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([:welcome]) + setup do: clear_config([:instance, :account_activation_required]) test "it autofollows accounts that are set for it" do user = insert(:user) @@ -408,20 +410,45 @@ test "it autofollows accounts that are set for it" do test "it sends a welcome message if it is set" do welcome_user = insert(:user) + Pleroma.Config.put([:welcome, :direct_message, :enabled], true) + Pleroma.Config.put([:welcome, :direct_message, :sender_nickname], welcome_user.nickname) + Pleroma.Config.put([:welcome, :direct_message, :message], "Hello, this is a cool site") - Pleroma.Config.put([:instance, :welcome_user_nickname], welcome_user.nickname) - Pleroma.Config.put([:instance, :welcome_message], "Hello, this is a cool site") + Pleroma.Config.put([:welcome, :email, :enabled], true) + Pleroma.Config.put([:welcome, :email, :sender], welcome_user.email) + + Pleroma.Config.put( + [:welcome, :email, :subject], + "Hello, welcome to cool site: <%= instance_name %>" + ) + + instance_name = Pleroma.Config.get([:instance, :name]) cng = User.register_changeset(%User{}, @full_user_data) {:ok, registered_user} = User.register(cng) + ObanHelpers.perform_all() activity = Repo.one(Pleroma.Activity) assert registered_user.ap_id in activity.recipients assert Object.normalize(activity).data["content"] =~ "cool site" assert activity.actor == welcome_user.ap_id + + assert_email_sent( + from: {instance_name, welcome_user.email}, + to: {registered_user.name, registered_user.email}, + subject: "Hello, welcome to cool site: #{instance_name}", + html_body: "Welcome to #{instance_name}" + ) 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) @@ -473,6 +500,24 @@ test "it sets the password_hash and ap_id" do assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers" end + + test "it sets the 'accepts_chat_messages' set to true" do + changeset = User.register_changeset(%User{}, @full_user_data) + assert changeset.valid? + + {:ok, user} = Repo.insert(changeset) + + assert user.accepts_chat_messages + end + + test "it creates a confirmed user" do + changeset = User.register_changeset(%User{}, @full_user_data) + assert changeset.valid? + + {:ok, user} = Repo.insert(changeset) + + refute user.confirmation_pending + end end describe "user registration, with :account_activation_required" do @@ -507,6 +552,46 @@ test "it creates confirmed user if :confirmed option is given" do end end + describe "user registration, with :account_approval_required" do + @full_user_data %{ + bio: "A guy", + name: "my name", + nickname: "nick", + password: "test", + password_confirmation: "test", + email: "email@example.com", + registration_reason: "I'm a cool guy :)" + } + setup do: clear_config([:instance, :account_approval_required], true) + + test "it creates unapproved user" do + changeset = User.register_changeset(%User{}, @full_user_data) + assert changeset.valid? + + {:ok, user} = Repo.insert(changeset) + + assert user.approval_pending + assert user.registration_reason == "I'm a cool guy :)" + end + + test "it restricts length of registration reason" do + reason_limit = Pleroma.Config.get([:instance, :registration_reason_length]) + + assert is_integer(reason_limit) + + params = + @full_user_data + |> Map.put( + :registration_reason, + "Quia et nesciunt dolores numquam ipsam nisi sapiente soluta. Ullam repudiandae nisi quam porro officiis officiis ad. Consequatur animi velit ex quia. Odit voluptatem perferendis quia ut nisi. Dignissimos sit soluta atque aliquid dolorem ut dolorum ut. Labore voluptates iste iusto amet voluptatum earum. Ad fugit illum nam eos ut nemo. Pariatur ea fuga non aspernatur. Dignissimos debitis officia corporis est nisi ab et. Atque itaque alias eius voluptas minus. Accusamus numquam tempore occaecati in." + ) + + changeset = User.register_changeset(%User{}, params) + + refute changeset.valid? + end + end + describe "get_or_fetch/1" do test "gets an existing user by nickname" do user = insert(:user) @@ -1172,6 +1257,31 @@ test "hide a user's statuses from timelines and notifications" do end end + describe "approve" do + test "approves a user" do + user = insert(:user, approval_pending: true) + assert true == user.approval_pending + {:ok, user} = User.approve(user) + assert false == user.approval_pending + end + + test "approves a list of users" do + unapproved_users = [ + insert(:user, approval_pending: true), + insert(:user, approval_pending: true), + insert(:user, approval_pending: true) + ] + + {:ok, users} = User.approve(unapproved_users) + + assert Enum.count(users) == 3 + + Enum.each(users, fn user -> + assert false == user.approval_pending + end) + end + end + describe "delete" do setup do {:ok, user} = insert(:user) |> User.set_cache() @@ -1259,6 +1369,17 @@ test "deactivates user when activation is not required", %{user: user} do end end + test "delete/1 when approval is pending deletes the user" do + user = insert(:user, approval_pending: true) + {:ok, user: user} + + {:ok, job} = User.delete(user) + {:ok, _} = ObanHelpers.perform(job) + + refute User.get_cached_by_id(user.id) + refute User.get_by_id(user.id) + end + test "get_public_key_for_ap_id fetches a user that's not in the db" do assert {:ok, _key} = User.get_public_key_for_ap_id("http://mastodon.example.org/users/admin") end @@ -1333,6 +1454,14 @@ test "returns :deactivated for deactivated user" do user = insert(:user, local: true, confirmation_pending: false, deactivated: true) assert User.account_status(user) == :deactivated end + + test "returns :approval_pending for unapproved user" do + user = insert(:user, local: true, approval_pending: true) + assert User.account_status(user) == :approval_pending + + user = insert(:user, local: true, confirmation_pending: true, approval_pending: true) + assert User.account_status(user) == :approval_pending + end end describe "superuser?/1" do 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 38c98f658..d6eab7337 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -184,38 +184,45 @@ test "it returns a user that is invisible" do assert User.invisible?(user) end - test "it fetches the appropriate tag-restricted posts" do - user = insert(:user) + test "it returns a user that accepts chat messages" do + user_id = "http://mastodon.example.org/users/admin" + {:ok, user} = ActivityPub.make_user_from_ap_id(user_id) - {:ok, status_one} = CommonAPI.post(user, %{status: ". #test"}) - {:ok, status_two} = CommonAPI.post(user, %{status: ". #essais"}) - {:ok, status_three} = CommonAPI.post(user, %{status: ". #test #reject"}) - - fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"}) - - fetch_two = ActivityPub.fetch_activities([], %{type: "Create", tag: ["test", "essais"]}) - - fetch_three = - ActivityPub.fetch_activities([], %{ - type: "Create", - tag: ["test", "essais"], - tag_reject: ["reject"] - }) - - fetch_four = - ActivityPub.fetch_activities([], %{ - type: "Create", - tag: ["test"], - tag_all: ["test", "reject"] - }) - - assert fetch_one == [status_one, status_three] - assert fetch_two == [status_one, status_two, status_three] - assert fetch_three == [status_one, status_two] - assert fetch_four == [status_three] + assert user.accepts_chat_messages end end + test "it fetches the appropriate tag-restricted posts" do + user = insert(:user) + + {:ok, status_one} = CommonAPI.post(user, %{status: ". #test"}) + {:ok, status_two} = CommonAPI.post(user, %{status: ". #essais"}) + {:ok, status_three} = CommonAPI.post(user, %{status: ". #test #reject"}) + + fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"}) + + fetch_two = ActivityPub.fetch_activities([], %{type: "Create", tag: ["test", "essais"]}) + + fetch_three = + ActivityPub.fetch_activities([], %{ + type: "Create", + tag: ["test", "essais"], + tag_reject: ["reject"] + }) + + fetch_four = + ActivityPub.fetch_activities([], %{ + type: "Create", + tag: ["test"], + tag_all: ["test", "reject"] + }) + + assert fetch_one == [status_one, status_three] + assert fetch_two == [status_one, status_two, status_three] + assert fetch_three == [status_one, status_two] + assert fetch_four == [status_three] + end + describe "insertion" do test "drops activities beyond a certain limit" do limit = Config.get([:instance, :remote_limit]) @@ -1172,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{ @@ -1437,7 +1445,7 @@ test "create" do assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params) - Pleroma.Workers.BackgroundWorker.perform(params, nil) + Pleroma.Workers.BackgroundWorker.perform(%Oban.Job{args: params}) refute User.following?(follower, old_user) assert User.following?(follower, new_user) @@ -2049,4 +2057,46 @@ test "creates an activity expiration for local Create activities" do assert [%{activity_id: ^id_create}] = Pleroma.ActivityExpiration |> Repo.all() end end + + describe "handling of clashing nicknames" do + test "renames an existing user with a clashing nickname and a different ap id" do + orig_user = + insert( + :user, + local: false, + nickname: "admin@mastodon.example.org", + ap_id: "http://mastodon.example.org/users/harinezumigari" + ) + + %{ + nickname: orig_user.nickname, + ap_id: orig_user.ap_id <> "part_2" + } + |> ActivityPub.maybe_handle_clashing_nickname() + + user = User.get_by_id(orig_user.id) + + assert user.nickname == "#{orig_user.id}.admin@mastodon.example.org" + end + + test "does nothing with a clashing nickname and the same ap id" do + orig_user = + insert( + :user, + local: false, + nickname: "admin@mastodon.example.org", + ap_id: "http://mastodon.example.org/users/harinezumigari" + ) + + %{ + nickname: orig_user.nickname, + ap_id: orig_user.ap_id + } + |> ActivityPub.maybe_handle_clashing_nickname() + + user = User.get_by_id(orig_user.id) + + assert user.nickname == orig_user.nickname + end + end end 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/ensure_re_prepended_test.exs b/test/web/activity_pub/mrf/ensure_re_prepended_test.exs index 38ddec5bb..9a283f27d 100644 --- a/test/web/activity_pub/mrf/ensure_re_prepended_test.exs +++ b/test/web/activity_pub/mrf/ensure_re_prepended_test.exs @@ -78,5 +78,15 @@ test "it skip if parent and child summary isn't equal" do assert {:ok, res} = EnsureRePrepended.filter(message) assert res == message end + + test "it skips if the object is only a reference" do + message = %{ + "type" => "Create", + "object" => "somereference" + } + + assert {:ok, res} = EnsureRePrepended.filter(message) + assert res == message + end 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/object_validators/chat_validation_test.exs b/test/web/activity_pub/object_validators/chat_validation_test.exs index ec1e497fa..50bf03515 100644 --- a/test/web/activity_pub/object_validators/chat_validation_test.exs +++ b/test/web/activity_pub/object_validators/chat_validation_test.exs @@ -161,6 +161,17 @@ test "does not validate if the recipient is blocking the actor", %{ refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, [])) end + test "does not validate if the recipient is not accepting chat messages", %{ + valid_chat_message: valid_chat_message, + recipient: recipient + } do + recipient + |> Ecto.Changeset.change(%{accepts_chat_messages: false}) + |> Pleroma.Repo.update!() + + refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, [])) + end + test "does not validate if the actor or the recipient is not in our system", %{ valid_chat_message: valid_chat_message } do diff --git a/test/web/activity_pub/pipeline_test.exs b/test/web/activity_pub/pipeline_test.exs index 8deb64501..f2a231eaf 100644 --- a/test/web/activity_pub/pipeline_test.exs +++ b/test/web/activity_pub/pipeline_test.exs @@ -14,6 +14,51 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do :ok end + test "when given an `object_data` in meta, Federation will receive a the original activity with the `object` field set to this embedded object" do + activity = insert(:note_activity) + object = %{"id" => "1", "type" => "Love"} + meta = [local: true, object_data: object] + + activity_with_object = %{activity | data: Map.put(activity.data, "object", object)} + + with_mocks([ + {Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]}, + { + Pleroma.Web.ActivityPub.MRF, + [], + [filter: fn o -> {:ok, o} end] + }, + { + Pleroma.Web.ActivityPub.ActivityPub, + [], + [persist: fn o, m -> {:ok, o, m} end] + }, + { + Pleroma.Web.ActivityPub.SideEffects, + [], + [ + handle: fn o, m -> {:ok, o, m} end, + handle_after_transaction: fn m -> m end + ] + }, + { + Pleroma.Web.Federator, + [], + [publish: fn _o -> :ok end] + } + ]) do + assert {:ok, ^activity, ^meta} = + Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta) + + assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta)) + assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity)) + assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta)) + assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta)) + refute called(Pleroma.Web.Federator.publish(activity)) + assert_called(Pleroma.Web.Federator.publish(activity_with_object)) + end + end + test "it goes through validation, filtering, persisting, side effects and federation for local activities" do activity = insert(:note_activity) meta = [local: true] 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/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index 2649b060a..4a08eb7ee 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -312,8 +312,12 @@ test "when activation is required", %{delete: delete, user: user} do } end - test "deletes the original block", %{block_undo: block_undo, block: block} do - {:ok, _block_undo, _} = SideEffects.handle(block_undo) + test "deletes the original block", %{ + block_undo: block_undo, + block: block + } do + {:ok, _block_undo, _meta} = SideEffects.handle(block_undo) + refute Activity.get_by_id(block.id) end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index f7b7d1a9f..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 = %{ @@ -774,6 +774,29 @@ test "it correctly processes messages with non-array cc field" do assert [user.follower_address] == activity.data["to"] end + test "it correctly processes messages with weirdness in address fields" do + user = insert(:user) + + message = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "to" => [nil, user.follower_address], + "cc" => ["https://www.w3.org/ns/activitystreams#Public", ["¿"]], + "type" => "Create", + "object" => %{ + "content" => "…", + "type" => "Note", + "attributedTo" => user.ap_id, + "inReplyTo" => nil + }, + "actor" => user.ap_id + } + + assert {:ok, activity} = Transmogrifier.handle_incoming(message) + + assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"] + assert [user.follower_address] == activity.data["to"] + end + test "it accepts Move activities" do old_user = insert(:user) new_user = insert(:user) 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/activity_pub/views/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs index bec15a996..98c7c9d09 100644 --- a/test/web/activity_pub/views/user_view_test.exs +++ b/test/web/activity_pub/views/user_view_test.exs @@ -158,4 +158,23 @@ test "sets correct totalItems when follows are hidden but the follow counter is assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user}) end end + + describe "acceptsChatMessages" do + test "it returns this value if it is set" do + true_user = insert(:user, accepts_chat_messages: true) + false_user = insert(:user, accepts_chat_messages: false) + nil_user = insert(:user, accepts_chat_messages: nil) + + assert %{"capabilities" => %{"acceptsChatMessages" => true}} = + UserView.render("user.json", user: true_user) + + assert %{"capabilities" => %{"acceptsChatMessages" => false}} = + UserView.render("user.json", user: false_user) + + refute Map.has_key?( + UserView.render("user.json", user: nil_user)["capabilities"], + "acceptsChatMessages" + ) + end + end end 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 48fb108ec..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 @@ -41,6 +42,16 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do {:ok, %{admin: admin, token: token, conn: conn}} end + test "with valid `admin_token` query parameter, skips OAuth scopes check" do + clear_config([:admin_token], "password123") + + user = insert(:user) + + conn = get(build_conn(), "/api/pleroma/admin/users/#{user.nickname}?admin_token=password123") + + assert json_response(conn, 200) + end + describe "with [:auth, :enforce_oauth_admin_scope_usage]," do setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], true) @@ -338,7 +349,9 @@ test "Show", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "approval_pending" => false, + "url" => user.ap_id, + "registration_reason" => nil } assert expected == json_response(conn, 200) @@ -602,6 +615,8 @@ test "/api/pleroma/admin/users/:nickname/password_reset", %{conn: conn} do describe "GET /api/pleroma/admin/users" do test "renders users array for the first page", %{conn: conn, admin: admin} do user = insert(:user, local: false, tags: ["foo", "bar"]) + user2 = insert(:user, approval_pending: true, registration_reason: "I'm a chill dude") + conn = get(conn, "/api/pleroma/admin/users?page=1") users = @@ -616,7 +631,9 @@ test "renders users array for the first page", %{conn: conn, admin: admin} do "avatar" => User.avatar_url(admin) |> MediaProxy.url(), "display_name" => HTML.strip_tags(admin.name || admin.nickname), "confirmation_pending" => false, - "url" => admin.ap_id + "approval_pending" => false, + "url" => admin.ap_id, + "registration_reason" => nil }, %{ "deactivated" => user.deactivated, @@ -628,13 +645,29 @@ test "renders users array for the first page", %{conn: conn, admin: admin} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "approval_pending" => false, + "url" => user.ap_id, + "registration_reason" => nil + }, + %{ + "deactivated" => user2.deactivated, + "id" => user2.id, + "nickname" => user2.nickname, + "roles" => %{"admin" => false, "moderator" => false}, + "local" => true, + "tags" => [], + "avatar" => User.avatar_url(user2) |> MediaProxy.url(), + "display_name" => HTML.strip_tags(user2.name || user2.nickname), + "confirmation_pending" => false, + "approval_pending" => true, + "url" => user2.ap_id, + "registration_reason" => "I'm a chill dude" } ] |> Enum.sort_by(& &1["nickname"]) assert json_response(conn, 200) == %{ - "count" => 2, + "count" => 3, "page_size" => 50, "users" => users } @@ -701,7 +734,9 @@ test "regular search", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "approval_pending" => false, + "url" => user.ap_id, + "registration_reason" => nil } ] } @@ -727,7 +762,9 @@ test "search by domain", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "approval_pending" => false, + "url" => user.ap_id, + "registration_reason" => nil } ] } @@ -753,7 +790,9 @@ test "search by full nickname", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "approval_pending" => false, + "url" => user.ap_id, + "registration_reason" => nil } ] } @@ -779,7 +818,9 @@ test "search by display name", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "approval_pending" => false, + "url" => user.ap_id, + "registration_reason" => nil } ] } @@ -805,7 +846,9 @@ test "search by email", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "approval_pending" => false, + "url" => user.ap_id, + "registration_reason" => nil } ] } @@ -831,7 +874,9 @@ test "regular search with page size", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "approval_pending" => false, + "url" => user.ap_id, + "registration_reason" => nil } ] } @@ -852,7 +897,9 @@ test "regular search with page size", %{conn: conn} do "avatar" => User.avatar_url(user2) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user2.name || user2.nickname), "confirmation_pending" => false, - "url" => user2.ap_id + "approval_pending" => false, + "url" => user2.ap_id, + "registration_reason" => nil } ] } @@ -885,7 +932,9 @@ test "only local users" do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "approval_pending" => false, + "url" => user.ap_id, + "registration_reason" => nil } ] } @@ -911,7 +960,9 @@ test "only local users with no query", %{conn: conn, admin: old_admin} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "approval_pending" => false, + "url" => user.ap_id, + "registration_reason" => nil }, %{ "deactivated" => admin.deactivated, @@ -923,7 +974,9 @@ test "only local users with no query", %{conn: conn, admin: old_admin} do "avatar" => User.avatar_url(admin) |> MediaProxy.url(), "display_name" => HTML.strip_tags(admin.name || admin.nickname), "confirmation_pending" => false, - "url" => admin.ap_id + "approval_pending" => false, + "url" => admin.ap_id, + "registration_reason" => nil }, %{ "deactivated" => false, @@ -935,7 +988,9 @@ test "only local users with no query", %{conn: conn, admin: old_admin} do "avatar" => User.avatar_url(old_admin) |> MediaProxy.url(), "display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname), "confirmation_pending" => false, - "url" => old_admin.ap_id + "approval_pending" => false, + "url" => old_admin.ap_id, + "registration_reason" => nil } ] |> Enum.sort_by(& &1["nickname"]) @@ -947,6 +1002,44 @@ test "only local users with no query", %{conn: conn, admin: old_admin} do } end + test "only unapproved users", %{conn: conn} do + user = + insert(:user, + nickname: "sadboy", + approval_pending: true, + registration_reason: "Plz let me in!" + ) + + insert(:user, nickname: "happyboy", approval_pending: false) + + conn = get(conn, "/api/pleroma/admin/users?filters=need_approval") + + users = + [ + %{ + "deactivated" => user.deactivated, + "id" => user.id, + "nickname" => user.nickname, + "roles" => %{"admin" => false, "moderator" => false}, + "local" => true, + "tags" => [], + "avatar" => User.avatar_url(user) |> MediaProxy.url(), + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false, + "approval_pending" => true, + "url" => user.ap_id, + "registration_reason" => "Plz let me in!" + } + ] + |> Enum.sort_by(& &1["nickname"]) + + assert json_response(conn, 200) == %{ + "count" => 1, + "page_size" => 50, + "users" => users + } + end + test "load only admins", %{conn: conn, admin: admin} do second_admin = insert(:user, is_admin: true) insert(:user) @@ -966,7 +1059,9 @@ test "load only admins", %{conn: conn, admin: admin} do "avatar" => User.avatar_url(admin) |> MediaProxy.url(), "display_name" => HTML.strip_tags(admin.name || admin.nickname), "confirmation_pending" => false, - "url" => admin.ap_id + "approval_pending" => false, + "url" => admin.ap_id, + "registration_reason" => nil }, %{ "deactivated" => false, @@ -978,7 +1073,9 @@ test "load only admins", %{conn: conn, admin: admin} do "avatar" => User.avatar_url(second_admin) |> MediaProxy.url(), "display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname), "confirmation_pending" => false, - "url" => second_admin.ap_id + "approval_pending" => false, + "url" => second_admin.ap_id, + "registration_reason" => nil } ] |> Enum.sort_by(& &1["nickname"]) @@ -1011,7 +1108,9 @@ test "load only moderators", %{conn: conn} do "avatar" => User.avatar_url(moderator) |> MediaProxy.url(), "display_name" => HTML.strip_tags(moderator.name || moderator.nickname), "confirmation_pending" => false, - "url" => moderator.ap_id + "approval_pending" => false, + "url" => moderator.ap_id, + "registration_reason" => nil } ] } @@ -1037,7 +1136,9 @@ test "load users with tags list", %{conn: conn} do "avatar" => User.avatar_url(user1) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user1.name || user1.nickname), "confirmation_pending" => false, - "url" => user1.ap_id + "approval_pending" => false, + "url" => user1.ap_id, + "registration_reason" => nil }, %{ "deactivated" => false, @@ -1049,7 +1150,9 @@ test "load users with tags list", %{conn: conn} do "avatar" => User.avatar_url(user2) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user2.name || user2.nickname), "confirmation_pending" => false, - "url" => user2.ap_id + "approval_pending" => false, + "url" => user2.ap_id, + "registration_reason" => nil } ] |> Enum.sort_by(& &1["nickname"]) @@ -1089,7 +1192,9 @@ test "it works with multiple filters" do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "approval_pending" => false, + "url" => user.ap_id, + "registration_reason" => nil } ] } @@ -1114,7 +1219,9 @@ test "it omits relay user", %{admin: admin, conn: conn} do "avatar" => User.avatar_url(admin) |> MediaProxy.url(), "display_name" => HTML.strip_tags(admin.name || admin.nickname), "confirmation_pending" => false, - "url" => admin.ap_id + "approval_pending" => false, + "url" => admin.ap_id, + "registration_reason" => nil } ] } @@ -1161,6 +1268,26 @@ test "PATCH /api/pleroma/admin/users/deactivate", %{admin: admin, conn: conn} do "@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}" end + test "PATCH /api/pleroma/admin/users/approve", %{admin: admin, conn: conn} do + user_one = insert(:user, approval_pending: true) + user_two = insert(:user, approval_pending: true) + + conn = + patch( + conn, + "/api/pleroma/admin/users/approve", + %{nicknames: [user_one.nickname, user_two.nickname]} + ) + + response = json_response(conn, 200) + assert Enum.map(response["users"], & &1["approval_pending"]) == [false, false] + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} approved users: @#{user_one.nickname}, @#{user_two.nickname}" + end + test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do user = insert(:user) @@ -1177,7 +1304,9 @@ test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admi "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "approval_pending" => false, + "url" => user.ap_id, + "registration_reason" => nil } log_entry = Repo.one(ModerationLog) @@ -1514,6 +1643,15 @@ test "returns log filtered by search", %{conn: conn, moderator: moderator} do end end + test "gets a remote users when [:instance, :limit_to_local_content] is set to :unauthenticated", + %{conn: conn} do + clear_config(Pleroma.Config.get([:instance, :limit_to_local_content]), :unauthenticated) + user = insert(:user, %{local: false, nickname: "u@peer1.com"}) + conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials") + + assert json_response(conn, 200) + end + describe "GET /users/:nickname/credentials" do test "gets the user credentials", %{conn: conn} do user = insert(:user) @@ -1712,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/controllers/config_controller_test.exs b/test/web/admin_api/controllers/config_controller_test.exs index 064ef9bc7..61bc9fd39 100644 --- a/test/web/admin_api/controllers/config_controller_test.exs +++ b/test/web/admin_api/controllers/config_controller_test.exs @@ -152,6 +152,14 @@ test "subkeys with full update right merge", %{conn: conn} do assert emoji_val[:groups] == [a: 1, b: 2] assert assets_val[:mascots] == [a: 1, b: 2] end + + test "with valid `admin_token` query parameter, skips OAuth scopes check" do + clear_config([:admin_token], "password123") + + build_conn() + |> get("/api/pleroma/admin/config?admin_token=password123") + |> json_response_and_validate_schema(200) + end end test "POST /api/pleroma/admin/config error", %{conn: conn} do diff --git a/test/web/admin_api/controllers/report_controller_test.exs b/test/web/admin_api/controllers/report_controller_test.exs index 940bce340..57946e6bb 100644 --- a/test/web/admin_api/controllers/report_controller_test.exs +++ b/test/web/admin_api/controllers/report_controller_test.exs @@ -204,7 +204,7 @@ test "updates state of multiple reports", %{ test "returns empty response when no reports created", %{conn: conn} do response = conn - |> get("/api/pleroma/admin/reports") + |> get(report_path(conn, :index)) |> json_response_and_validate_schema(:ok) assert Enum.empty?(response["reports"]) @@ -224,7 +224,7 @@ test "returns reports", %{conn: conn} do response = conn - |> get("/api/pleroma/admin/reports") + |> get(report_path(conn, :index)) |> json_response_and_validate_schema(:ok) [report] = response["reports"] @@ -256,7 +256,7 @@ test "returns reports with specified state", %{conn: conn} do response = conn - |> get("/api/pleroma/admin/reports?state=open") + |> get(report_path(conn, :index, %{state: "open"})) |> json_response_and_validate_schema(:ok) assert [open_report] = response["reports"] @@ -268,7 +268,7 @@ test "returns reports with specified state", %{conn: conn} do response = conn - |> get("/api/pleroma/admin/reports?state=closed") + |> get(report_path(conn, :index, %{state: "closed"})) |> json_response_and_validate_schema(:ok) assert [closed_report] = response["reports"] @@ -280,9 +280,7 @@ test "returns reports with specified state", %{conn: conn} do assert %{"total" => 0, "reports" => []} == conn - |> get("/api/pleroma/admin/reports?state=resolved", %{ - "" => "" - }) + |> get(report_path(conn, :index, %{state: "resolved"})) |> json_response_and_validate_schema(:ok) end @@ -297,7 +295,7 @@ test "returns 403 when requested by a non-admin" do |> get("/api/pleroma/admin/reports") assert json_response(conn, :forbidden) == - %{"error" => "User is not an admin or OAuth admin scope is not granted."} + %{"error" => "User is not an admin."} end test "returns 403 when requested by anonymous" do diff --git a/test/web/admin_api/search_test.exs b/test/web/admin_api/search_test.exs index e0e3d4153..b974cedd5 100644 --- a/test/web/admin_api/search_test.exs +++ b/test/web/admin_api/search_test.exs @@ -166,5 +166,16 @@ test "it returns user by email" do assert total == 3 assert count == 1 end + + test "it returns unapproved user" do + unapproved = insert(:user, approval_pending: true) + insert(:user) + insert(:user) + + {:ok, _results, total} = Search.user() + {:ok, [^unapproved], count} = Search.user(%{need_approval: true}) + assert total == 3 + assert count == 1 + end 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/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 7e11fede3..313dda21b 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -624,14 +624,27 @@ test "unreacting to a status with an emoji" do user = insert(:user) other_user = insert(:user) - {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"}) - {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍") + clear_config([:instance, :federating], true) - {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍") + with_mock Pleroma.Web.Federator, + publish: fn _ -> nil end do + {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"}) + {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍") - assert unreaction.data["type"] == "Undo" - assert unreaction.data["object"] == reaction.data["id"] - assert unreaction.local + {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍") + + assert unreaction.data["type"] == "Undo" + assert unreaction.data["object"] == reaction.data["id"] + assert unreaction.local + + # On federation, it contains the undone (and deleted) object + unreaction_with_object = %{ + unreaction + | data: Map.put(unreaction.data, "object", reaction.data) + } + + assert called(Pleroma.Web.Federator.publish(unreaction_with_object)) + end end test "repeating a status" do diff --git a/test/web/feed/user_controller_test.exs b/test/web/feed/user_controller_test.exs index fa2ed1ea5..0d2a61967 100644 --- a/test/web/feed/user_controller_test.exs +++ b/test/web/feed/user_controller_test.exs @@ -181,6 +181,17 @@ test "returns feed with public and unlisted activities", %{conn: conn} do assert activity_titles == ['public', 'unlisted'] end + + test "returns 404 when the user is remote", %{conn: conn} do + user = insert(:user, local: false) + + {:ok, _} = CommonAPI.post(user, %{status: "test"}) + + assert conn + |> put_req_header("accept", "application/atom+xml") + |> get(user_feed_path(conn, :feed, user.nickname)) + |> response(404) + end end # Note: see ActivityPubControllerTest for JSON format tests diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs index b55bb76a7..b888e4c71 100644 --- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs +++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs @@ -108,6 +108,13 @@ test "updates the user's locking status", %{conn: conn} do assert user_data["locked"] == true end + test "updates the user's chat acceptance status", %{conn: conn} do + conn = patch(conn, "/api/v1/accounts/update_credentials", %{accepts_chat_messages: "false"}) + + assert user_data = json_response_and_validate_schema(conn, 200) + assert user_data["pleroma"]["accepts_chat_messages"] == false + end + test "updates the user's allow_following_move", %{user: user, conn: conn} do assert user.allow_following_move == true @@ -344,6 +351,30 @@ test "update fields", %{conn: conn} do ] end + test "emojis in fields labels", %{conn: conn} do + fields = [ + %{"name" => ":firefox:", "value" => "is best 2hu"}, + %{"name" => "they wins", "value" => ":blank:"} + ] + + account_data = + conn + |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) + |> json_response_and_validate_schema(200) + + assert account_data["fields"] == [ + %{"name" => ":firefox:", "value" => "is best 2hu"}, + %{"name" => "they wins", "value" => ":blank:"} + ] + + assert account_data["source"]["fields"] == [ + %{"name" => ":firefox:", "value" => "is best 2hu"}, + %{"name" => "they wins", "value" => ":blank:"} + ] + + assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = account_data["emojis"] + end + test "update fields via x-www-form-urlencoded", %{conn: conn} do fields = [ diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index 9c7b5e9b2..d390c3ce1 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -5,7 +5,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do use Pleroma.Web.ConnCase - alias Pleroma.Config alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub @@ -16,8 +15,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do import Pleroma.Factory describe "account fetching" do - setup do: clear_config([:instance, :limit_to_local_content]) - test "works by id" do %User{id: user_id} = insert(:user) @@ -42,7 +39,7 @@ test "works by nickname" do end test "works by nickname for remote users" do - Config.put([:instance, :limit_to_local_content], false) + clear_config([:instance, :limit_to_local_content], false) user = insert(:user, nickname: "user@example.com", local: false) @@ -53,7 +50,7 @@ test "works by nickname for remote users" do end test "respects limit_to_local_content == :all for remote user nicknames" do - Config.put([:instance, :limit_to_local_content], :all) + clear_config([:instance, :limit_to_local_content], :all) user = insert(:user, nickname: "user@example.com", local: false) @@ -63,7 +60,7 @@ test "respects limit_to_local_content == :all for remote user nicknames" do end test "respects limit_to_local_content == :unauthenticated for remote user nicknames" do - Config.put([:instance, :limit_to_local_content], :unauthenticated) + clear_config([:instance, :limit_to_local_content], :unauthenticated) user = insert(:user, nickname: "user@example.com", local: false) reading_user = insert(:user) @@ -583,6 +580,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 +660,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}") @@ -884,9 +900,11 @@ test "blocking / unblocking a user" do [valid_params: valid_params] end - setup do: clear_config([:instance, :account_activation_required]) + test "registers and logs in without :account_activation_required / :account_approval_required", + %{conn: conn} do + clear_config([:instance, :account_activation_required], false) + clear_config([:instance, :account_approval_required], false) - test "Account registration via Application", %{conn: conn} do conn = conn |> put_req_header("content-type", "application/json") @@ -943,10 +961,134 @@ test "Account registration via Application", %{conn: conn} do token_from_db = Repo.get_by(Token, token: token) assert token_from_db - token_from_db = Repo.preload(token_from_db, :user) - assert token_from_db.user + user = Repo.preload(token_from_db, :user).user - assert token_from_db.user.confirmation_pending + assert user + refute user.confirmation_pending + refute user.approval_pending + end + + test "registers but does not log in with :account_activation_required", %{conn: conn} do + clear_config([:instance, :account_activation_required], true) + clear_config([:instance, :account_approval_required], false) + + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/apps", %{ + client_name: "client_name", + redirect_uris: "urn:ietf:wg:oauth:2.0:oob", + scopes: "read, write, follow" + }) + + assert %{ + "client_id" => client_id, + "client_secret" => client_secret, + "id" => _, + "name" => "client_name", + "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob", + "vapid_key" => _, + "website" => nil + } = json_response_and_validate_schema(conn, 200) + + conn = + post(conn, "/oauth/token", %{ + grant_type: "client_credentials", + client_id: client_id, + client_secret: client_secret + }) + + assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} = + json_response(conn, 200) + + assert token + token_from_db = Repo.get_by(Token, token: token) + assert token_from_db + assert refresh + assert scope == "read write follow" + + conn = + build_conn() + |> put_req_header("content-type", "multipart/form-data") + |> put_req_header("authorization", "Bearer " <> token) + |> post("/api/v1/accounts", %{ + username: "lain", + email: "lain@example.org", + password: "PlzDontHackLain", + bio: "Test Bio", + agreement: true + }) + + response = json_response_and_validate_schema(conn, 200) + assert %{"identifier" => "missing_confirmed_email"} = response + refute response["access_token"] + refute response["token_type"] + + user = Repo.get_by(User, email: "lain@example.org") + assert user.confirmation_pending + end + + test "registers but does not log in with :account_approval_required", %{conn: conn} do + clear_config([:instance, :account_approval_required], true) + clear_config([:instance, :account_activation_required], false) + + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/apps", %{ + client_name: "client_name", + redirect_uris: "urn:ietf:wg:oauth:2.0:oob", + scopes: "read, write, follow" + }) + + assert %{ + "client_id" => client_id, + "client_secret" => client_secret, + "id" => _, + "name" => "client_name", + "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob", + "vapid_key" => _, + "website" => nil + } = json_response_and_validate_schema(conn, 200) + + conn = + post(conn, "/oauth/token", %{ + grant_type: "client_credentials", + client_id: client_id, + client_secret: client_secret + }) + + assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} = + json_response(conn, 200) + + assert token + token_from_db = Repo.get_by(Token, token: token) + assert token_from_db + assert refresh + assert scope == "read write follow" + + conn = + build_conn() + |> put_req_header("content-type", "multipart/form-data") + |> put_req_header("authorization", "Bearer " <> token) + |> post("/api/v1/accounts", %{ + username: "lain", + email: "lain@example.org", + password: "PlzDontHackLain", + bio: "Test Bio", + agreement: true, + reason: "I'm a cool dude, bro" + }) + + response = json_response_and_validate_schema(conn, 200) + assert %{"identifier" => "awaiting_approval"} = response + refute response["access_token"] + refute response["token_type"] + + user = Repo.get_by(User, email: "lain@example.org") + + assert user.approval_pending + assert user.registration_reason == "I'm a cool dude, bro" end test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do @@ -1000,11 +1142,9 @@ test "returns bad_request if missing required params", %{ end) end - setup do: clear_config([:instance, :account_activation_required]) - test "returns bad_request if missing email params when :account_activation_required is enabled", %{conn: conn, valid_params: valid_params} do - Pleroma.Config.put([:instance, :account_activation_required], true) + clear_config([:instance, :account_activation_required], true) app_token = insert(:oauth_token, user: nil) @@ -1169,8 +1309,6 @@ test "respects rate limit setting", %{conn: conn} do assert token_from_db token_from_db = Repo.preload(token_from_db, :user) assert token_from_db.user - - assert token_from_db.user.confirmation_pending end conn = 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/instance_controller_test.exs b/test/web/mastodon_api/controllers/instance_controller_test.exs index cc880d82c..6a9ccd979 100644 --- a/test/web/mastodon_api/controllers/instance_controller_test.exs +++ b/test/web/mastodon_api/controllers/instance_controller_test.exs @@ -27,6 +27,7 @@ test "get instance information", %{conn: conn} do "thumbnail" => _, "languages" => _, "registrations" => _, + "approval_required" => _, "poll_limits" => _, "upload_limit" => _, "avatar_upload_limit" => _, diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs index fd2de8d80..5955d8334 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", @@ -1419,6 +1432,20 @@ test "requires authentication for private posts", %{user: user} do [%{"id" => id}] = response assert id == other_user.id end + + test "returns empty array when :show_reactions is disabled", %{conn: conn, activity: activity} do + clear_config([:instance, :show_reactions], false) + + other_user = insert(:user) + {:ok, _} = CommonAPI.favorite(other_user, activity.id) + + response = + conn + |> get("/api/v1/statuses/#{activity.id}/favourited_by") + |> json_response_and_validate_schema(:ok) + + assert Enum.empty?(response) + end end describe "GET /api/v1/statuses/:id/reblogged_by" do diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs index f5bfc9c67..8f37efa3c 100644 --- a/test/web/mastodon_api/views/account_view_test.exs +++ b/test/web/mastodon_api/views/account_view_test.exs @@ -90,11 +90,12 @@ test "Represent a user account" do hide_followers_count: false, hide_follows_count: false, relationship: %{}, - skip_thread_containment: false + skip_thread_containment: false, + accepts_chat_messages: nil } } - 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 @@ -107,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 @@ -186,11 +185,12 @@ test "Represent a Service(bot) account" do hide_followers_count: false, hide_follows_count: false, relationship: %{}, - skip_thread_containment: false + skip_thread_containment: false, + accepts_chat_messages: nil } } - 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 @@ -199,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 @@ -224,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} @@ -337,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}) @@ -346,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 @@ -376,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 @@ -389,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 @@ -532,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/media_proxy/media_proxy_controller_test.exs b/test/web/media_proxy/media_proxy_controller_test.exs index d61cef83b..d4db44c63 100644 --- a/test/web/media_proxy/media_proxy_controller_test.exs +++ b/test/web/media_proxy/media_proxy_controller_test.exs @@ -4,82 +4,118 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do use Pleroma.Web.ConnCase - import Mock - alias Pleroma.Config - setup do: clear_config(:media_proxy) - setup do: clear_config([Pleroma.Web.Endpoint, :secret_key_base]) + import Mock + + alias Pleroma.Web.MediaProxy + alias Pleroma.Web.MediaProxy.MediaProxyController + alias Plug.Conn setup do on_exit(fn -> Cachex.clear(:banned_urls_cache) end) end test "it returns 404 when MediaProxy disabled", %{conn: conn} do - Config.put([:media_proxy, :enabled], false) + clear_config([:media_proxy, :enabled], false) - assert %Plug.Conn{ + assert %Conn{ status: 404, resp_body: "Not Found" } = get(conn, "/proxy/hhgfh/eeeee") - assert %Plug.Conn{ + assert %Conn{ status: 404, resp_body: "Not Found" } = get(conn, "/proxy/hhgfh/eeee/fff") end - test "it returns 403 when signature invalidated", %{conn: conn} do - Config.put([:media_proxy, :enabled], true) - Config.put([Pleroma.Web.Endpoint, :secret_key_base], "00000000000") - path = URI.parse(Pleroma.Web.MediaProxy.encode_url("https://google.fn")).path - Config.put([Pleroma.Web.Endpoint, :secret_key_base], "000") + describe "" do + setup do + clear_config([:media_proxy, :enabled], true) + clear_config([Pleroma.Web.Endpoint, :secret_key_base], "00000000000") + [url: MediaProxy.encode_url("https://google.fn/test.png")] + end - assert %Plug.Conn{ - status: 403, - resp_body: "Forbidden" - } = get(conn, path) + test "it returns 403 for invalid signature", %{conn: conn, url: url} do + Pleroma.Config.put([Pleroma.Web.Endpoint, :secret_key_base], "000") + %{path: path} = URI.parse(url) - assert %Plug.Conn{ - status: 403, - resp_body: "Forbidden" - } = get(conn, "/proxy/hhgfh/eeee") + assert %Conn{ + status: 403, + resp_body: "Forbidden" + } = get(conn, path) - assert %Plug.Conn{ - status: 403, - resp_body: "Forbidden" - } = get(conn, "/proxy/hhgfh/eeee/fff") - end + assert %Conn{ + status: 403, + resp_body: "Forbidden" + } = get(conn, "/proxy/hhgfh/eeee") - test "redirects on valid url when filename invalidated", %{conn: conn} do - Config.put([:media_proxy, :enabled], true) - Config.put([Pleroma.Web.Endpoint, :secret_key_base], "00000000000") - url = Pleroma.Web.MediaProxy.encode_url("https://google.fn/test.png") - invalid_url = String.replace(url, "test.png", "test-file.png") - response = get(conn, invalid_url) - assert response.status == 302 - assert redirected_to(response) == url - end + assert %Conn{ + status: 403, + resp_body: "Forbidden" + } = get(conn, "/proxy/hhgfh/eeee/fff") + end - test "it performs ReverseProxy.call when signature valid", %{conn: conn} do - Config.put([:media_proxy, :enabled], true) - Config.put([Pleroma.Web.Endpoint, :secret_key_base], "00000000000") - url = Pleroma.Web.MediaProxy.encode_url("https://google.fn/test.png") + test "redirects on valid url when filename is invalidated", %{conn: conn, url: url} do + invalid_url = String.replace(url, "test.png", "test-file.png") + response = get(conn, invalid_url) + assert response.status == 302 + assert redirected_to(response) == url + end - with_mock Pleroma.ReverseProxy, - call: fn _conn, _url, _opts -> %Plug.Conn{status: :success} end do - assert %Plug.Conn{status: :success} = get(conn, url) + test "it performs ReverseProxy.call with valid signature", %{conn: conn, url: url} do + with_mock Pleroma.ReverseProxy, + call: fn _conn, _url, _opts -> %Conn{status: :success} end do + assert %Conn{status: :success} = get(conn, url) + end + end + + test "it returns 404 when url is in banned_urls cache", %{conn: conn, url: url} do + MediaProxy.put_in_banned_urls("https://google.fn/test.png") + + with_mock Pleroma.ReverseProxy, + call: fn _conn, _url, _opts -> %Conn{status: :success} end do + assert %Conn{status: 404, resp_body: "Not Found"} = get(conn, url) + end end end - test "it returns 404 when url contains in banned_urls cache", %{conn: conn} do - Config.put([:media_proxy, :enabled], true) - Config.put([Pleroma.Web.Endpoint, :secret_key_base], "00000000000") - url = Pleroma.Web.MediaProxy.encode_url("https://google.fn/test.png") - Pleroma.Web.MediaProxy.put_in_banned_urls("https://google.fn/test.png") + describe "filename_matches/3" do + test "preserves the encoded or decoded path" do + assert MediaProxyController.filename_matches( + %{"filename" => "/Hello world.jpg"}, + "/Hello world.jpg", + "http://pleroma.social/Hello world.jpg" + ) == :ok - with_mock Pleroma.ReverseProxy, - call: fn _conn, _url, _opts -> %Plug.Conn{status: :success} end do - assert %Plug.Conn{status: 404, resp_body: "Not Found"} = get(conn, url) + assert MediaProxyController.filename_matches( + %{"filename" => "/Hello%20world.jpg"}, + "/Hello%20world.jpg", + "http://pleroma.social/Hello%20world.jpg" + ) == :ok + + assert MediaProxyController.filename_matches( + %{"filename" => "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"}, + "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg", + "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg" + ) == :ok + + assert MediaProxyController.filename_matches( + %{"filename" => "/my%2Flong%2Furl%2F2019%2F07%2FS.jp"}, + "/my%2Flong%2Furl%2F2019%2F07%2FS.jp", + "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg" + ) == {:wrong_filename, "my%2Flong%2Furl%2F2019%2F07%2FS.jpg"} + end + + test "encoded url are tried to match for proxy as `conn.request_path` encodes the url" do + # conn.request_path will return encoded url + request_path = "/ANALYSE-DAI-_-LE-STABLECOIN-100-D%C3%89CENTRALIS%C3%89-BQ.jpg" + + assert MediaProxyController.filename_matches( + true, + request_path, + "https://mydomain.com/uploads/2019/07/ANALYSE-DAI-_-LE-STABLECOIN-100-DÉCENTRALISÉ-BQ.jpg" + ) == :ok end end end diff --git a/test/web/media_proxy/media_proxy_test.exs b/test/web/media_proxy/media_proxy_test.exs index 69d2a71a6..72885cfdd 100644 --- a/test/web/media_proxy/media_proxy_test.exs +++ b/test/web/media_proxy/media_proxy_test.exs @@ -5,38 +5,33 @@ defmodule Pleroma.Web.MediaProxyTest do use ExUnit.Case use Pleroma.Tests.Helpers - import Pleroma.Web.MediaProxy - alias Pleroma.Web.MediaProxy.MediaProxyController - setup do: clear_config([:media_proxy, :enabled]) - setup do: clear_config(Pleroma.Upload) + alias Pleroma.Web.Endpoint + alias Pleroma.Web.MediaProxy describe "when enabled" do - setup do - Pleroma.Config.put([:media_proxy, :enabled], true) - :ok - end + setup do: clear_config([:media_proxy, :enabled], true) test "ignores invalid url" do - assert url(nil) == nil - assert url("") == nil + assert MediaProxy.url(nil) == nil + assert MediaProxy.url("") == nil end test "ignores relative url" do - assert url("/local") == "/local" - assert url("/") == "/" + assert MediaProxy.url("/local") == "/local" + assert MediaProxy.url("/") == "/" end test "ignores local url" do - local_url = Pleroma.Web.Endpoint.url() <> "/hello" - local_root = Pleroma.Web.Endpoint.url() - assert url(local_url) == local_url - assert url(local_root) == local_root + local_url = Endpoint.url() <> "/hello" + local_root = Endpoint.url() + assert MediaProxy.url(local_url) == local_url + assert MediaProxy.url(local_root) == local_root end test "encodes and decodes URL" do url = "https://pleroma.soykaf.com/static/logo.png" - encoded = url(url) + encoded = MediaProxy.url(url) assert String.starts_with?( encoded, @@ -50,86 +45,44 @@ test "encodes and decodes URL" do test "encodes and decodes URL without a path" do url = "https://pleroma.soykaf.com" - encoded = url(url) + encoded = MediaProxy.url(url) assert decode_result(encoded) == url end test "encodes and decodes URL without an extension" do url = "https://pleroma.soykaf.com/path/" - encoded = url(url) + encoded = MediaProxy.url(url) assert String.ends_with?(encoded, "/path") assert decode_result(encoded) == url end test "encodes and decodes URL and ignores query params for the path" do url = "https://pleroma.soykaf.com/static/logo.png?93939393939&bunny=true" - encoded = url(url) + encoded = MediaProxy.url(url) assert String.ends_with?(encoded, "/logo.png") assert decode_result(encoded) == url end test "validates signature" do - secret_key_base = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base]) + encoded = MediaProxy.url("https://pleroma.social") - on_exit(fn -> - Pleroma.Config.put([Pleroma.Web.Endpoint, :secret_key_base], secret_key_base) - end) - - encoded = url("https://pleroma.social") - - Pleroma.Config.put( - [Pleroma.Web.Endpoint, :secret_key_base], + clear_config( + [Endpoint, :secret_key_base], "00000000000000000000000000000000000000000000000" ) [_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/") - assert decode_url(sig, base64) == {:error, :invalid_signature} - end - - test "filename_matches preserves the encoded or decoded path" do - assert MediaProxyController.filename_matches( - %{"filename" => "/Hello world.jpg"}, - "/Hello world.jpg", - "http://pleroma.social/Hello world.jpg" - ) == :ok - - assert MediaProxyController.filename_matches( - %{"filename" => "/Hello%20world.jpg"}, - "/Hello%20world.jpg", - "http://pleroma.social/Hello%20world.jpg" - ) == :ok - - assert MediaProxyController.filename_matches( - %{"filename" => "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"}, - "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg", - "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg" - ) == :ok - - assert MediaProxyController.filename_matches( - %{"filename" => "/my%2Flong%2Furl%2F2019%2F07%2FS.jp"}, - "/my%2Flong%2Furl%2F2019%2F07%2FS.jp", - "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg" - ) == {:wrong_filename, "my%2Flong%2Furl%2F2019%2F07%2FS.jpg"} - end - - test "encoded url are tried to match for proxy as `conn.request_path` encodes the url" do - # conn.request_path will return encoded url - request_path = "/ANALYSE-DAI-_-LE-STABLECOIN-100-D%C3%89CENTRALIS%C3%89-BQ.jpg" - - assert MediaProxyController.filename_matches( - true, - request_path, - "https://mydomain.com/uploads/2019/07/ANALYSE-DAI-_-LE-STABLECOIN-100-DÉCENTRALISÉ-BQ.jpg" - ) == :ok + assert MediaProxy.decode_url(sig, base64) == {:error, :invalid_signature} end test "uses the configured base_url" do - clear_config([:media_proxy, :base_url], "https://cache.pleroma.social") + base_url = "https://cache.pleroma.social" + clear_config([:media_proxy, :base_url], base_url) url = "https://pleroma.soykaf.com/static/logo.png" - encoded = url(url) + encoded = MediaProxy.url(url) - assert String.starts_with?(encoded, Pleroma.Config.get([:media_proxy, :base_url])) + assert String.starts_with?(encoded, base_url) end # Some sites expect ASCII encoded characters in the URL to be preserved even if @@ -140,7 +93,7 @@ test "preserve ASCII encoding" do url = "https://pleroma.com/%20/%21/%22/%23/%24/%25/%26/%27/%28/%29/%2A/%2B/%2C/%2D/%2E/%2F/%30/%31/%32/%33/%34/%35/%36/%37/%38/%39/%3A/%3B/%3C/%3D/%3E/%3F/%40/%41/%42/%43/%44/%45/%46/%47/%48/%49/%4A/%4B/%4C/%4D/%4E/%4F/%50/%51/%52/%53/%54/%55/%56/%57/%58/%59/%5A/%5B/%5C/%5D/%5E/%5F/%60/%61/%62/%63/%64/%65/%66/%67/%68/%69/%6A/%6B/%6C/%6D/%6E/%6F/%70/%71/%72/%73/%74/%75/%76/%77/%78/%79/%7A/%7B/%7C/%7D/%7E/%7F/%80/%81/%82/%83/%84/%85/%86/%87/%88/%89/%8A/%8B/%8C/%8D/%8E/%8F/%90/%91/%92/%93/%94/%95/%96/%97/%98/%99/%9A/%9B/%9C/%9D/%9E/%9F/%C2%A0/%A1/%A2/%A3/%A4/%A5/%A6/%A7/%A8/%A9/%AA/%AB/%AC/%C2%AD/%AE/%AF/%B0/%B1/%B2/%B3/%B4/%B5/%B6/%B7/%B8/%B9/%BA/%BB/%BC/%BD/%BE/%BF/%C0/%C1/%C2/%C3/%C4/%C5/%C6/%C7/%C8/%C9/%CA/%CB/%CC/%CD/%CE/%CF/%D0/%D1/%D2/%D3/%D4/%D5/%D6/%D7/%D8/%D9/%DA/%DB/%DC/%DD/%DE/%DF/%E0/%E1/%E2/%E3/%E4/%E5/%E6/%E7/%E8/%E9/%EA/%EB/%EC/%ED/%EE/%EF/%F0/%F1/%F2/%F3/%F4/%F5/%F6/%F7/%F8/%F9/%FA/%FB/%FC/%FD/%FE/%FF" - encoded = url(url) + encoded = MediaProxy.url(url) assert decode_result(encoded) == url end @@ -151,56 +104,49 @@ test "preserve non-unicode characters per RFC3986" do url = "https://pleroma.com/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890-._~:/?#[]@!$&'()*+,;=|^`{}" - encoded = url(url) + encoded = MediaProxy.url(url) assert decode_result(encoded) == url end test "preserve unicode characters" do url = "https://ko.wikipedia.org/wiki/위키백과:대문" - encoded = url(url) + encoded = MediaProxy.url(url) assert decode_result(encoded) == url end end describe "when disabled" do - setup do - enabled = Pleroma.Config.get([:media_proxy, :enabled]) - - if enabled do - Pleroma.Config.put([:media_proxy, :enabled], false) - - on_exit(fn -> - Pleroma.Config.put([:media_proxy, :enabled], enabled) - :ok - end) - end - - :ok - end + setup do: clear_config([:media_proxy, :enabled], false) test "does not encode remote urls" do - assert url("https://google.fr") == "https://google.fr" + assert MediaProxy.url("https://google.fr") == "https://google.fr" end end defp decode_result(encoded) do [_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/") - {:ok, decoded} = decode_url(sig, base64) + {:ok, decoded} = MediaProxy.decode_url(sig, base64) decoded end describe "whitelist" do - setup do - Pleroma.Config.put([:media_proxy, :enabled], true) - :ok - end + setup do: clear_config([:media_proxy, :enabled], true) test "mediaproxy whitelist" do - Pleroma.Config.put([:media_proxy, :whitelist], ["google.com", "feld.me"]) + clear_config([:media_proxy, :whitelist], ["https://google.com", "https://feld.me"]) url = "https://feld.me/foo.png" - unencoded = url(url) + unencoded = MediaProxy.url(url) + assert unencoded == url + end + + # TODO: delete after removing support bare domains for media proxy whitelist + test "mediaproxy whitelist bare domains whitelist (deprecated)" do + clear_config([:media_proxy, :whitelist], ["google.com", "feld.me"]) + url = "https://feld.me/foo.png" + + unencoded = MediaProxy.url(url) assert unencoded == url end @@ -211,17 +157,17 @@ test "does not change whitelisted urls" do media_url = "https://mycdn.akamai.com" url = "#{media_url}/static/logo.png" - encoded = url(url) + encoded = MediaProxy.url(url) assert String.starts_with?(encoded, media_url) end test "ensure Pleroma.Upload base_url is always whitelisted" do media_url = "https://media.pleroma.social" - Pleroma.Config.put([Pleroma.Upload, :base_url], media_url) + clear_config([Pleroma.Upload, :base_url], media_url) url = "#{media_url}/static/logo.png" - encoded = url(url) + encoded = MediaProxy.url(url) assert String.starts_with?(encoded, media_url) end diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs index d389e4ce0..1200126b8 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -19,7 +19,10 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do key: "_test", signing_salt: "cooldude" ] - setup do: clear_config([:instance, :account_activation_required]) + setup do + clear_config([:instance, :account_activation_required]) + clear_config([:instance, :account_approval_required]) + end describe "in OAuth consumer mode, " do setup do @@ -995,6 +998,30 @@ test "rejects token exchange for user with confirmation_pending set to true" do } end + test "rejects token exchange for valid credentials belonging to an unapproved user" do + password = "testpassword" + + user = insert(:user, password_hash: Pbkdf2.hash_pwd_salt(password), approval_pending: true) + + refute Pleroma.User.account_status(user) == :active + + app = insert(:oauth_app) + + conn = + build_conn() + |> post("/oauth/token", %{ + "grant_type" => "password", + "username" => user.nickname, + "password" => password, + "client_id" => app.client_id, + "client_secret" => app.client_secret + }) + + assert resp = json_response(conn, 403) + assert %{"error" => _} = resp + refute Map.has_key?(resp, "access_token") + end + test "rejects an invalid authorization code" do app = insert(:oauth_app) 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/controllers/emoji_reaction_controller_test.exs b/test/web/pleroma_api/controllers/emoji_reaction_controller_test.exs index e1bb5ebfe..3deab30d1 100644 --- a/test/web/pleroma_api/controllers/emoji_reaction_controller_test.exs +++ b/test/web/pleroma_api/controllers/emoji_reaction_controller_test.exs @@ -106,6 +106,23 @@ test "GET /api/v1/pleroma/statuses/:id/reactions", %{conn: conn} do result end + test "GET /api/v1/pleroma/statuses/:id/reactions with :show_reactions disabled", %{conn: conn} do + clear_config([:instance, :show_reactions], false) + + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{status: "#cofe"}) + {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅") + + result = + conn + |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions") + |> json_response_and_validate_schema(200) + + assert result == [] + end + test "GET /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do user = insert(:user) other_user = insert(:user) diff --git a/test/web/pleroma_api/views/chat/message_reference_view_test.exs b/test/web/pleroma_api/views/chat/message_reference_view_test.exs index e5b165255..40dbae3cd 100644 --- a/test/web/pleroma_api/views/chat/message_reference_view_test.exs +++ b/test/web/pleroma_api/views/chat/message_reference_view_test.exs @@ -43,7 +43,17 @@ test "it displays a chat message" do assert chat_message[:unread] == false assert match?([%{shortcode: "firefox"}], chat_message[:emojis]) - {:ok, activity} = CommonAPI.post_chat_message(recipient, user, "gkgkgk", media_id: upload.id) + clear_config([:rich_media, :enabled], true) + + Tesla.Mock.mock(fn + %{url: "https://example.com/ogp"} -> + %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")} + end) + + {:ok, activity} = + CommonAPI.post_chat_message(recipient, user, "gkgkgk https://example.com/ogp", + media_id: upload.id + ) object = Object.normalize(activity) @@ -52,10 +62,11 @@ test "it displays a chat message" do chat_message_two = MessageReferenceView.render("show.json", chat_message_reference: cm_ref) assert chat_message_two[:id] == cm_ref.id - assert chat_message_two[:content] == "gkgkgk" + assert chat_message_two[:content] == object.data["content"] assert chat_message_two[:account_id] == recipient.id assert chat_message_two[:chat_id] == chat_message[:chat_id] assert chat_message_two[:attachment] assert chat_message_two[:unread] == true + assert chat_message_two[:card] end end 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 368533292..20a45cb6f 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -4,11 +4,11 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do use Pleroma.DataCase + import Pleroma.Factory alias Pleroma.Repo alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.UserInviteToken - alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.TwitterAPI.TwitterAPI setup_all do @@ -27,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", @@ -45,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 @@ -85,6 +79,42 @@ test "it sends confirmation email if :account_activation_required is specified i ) end + test "it sends an admin email if :account_approval_required is specified in instance config" do + admin = insert(:user, is_admin: true) + setting = Pleroma.Config.get([:instance, :account_approval_required]) + + unless setting do + Pleroma.Config.put([:instance, :account_approval_required], true) + on_exit(fn -> Pleroma.Config.put([:instance, :account_approval_required], setting) end) + end + + data = %{ + :username => "lain", + :email => "lain@wired.jp", + :fullname => "lain iwakura", + :bio => "", + :password => "bear", + :confirm => "bear", + :reason => "I love anime" + } + + {:ok, user} = TwitterAPI.register_user(data) + ObanHelpers.perform_all() + + assert user.approval_pending + + email = Pleroma.Emails.AdminEmail.new_unapproved_registration(admin, user) + + notify_email = Pleroma.Config.get([:instance, :notify_email]) + instance_name = Pleroma.Config.get([:instance, :name]) + + Swoosh.TestAssertions.assert_email_sent( + from: {instance_name, notify_email}, + to: {admin.name, admin.email}, + html_body: email.html_body + ) + end + test "it registers a new user and parses mentions in the bio" do data1 = %{ :username => "john", @@ -134,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 @@ -197,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} @@ -260,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", @@ -302,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 @@ -327,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 diff --git a/test/workers/cron/clear_oauth_token_worker_test.exs b/test/workers/cron/clear_oauth_token_worker_test.exs index df82dc75d..67836f34f 100644 --- a/test/workers/cron/clear_oauth_token_worker_test.exs +++ b/test/workers/cron/clear_oauth_token_worker_test.exs @@ -16,7 +16,7 @@ test "deletes expired tokens" do ) Pleroma.Config.put([:oauth2, :clean_expired_tokens], true) - ClearOauthTokenWorker.perform(:opts, :job) + ClearOauthTokenWorker.perform(%Oban.Job{}) assert Pleroma.Repo.all(Pleroma.Web.OAuth.Token) == [] end end diff --git a/test/workers/cron/digest_emails_worker_test.exs b/test/workers/cron/digest_emails_worker_test.exs index f9bc50db5..65887192e 100644 --- a/test/workers/cron/digest_emails_worker_test.exs +++ b/test/workers/cron/digest_emails_worker_test.exs @@ -35,7 +35,7 @@ defmodule Pleroma.Workers.Cron.DigestEmailsWorkerTest do end test "it sends digest emails", %{user2: user2} do - Pleroma.Workers.Cron.DigestEmailsWorker.perform(:opts, :pid) + Pleroma.Workers.Cron.DigestEmailsWorker.perform(%Oban.Job{}) # Performing job(s) enqueued at previous step ObanHelpers.perform_all() @@ -47,7 +47,7 @@ test "it sends digest emails", %{user2: user2} do test "it doesn't fail when a user has no email", %{user2: user2} do {:ok, _} = user2 |> Ecto.Changeset.change(%{email: nil}) |> Pleroma.Repo.update() - Pleroma.Workers.Cron.DigestEmailsWorker.perform(:opts, :pid) + Pleroma.Workers.Cron.DigestEmailsWorker.perform(%Oban.Job{}) # Performing job(s) enqueued at previous step ObanHelpers.perform_all() end diff --git a/test/workers/cron/new_users_digest_worker_test.exs b/test/workers/cron/new_users_digest_worker_test.exs index ee589bb55..129534cb1 100644 --- a/test/workers/cron/new_users_digest_worker_test.exs +++ b/test/workers/cron/new_users_digest_worker_test.exs @@ -17,7 +17,7 @@ test "it sends new users digest emails" do user2 = insert(:user, %{inserted_at: yesterday}) CommonAPI.post(user, %{status: "cofe"}) - NewUsersDigestWorker.perform(nil, nil) + NewUsersDigestWorker.perform(%Oban.Job{}) ObanHelpers.perform_all() assert_received {:email, email} @@ -39,7 +39,7 @@ test "it doesn't fail when admin has no email" do CommonAPI.post(user, %{status: "cofe"}) - NewUsersDigestWorker.perform(nil, nil) + NewUsersDigestWorker.perform(%Oban.Job{}) ObanHelpers.perform_all() end end diff --git a/test/workers/cron/purge_expired_activities_worker_test.exs b/test/workers/cron/purge_expired_activities_worker_test.exs index b1db59fdf..d1acd9ae6 100644 --- a/test/workers/cron/purge_expired_activities_worker_test.exs +++ b/test/workers/cron/purge_expired_activities_worker_test.exs @@ -32,7 +32,7 @@ test "deletes an expiration activity" do %{activity_id: activity.id, scheduled_at: naive_datetime} ) - Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(:ops, :pid) + Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(%Oban.Job{}) refute Pleroma.Repo.get(Pleroma.Activity, activity.id) refute Pleroma.Repo.get(Pleroma.ActivityExpiration, expiration.id) @@ -58,7 +58,7 @@ test "works with ActivityExpirationPolicy" do |> Ecto.Changeset.change(%{scheduled_at: past_date}) |> Repo.update!() - Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(:ops, :pid) + Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(%Oban.Job{}) assert [%{data: %{"type" => "Delete", "deleted_activity_id" => ^id}}] = Pleroma.Repo.all(Pleroma.Activity) diff --git a/test/workers/scheduled_activity_worker_test.exs b/test/workers/scheduled_activity_worker_test.exs index b312d975b..f3eddf7b1 100644 --- a/test/workers/scheduled_activity_worker_test.exs +++ b/test/workers/scheduled_activity_worker_test.exs @@ -32,10 +32,7 @@ test "creates a status from the scheduled activity" do params: %{status: "hi"} ) - ScheduledActivityWorker.perform( - %{"activity_id" => scheduled_activity.id}, - :pid - ) + ScheduledActivityWorker.perform(%Oban.Job{args: %{"activity_id" => scheduled_activity.id}}) refute Repo.get(ScheduledActivity, scheduled_activity.id) activity = Repo.all(Pleroma.Activity) |> Enum.find(&(&1.actor == user.ap_id)) @@ -46,7 +43,7 @@ test "adds log message if ScheduledActivity isn't find" do Pleroma.Config.put([ScheduledActivity, :enabled], true) assert capture_log([level: :error], fn -> - ScheduledActivityWorker.perform(%{"activity_id" => 42}, :pid) + ScheduledActivityWorker.perform(%Oban.Job{args: %{"activity_id" => 42}}) end) =~ "Couldn't find scheduled activity" end end