logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://hacktivis.me/git/pleroma.git
commit: 918c406a914d49b15beb3611e1c780d0e0d253cd
parent 90b442727e4e2e56b4b68a15172a5ef7516531df
Author: marcin mikołajczak <git@mkljczk.pl>
Date:   Mon, 18 Mar 2024 13:50:25 +0100

Merge remote-tracking branch 'origin/develop' into instance_rules

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>

Diffstat:

A.dialyzer_ignore.exs6++++++
M.gitignore5+++--
M.gitlab-ci.yml93+++++++++++++++++++++++++++++++++++++++++++------------------------------------
MCHANGELOG.md17+++++++++++------
Achangelog.d/account-rendering-auth-check.fix1+
Achangelog.d/api-docs.skip0
Achangelog.d/atom-leak.skip0
Achangelog.d/backups-follows.add2++
Achangelog.d/bandit.change1+
Achangelog.d/bugfix-ccworks.fix2++
Achangelog.d/chat-attachment-empty-array.fix1+
Achangelog.d/config-stat-symlink.fix1+
Achangelog.d/content-length.fix1+
Achangelog.d/deps-bump-2024-01-25.skip0
Achangelog.d/dialyzer.skip0
Achangelog.d/dialyzer2.skip0
Achangelog.d/dialyzer3.skip0
Achangelog.d/dialyzer4.skip0
Achangelog.d/emoji-download-paginate.fix2++
Achangelog.d/emoji-use-v1.fix2++
Achangelog.d/exile-bsds.skip0
Achangelog.d/exile-freebsd.skip0
Achangelog.d/exile-macos.skip0
Achangelog.d/exile.skip0
Achangelog.d/federator-modules.remove1+
Achangelog.d/federator.skip0
Achangelog.d/finch_redirects.fix1+
Achangelog.d/fix-duplicate-inbox-deliveries.fix0
Achangelog.d/force-mention-mrf.add2++
Achangelog.d/framegrabs.fix1+
Achangelog.d/group-actor.add1+
Achangelog.d/gun-logs.skip0
Achangelog.d/gun_pool.fix1+
Achangelog.d/gun_pool2.fix1+
Achangelog.d/gun_pool3.skip0
Achangelog.d/handle_object_fetch_failures.change1+
Achangelog.d/instance-contact-account.add2++
Achangelog.d/instance-defdelegates.skip0
Achangelog.d/instance-v2.skip0
Achangelog.d/issue-3241.fix1+
Achangelog.d/link-verification.add2++
Achangelog.d/local-webfinger.fix2++
Achangelog.d/mastodon_directory.fix1+
Achangelog.d/memleak.fix1+
Achangelog.d/mergeback-2.6.2.skip0
Achangelog.d/mrf-regex-error.fix1+
Achangelog.d/mrf-steal-emoji-extname.fix1+
Achangelog.d/mrf_hashtags.fix1+
Achangelog.d/nil-content-map.fix1+
Achangelog.d/notifications-index.fix1+
Achangelog.d/notifications.fix2++
Achangelog.d/oauth-nickname.skip2++
Achangelog.d/otp26.add1+
Achangelog.d/public-polls.add2++
Achangelog.d/publisher_discard.change1+
Achangelog.d/publisher_log.change1+
Achangelog.d/qtfaststart.fix1+
Achangelog.d/remote-fetcher-error.skip0
Achangelog.d/rich_media.fix1+
Achangelog.d/rich_media_tests.skip0
Achangelog.d/tesla.deps1+
Achangelog.d/typo.skip0
Achangelog.d/websocket-refactor.change1+
Dci/build_and_push.sh1-
Rci/Dockerfile -> ci/elixir-1.12/Dockerfile0
Aci/elixir-1.12/build_and_push.sh1+
Aci/elixir-1.15-otp25/Dockerfile8++++++++
Aci/elixir-1.15-otp25/build_and_push.sh1+
Rci/postgres_rum/Dockerfile -> ci/postgres-with-rum-13/Dockerfile0
Rci/postgres_rum/build_and_push.sh -> ci/postgres-with-rum-13/build_and_push.sh0
Mconfig/benchmark.exs4++++
Mconfig/config.exs25++++++++++++++-----------
Mconfig/description.exs20+++++++++++++++++---
Mconfig/dev.exs3+--
Mconfig/test.exs12++++++++++++
Mdocs/administration/CLI_tasks/config.md4++--
Mdocs/administration/backup.md2+-
Mdocs/configuration/cheatsheet.md15++++++++++-----
Mdocs/configuration/custom_emoji.md2+-
Mdocs/configuration/i2p.md2+-
Mdocs/configuration/onion_federation.md2+-
Mdocs/configuration/optimizing_beam.md2+-
Mdocs/configuration/postgresql.md2+-
Mdocs/configuration/search.md4++--
Mdocs/development/API/admin_api.md10+++++-----
Mdocs/development/API/differences_in_mastoapi_responses.md14++++++++++++--
Mdocs/development/API/pleroma_api.md30++++++++++++++++++++++++++++--
Mdocs/development/ap_extensions.md6+++---
Mdocs/development/setting_up_pleroma_dev.md2+-
Mdocs/installation/generic_dependencies.include2+-
Mdocs/installation/gentoo_en.md4++--
Mdocs/installation/gentoo_otp_en.md2+-
Mdocs/installation/openbsd_en.md4++--
Mdocs/installation/otp_en.md2+-
Minstallation/pleroma-mongooseim.cfg6+++---
Mlib/mix/tasks/pleroma/database.ex2+-
Mlib/mix/tasks/pleroma/digest.ex2+-
Mlib/mix/tasks/pleroma/ecto/rollback.ex2+-
Mlib/mix/tasks/pleroma/emoji.ex2+-
Mlib/mix/tasks/pleroma/instance.ex4+---
Dlib/phoenix/transports/web_socket/raw.ex93-------------------------------------------------------------------------------
Mlib/pleroma/activity/html.ex2+-
Mlib/pleroma/activity/queries.ex2+-
Mlib/pleroma/announcement.ex16+++++++++-------
Mlib/pleroma/application.ex101+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mlib/pleroma/application_requirements.ex7++++---
Mlib/pleroma/bookmark.ex10+++++-----
Mlib/pleroma/caching.ex3+++
Mlib/pleroma/captcha/kocaptcha.ex2+-
Mlib/pleroma/chat.ex12++++++------
Mlib/pleroma/config/deprecation_warnings.ex18+++++++++---------
Mlib/pleroma/config/release_runtime_provider.ex2+-
Mlib/pleroma/config_db.ex6+++---
Mlib/pleroma/constants.ex8++++++++
Mlib/pleroma/conversation.ex2+-
Mlib/pleroma/data_migration.ex2++
Mlib/pleroma/docs/generator.ex2+-
Mlib/pleroma/docs/json.ex2+-
Mlib/pleroma/ecto_type/activity_pub/object_validators/bare_uri.ex10++++++----
Mlib/pleroma/emoji.ex16+++++++++-------
Mlib/pleroma/emoji/loader.ex4+---
Mlib/pleroma/emoji/pack.ex14++++++++++----
Mlib/pleroma/filter.ex3---
Mlib/pleroma/following_relationship.ex10+++++-----
Mlib/pleroma/gopher/server.ex2+-
Mlib/pleroma/gun/connection_pool/reclaimer.ex2+-
Mlib/pleroma/gun/connection_pool/worker_supervisor.ex6++++--
Mlib/pleroma/helpers/media_helper.ex103++++++++++++++++++-------------------------------------------------------------
Mlib/pleroma/helpers/qt_fast_start.ex37++++++++++++++++++++++++-------------
Mlib/pleroma/html.ex31+++++++++++--------------------
Mlib/pleroma/http.ex4++++
Mlib/pleroma/http/adapter_helper.ex4++--
Mlib/pleroma/http/request_builder.ex4++--
Mlib/pleroma/instances.ex11+++++------
Mlib/pleroma/maps.ex15++++++++++++++-
Mlib/pleroma/mfa.ex2+-
Mlib/pleroma/mfa/totp.ex3++-
Mlib/pleroma/migrators/hashtags_table_migrator.ex2+-
Mlib/pleroma/migrators/support/base_migrator.ex5+++--
Mlib/pleroma/moderation_log.ex5+++--
Mlib/pleroma/notification.ex8++++----
Mlib/pleroma/object.ex13++++++++-----
Mlib/pleroma/object/fetcher.ex63+++++++++++++++++++++++++++++----------------------------------
Mlib/pleroma/pagination.ex9+++++----
Mlib/pleroma/password/pbkdf2.ex2+-
Mlib/pleroma/release_tasks.ex6------
Mlib/pleroma/report_note.ex8++++----
Mlib/pleroma/reverse_proxy.ex18++++++------------
Mlib/pleroma/search/search_backend.ex4++--
Mlib/pleroma/signature.ex2+-
Mlib/pleroma/telemetry/logger.ex4++--
Mlib/pleroma/upload.ex5+++--
Mlib/pleroma/uploaders/uploader.ex11++---------
Mlib/pleroma/user.ex102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mlib/pleroma/user/backup.ex32+++++++++++++++++++++++++++++---
Mlib/pleroma/user/query.ex4++--
Mlib/pleroma/user_invite_token.ex2+-
Mlib/pleroma/user_relationship.ex2++
Mlib/pleroma/web/activity_pub/activity_pub.ex17++++++++---------
Mlib/pleroma/web/activity_pub/builder.ex7++++---
Mlib/pleroma/web/activity_pub/mrf.ex13+++++++++++--
Mlib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex2--
Alib/pleroma/web/activity_pub/mrf/force_mention.ex59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/pleroma/web/activity_pub/mrf/hashtag_policy.ex4++--
Mlib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex1-
Mlib/pleroma/web/activity_pub/mrf/keyword_policy.ex5+----
Mlib/pleroma/web/activity_pub/mrf/no_empty_policy.ex16++++++++--------
Mlib/pleroma/web/activity_pub/mrf/policy.ex4++--
Mlib/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy.ex2+-
Mlib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex6+++++-
Mlib/pleroma/web/activity_pub/object_validator.ex3+++
Mlib/pleroma/web/activity_pub/object_validators/announce_validator.ex2+-
Mlib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex5+++++
Mlib/pleroma/web/activity_pub/object_validators/common_fixes.ex9++++++---
Mlib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex8++++----
Mlib/pleroma/web/activity_pub/object_validators/question_validator.ex1+
Mlib/pleroma/web/activity_pub/pipeline.ex2+-
Mlib/pleroma/web/activity_pub/publisher.ex107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Mlib/pleroma/web/activity_pub/relay.ex2+-
Mlib/pleroma/web/activity_pub/side_effects.ex21+++++++++++++++++----
Mlib/pleroma/web/activity_pub/side_effects/handling.ex2+-
Mlib/pleroma/web/activity_pub/transmogrifier.ex12++++--------
Mlib/pleroma/web/activity_pub/utils.ex34++++++++++++++++++++++++++++------
Mlib/pleroma/web/activity_pub/visibility.ex50+++++++++++++++++++++++++-------------------------
Mlib/pleroma/web/admin_api/controllers/config_controller.ex6+++---
Mlib/pleroma/web/admin_api/controllers/instance_document_controller.ex13+++++++++----
Mlib/pleroma/web/admin_api/controllers/invite_controller.ex14++++++++++----
Mlib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex15+++++++++++----
Mlib/pleroma/web/admin_api/controllers/relay_controller.ex18+++++++++++++++---
Mlib/pleroma/web/admin_api/controllers/report_controller.ex42+++++++++++++++++++++++++++++++-----------
Mlib/pleroma/web/admin_api/controllers/user_controller.ex107++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mlib/pleroma/web/api_spec.ex8++++----
Mlib/pleroma/web/api_spec/cast_and_validate.ex14++++++++------
Mlib/pleroma/web/api_spec/helpers.ex2+-
Mlib/pleroma/web/api_spec/operations/account_operation.ex19++++++++++++-------
Mlib/pleroma/web/api_spec/operations/admin/frontend_operation.ex4++--
Mlib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex8++++----
Mlib/pleroma/web/api_spec/operations/admin/report_operation.ex12++++++------
Mlib/pleroma/web/api_spec/operations/chat_operation.ex14++++++++++++--
Mlib/pleroma/web/api_spec/operations/directory_operation.ex2+-
Mlib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex6+++---
Mlib/pleroma/web/api_spec/operations/instance_operation.ex4++--
Mlib/pleroma/web/api_spec/operations/notification_operation.ex2+-
Mlib/pleroma/web/api_spec/operations/pleroma_account_operation.ex2+-
Mlib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex2+-
Mlib/pleroma/web/api_spec/operations/pleroma_status_operation.ex2+-
Mlib/pleroma/web/api_spec/operations/poll_operation.ex2+-
Mlib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex2+-
Mlib/pleroma/web/api_spec/operations/search_operation.ex4++--
Mlib/pleroma/web/api_spec/operations/status_operation.ex10+++++-----
Mlib/pleroma/web/api_spec/operations/timeline_operation.ex7++++++-
Mlib/pleroma/web/api_spec/operations/twitter_util_operation.ex10+++++-----
Mlib/pleroma/web/api_spec/schemas/attachment.ex2+-
Mlib/pleroma/web/api_spec/schemas/poll.ex14+++++++++++++-
Mlib/pleroma/web/auth/authenticator.ex2+-
Mlib/pleroma/web/common_api.ex6+++---
Mlib/pleroma/web/common_api/activity_draft.ex2++
Mlib/pleroma/web/common_api/utils.ex2+-
Mlib/pleroma/web/controller_helper.ex18++++++++++--------
Mlib/pleroma/web/embed_controller.ex4+---
Mlib/pleroma/web/endpoint.ex44+++++++++++++++++++++++++++++++++-----------
Mlib/pleroma/web/federator.ex8+++-----
Dlib/pleroma/web/federator/publisher.ex110-------------------------------------------------------------------------------
Mlib/pleroma/web/feed/feed_view.ex4++--
Mlib/pleroma/web/gettext.ex4++--
Mlib/pleroma/web/mastodon_api/controllers/account_controller.ex71++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mlib/pleroma/web/mastodon_api/controllers/directory_controller.ex2+-
Mlib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex18+++++++++++++-----
Mlib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex4++--
Mlib/pleroma/web/mastodon_api/controllers/list_controller.ex52+++++++++++++++++++++++++++++++++++++++-------------
Mlib/pleroma/web/mastodon_api/controllers/media_controller.ex26+++++++++++++++++++++-----
Mlib/pleroma/web/mastodon_api/controllers/notification_controller.ex63+++++++++++++++++++++++++++++++++++++++------------------------
Mlib/pleroma/web/mastodon_api/controllers/poll_controller.ex12+++++++++---
Mlib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex17+++++++++++++----
Mlib/pleroma/web/mastodon_api/controllers/search_controller.ex17+++++++++++++----
Mlib/pleroma/web/mastodon_api/controllers/status_controller.ex169++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mlib/pleroma/web/mastodon_api/views/account_view.ex16+++++++++++++---
Mlib/pleroma/web/mastodon_api/views/instance_view.ex41++++++++++++++++++++++++++++++++++++-----
Mlib/pleroma/web/mastodon_api/views/poll_view.ex5++++-
Mlib/pleroma/web/mastodon_api/views/status_view.ex2--
Mlib/pleroma/web/mastodon_api/websocket_handler.ex256++++++++++++++++++++++++++++++++++++++-----------------------------------------
Mlib/pleroma/web/media_proxy/media_proxy_controller.ex2+-
Mlib/pleroma/web/nodeinfo/nodeinfo.ex2+-
Mlib/pleroma/web/o_auth/authorization.ex8++++----
Mlib/pleroma/web/o_auth/o_auth_controller.ex9++-------
Mlib/pleroma/web/o_auth/token.ex9+++++----
Mlib/pleroma/web/o_auth/token/query.ex4++--
Mlib/pleroma/web/o_status/o_status_controller.ex9++++-----
Mlib/pleroma/web/pleroma_api/controllers/chat_controller.ex69++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mlib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex24++++++++++++++++++++----
Mlib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex29++++++++++++++++++++---------
Mlib/pleroma/web/pleroma_api/controllers/mascot_controller.ex13++++++++++---
Mlib/pleroma/web/pleroma_api/controllers/notification_controller.ex16+++++++++++++---
Mlib/pleroma/web/pleroma_api/controllers/user_import_controller.ex41+++++++++++++++++++++++++++++++----------
Mlib/pleroma/web/plugs/cache.ex2+-
Mlib/pleroma/web/plugs/o_auth_plug.ex4++--
Mlib/pleroma/web/plugs/rate_limiter/supervisor.ex2+-
Mlib/pleroma/web/plugs/remote_ip.ex2+-
Mlib/pleroma/web/plugs/uploaded_media.ex2+-
Mlib/pleroma/web/rich_media/helpers.ex62++++++++++++++++++++------------------------------------------
Mlib/pleroma/web/rich_media/parser.ex155+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mlib/pleroma/web/rich_media/parser/ttl.ex2+-
Mlib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex8++++----
Mlib/pleroma/web/router.ex4++--
Mlib/pleroma/web/static_fe/static_fe_controller.ex3+--
Mlib/pleroma/web/streamer.ex38++++++++++++++++----------------------
Mlib/pleroma/web/templates/o_auth/o_auth/show.html.eex2+-
Mlib/pleroma/web/twitter_api/controllers/password_controller.ex2+-
Mlib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex4++--
Mlib/pleroma/web/twitter_api/controllers/util_controller.ex54+++++++++++++++++++++++++++++++++++++++++-------------
Mlib/pleroma/web/web_finger.ex2+-
Mlib/pleroma/web/web_finger/web_finger_controller.ex2+-
Mlib/pleroma/workers/background_worker.ex7++++++-
Mlib/pleroma/workers/publisher_worker.ex4++--
Mlib/pleroma/workers/remote_fetcher_worker.ex20+++++++++++++++++++-
Mmix.exs16+++++++++++-----
Mmix.lock95++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mpriv/gettext/ja/LC_MESSAGES/errors.po90++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mpriv/static/schemas/litepub-0.1.jsonld4+++-
Atest/fixtures/ccworld-ap-bridge_note.json1+
Atest/fixtures/minds-invalid-mention-post.json2++
Atest/fixtures/minds-pleroma-mentioned-post.json2++
Atest/fixtures/rich_media/google.html12++++++++++++
Mtest/fixtures/rich_media/oembed.html2+-
Atest/fixtures/rich_media/yahoo.html12++++++++++++
Atest/fixtures/tesla_mock/smithereen_non_anonymous_poll.json2++
Atest/fixtures/tesla_mock/smithereen_user.json2++
Mtest/mix/pleroma_test.exs2+-
Mtest/mix/tasks/pleroma/config_test.exs5++---
Mtest/mix/tasks/pleroma/ecto/rollback_test.exs2+-
Mtest/mix/tasks/pleroma/robots_txt_test.exs2+-
Mtest/pleroma/activity_test.exs1+
Mtest/pleroma/config/deprecation_warnings_test.exs11+++++------
Mtest/pleroma/config_db_test.exs4++--
Mtest/pleroma/conversation/participation_test.exs2+-
Mtest/pleroma/emoji/loader_test.exs2+-
Mtest/pleroma/emoji_test.exs26+++++++++++++-------------
Mtest/pleroma/formatter_test.exs4++--
Mtest/pleroma/healthcheck_test.exs8+++++---
Mtest/pleroma/http/adapter_helper/gun_test.exs4++--
Mtest/pleroma/integration/mastodon_websocket_test.exs11-----------
Atest/pleroma/maps_test.exs22++++++++++++++++++++++
Mtest/pleroma/mfa/totp_test.exs6+++++-
Mtest/pleroma/object/fetcher_test.exs7+++----
Mtest/pleroma/otp_version_test.exs2+-
Mtest/pleroma/repo/migrations/autolinker_to_linkify_test.exs6+++---
Mtest/pleroma/repo/migrations/fix_malformed_formatter_config_test.exs4++--
Mtest/pleroma/reverse_proxy_test.exs2+-
Mtest/pleroma/signature_test.exs2+-
Mtest/pleroma/user/backup_test.exs13+++++++++++++
Mtest/pleroma/user_test.exs91++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mtest/pleroma/web/activity_pub/activity_pub_controller_test.exs10+++++-----
Mtest/pleroma/web/activity_pub/activity_pub_test.exs2+-
Mtest/pleroma/web/activity_pub/mrf/ensure_re_prepended_test.exs2+-
Atest/pleroma/web/activity_pub/mrf/force_mention_test.exs73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/pleroma/web/activity_pub/mrf_test.exs13++++++++++++-
Mtest/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs11+++++++++++
Mtest/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs2+-
Mtest/pleroma/web/activity_pub/object_validators/chat_validation_test.exs15+++++++++++++++
Mtest/pleroma/web/activity_pub/publisher_test.exs41+++++++++++++++++++++++++----------------
Mtest/pleroma/web/activity_pub/side_effects_test.exs89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs15++++++++++++++-
Mtest/pleroma/web/activity_pub/transmogrifier/undo_handling_test.exs2+-
Mtest/pleroma/web/activity_pub/transmogrifier_test.exs8++++----
Mtest/pleroma/web/activity_pub/utils_test.exs6+++---
Mtest/pleroma/web/activity_pub/visibility_test.exs50+++++++++++++++++++++++++-------------------------
Mtest/pleroma/web/admin_api/controllers/config_controller_test.exs4++--
Mtest/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs2+-
Mtest/pleroma/web/common_api_test.exs81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mtest/pleroma/web/federator_test.exs38++++++++++++++++++++++++++++++++++++++
Mtest/pleroma/web/mastodon_api/controllers/account_controller_test.exs6+++---
Mtest/pleroma/web/mastodon_api/controllers/instance_controller_test.exs12++++++++++++
Mtest/pleroma/web/mastodon_api/controllers/search_controller_test.exs1+
Mtest/pleroma/web/mastodon_api/controllers/status_controller_test.exs14++++++--------
Mtest/pleroma/web/mastodon_api/controllers/subscription_controller_test.exs15+++++++++------
Mtest/pleroma/web/mastodon_api/update_credentials_test.exs36+++++++++++++++++++++++++++++-------
Mtest/pleroma/web/mastodon_api/views/poll_view_test.exs10+++++++++-
Mtest/pleroma/web/media_proxy/media_proxy_controller_test.exs18+++++++++---------
Mtest/pleroma/web/o_auth/mfa_controller_test.exs2+-
Mtest/pleroma/web/o_auth/token/utils_test.exs4++--
Mtest/pleroma/web/pleroma_api/controllers/chat_controller_test.exs50+++++++++++++++++++++++++++++++++++++++-----------
Mtest/pleroma/web/pleroma_api/controllers/emoji_pack_controller_test.exs16+++++++++-------
Mtest/pleroma/web/pleroma_api/controllers/instances_controller_test.exs2+-
Mtest/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs1+
Mtest/pleroma/web/rich_media/helpers_test.exs44+++++++++++++++++++++++++++++++++++---------
Mtest/pleroma/web/rich_media/parser/ttl/aws_signed_url_test.exs2+-
Mtest/pleroma/web/rich_media/parser_test.exs105++++++++++++++-----------------------------------------------------------------
Mtest/pleroma/web/twitter_api/remote_follow_controller_test.exs2+-
Mtest/pleroma/web/web_finger/web_finger_controller_test.exs25++++++++++---------------
Atest/pleroma/workers/remote_fetcher_worker_test.exs69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/support/cachex_proxy.ex6++++++
Mtest/support/helpers.ex33+++++++++++++++++++++++++++++++++
Mtest/support/http_request_mock.ex92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mtest/support/null_cache.ex6++++++
Mtest/test_helper.exs6++++++
356 files changed, 3340 insertions(+), 1837 deletions(-)

diff --git a/.dialyzer_ignore.exs b/.dialyzer_ignore.exs @@ -0,0 +1,6 @@ +[ +{"lib/cachex.ex", "Unknown type: Spec.cache/0."}, +{"lib/pleroma/web/plugs/rate_limiter.ex", "The pattern can never match the type {:commit, _} | {:ignore, _}."}, +{"lib/pleroma/web/plugs/rate_limiter.ex", "Function get_scale/2 will never be called."}, +{"lib/pleroma/web/plugs/rate_limiter.ex", "Function initialize_buckets!/1 will never be called."} +] diff --git a/.gitignore b/.gitignore @@ -57,5 +57,6 @@ pleroma.iml .tool-versions # Editor temp files -/*~ -/*# +*~ +*# +*.swp diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml @@ -28,6 +28,7 @@ cache: &global_cache_policy stages: - check-changelog - build + - lint - test - benchmark - deploy @@ -71,7 +72,7 @@ check-changelog: tags: - amd64 -build: +build-1.12.3: extends: - .build_changes_policy - .using-ci-base @@ -79,10 +80,20 @@ build: script: - mix compile --force +build-1.15.7-otp-25: + extends: + - .build_changes_policy + - .using-ci-base + stage: build + image: git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.15 + allow_failure: true + script: + - mix compile --force + spec-build: extends: - .using-ci-base - stage: test + stage: build rules: - changes: - ".gitlab-ci.yml" @@ -110,7 +121,7 @@ benchmark: - mix ecto.migrate - mix pleroma.load_testing -unit-testing: +unit-testing-1.12.3: extends: - .build_changes_policy - .using-ci-base @@ -118,12 +129,11 @@ unit-testing: cache: &testing_cache_policy <<: *global_cache_policy policy: pull - - services: + services: &testing_services - name: postgres:13-alpine alias: postgres command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] - script: + script: &testing_script - mix ecto.create - mix ecto.migrate - mix test --cover --preload-modules @@ -134,49 +144,35 @@ unit-testing: coverage_format: cobertura path: coverage.xml -unit-testing-erratic: +unit-testing-1.15.7-otp-25: extends: - .build_changes_policy - .using-ci-base stage: test - retry: 2 + image: git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.15-otp25 allow_failure: true - cache: &testing_cache_policy - <<: *global_cache_policy - policy: pull - - services: - - name: postgres:13-alpine - alias: postgres - command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] - script: - - mix ecto.create - - mix ecto.migrate - - mix test --only=erratic + cache: *testing_cache_policy + services: *testing_services + script: *testing_script -unit-testing-rum: +unit-testing-1.12-erratic: extends: - .build_changes_policy - .using-ci-base stage: test + retry: 2 + allow_failure: true cache: *testing_cache_policy - services: - - name: git.pleroma.social:5050/pleroma/pleroma/postgres-with-rum-13 - alias: postgres - command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] - variables: - <<: *global_variables - RUM_ENABLED: "true" + services: *testing_services script: - mix ecto.create - mix ecto.migrate - - "mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/" - - mix test --preload-modules + - mix test --only=erratic -lint: +formatting-1.13: extends: .build_changes_policy - image: &current_elixir elixir:1.13-alpine - stage: test + image: &formatting_elixir elixir:1.13-alpine + stage: lint cache: *testing_cache_policy before_script: &current_bfr_script - apk update @@ -187,24 +183,37 @@ lint: script: - mix format --check-formatted +cycles-1.13: + extends: .build_changes_policy + image: *formatting_elixir + stage: lint + cache: {} + before_script: *current_bfr_script + script: + - mix compile + - mix xref graph --format cycles --label compile | awk '{print $0} END{exit ($0 != "No cycles found")}' + analysis: extends: - .build_changes_policy - .using-ci-base - stage: test + stage: lint cache: *testing_cache_policy script: - mix credo --strict --only=warnings,todo,fixme,consistency,readability -cycles: - extends: .build_changes_policy - image: *current_elixir - stage: test - cache: {} - before_script: *current_bfr_script +dialyzer: + extends: + - .build_changes_policy + - .using-ci-base + stage: lint + allow_failure: true + when: manual + cache: *testing_cache_policy + tags: + - feld script: - - mix compile - - mix xref graph --format cycles --label compile | awk '{print $0} END{exit ($0 != "No cycles found")}' + - mix dialyzer docs-deploy: stage: deploy diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## 2.6.2 + +### Security +- MRF StealEmojiPolicy: Sanitize shortcodes (thanks to Hazel K for the report + ## 2.6.1 ### Changed - - Document maximum supported version of Erlang & Elixir @@ -62,7 +67,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## 2.5.4 ## Security -- Fix XML External Entity (XXE) loading vulnerability allowing to fetch arbitary files from the server's filesystem +- Fix XML External Entity (XXE) loading vulnerability allowing to fetch arbitrary files from the server's filesystem ## 2.5.3 @@ -78,7 +83,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## 2.5.4 ## Security -- Fix XML External Entity (XXE) loading vulnerability allowing to fetch arbitary files from the server's filesystem +- Fix XML External Entity (XXE) loading vulnerability allowing to fetch arbitrary files from the server's filesystem ## 2.5.3 @@ -118,7 +123,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Fix `block_from_stranger` setting - Fix rel="me" - Docker images will now run properly -- Fix inproper content being cached in report content +- Fix improper content being cached in report content - Notification filter on object content will not operate on the ones that inherently have no content - ZWNJ and double dots in links are parsed properly for Plain-text posts - OTP releases will work on systems with a newer libcrypt @@ -784,7 +789,7 @@ switched to a new configuration mechanism, however it was not officially removed - Rate limiter crashes when there is no explicitly specified ip in the config - 500 errors when no `Accept` header is present if Static-FE is enabled - Instance panel not being updated immediately due to wrong `Cache-Control` headers -- Statuses posted with BBCode/Markdown having unncessary newlines in Pleroma-FE +- Statuses posted with BBCode/Markdown having unnecessary newlines in Pleroma-FE - OTP: Fix some settings not being migrated to in-database config properly - No `Cache-Control` headers on attachment/media proxy requests - Character limit enforcement being off by 1 @@ -1104,10 +1109,10 @@ curl -Lo ./bin/pleroma_ctl 'https://git.pleroma.social/pleroma/pleroma/raw/devel - Reverse Proxy limiting `max_body_length` was incorrectly defined and only checked `Content-Length` headers which may not be sufficient in some circumstances ### Added -- Expiring/ephemeral activites. All activities can have expires_at value set, which controls when they should be deleted automatically. +- Expiring/ephemeral activities. All activities can have expires_at value set, which controls when they should be deleted automatically. - Mastodon API: in post_status, the expires_in parameter lets you set the number of seconds until an activity expires. It must be at least one hour. - Mastodon API: all status JSON responses contain a `pleroma.expires_at` item which states when an activity will expire. The value is only shown to the user who created the activity. To everyone else it's empty. -- Configuration: `ActivityExpiration.enabled` controls whether expired activites will get deleted at the appropriate time. Enabled by default. +- Configuration: `ActivityExpiration.enabled` controls whether expired activities will get deleted at the appropriate time. Enabled by default. - Conversations: Add Pleroma-specific conversation endpoints and status posting extensions. Run the `bump_all_conversations` task again to create the necessary data. - MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`) - MRF: Support for excluding specific domains from Transparency. diff --git a/changelog.d/account-rendering-auth-check.fix b/changelog.d/account-rendering-auth-check.fix @@ -0,0 +1 @@ +Fix authentication check on account rendering when bio is defined diff --git a/changelog.d/api-docs.skip b/changelog.d/api-docs.skip diff --git a/changelog.d/atom-leak.skip b/changelog.d/atom-leak.skip diff --git a/changelog.d/backups-follows.add b/changelog.d/backups-follows.add @@ -0,0 +1 @@ +Include following/followers in backups +\ No newline at end of file diff --git a/changelog.d/bandit.change b/changelog.d/bandit.change @@ -0,0 +1 @@ +Support Bandit as an alternative to Cowboy for the HTTP server. diff --git a/changelog.d/bugfix-ccworks.fix b/changelog.d/bugfix-ccworks.fix @@ -0,0 +1 @@ +Fix federation with Convergence AP Bridge +\ No newline at end of file diff --git a/changelog.d/chat-attachment-empty-array.fix b/changelog.d/chat-attachment-empty-array.fix @@ -0,0 +1 @@ +ChatMessage: Tolerate attachment field set to an empty array diff --git a/changelog.d/config-stat-symlink.fix b/changelog.d/config-stat-symlink.fix @@ -0,0 +1 @@ +- Config: Check the permissions of the linked file instead of the symlink diff --git a/changelog.d/content-length.fix b/changelog.d/content-length.fix @@ -0,0 +1 @@ +MediaProxy was setting the content-length header which is not permitted by RFC9112§6.2 when we are chunking the reply as it conflicts with the existence of the transfer-encoding header. diff --git a/changelog.d/deps-bump-2024-01-25.skip b/changelog.d/deps-bump-2024-01-25.skip diff --git a/changelog.d/dialyzer.skip b/changelog.d/dialyzer.skip diff --git a/changelog.d/dialyzer2.skip b/changelog.d/dialyzer2.skip diff --git a/changelog.d/dialyzer3.skip b/changelog.d/dialyzer3.skip diff --git a/changelog.d/dialyzer4.skip b/changelog.d/dialyzer4.skip diff --git a/changelog.d/emoji-download-paginate.fix b/changelog.d/emoji-download-paginate.fix @@ -0,0 +1 @@ +When downloading remote emojis packs, account for pagination +\ No newline at end of file diff --git a/changelog.d/emoji-use-v1.fix b/changelog.d/emoji-use-v1.fix @@ -0,0 +1 @@ +Make remote emoji packs API use specifically the V1 URL. Akkoma does not understand it without V1, and it works either way with normal pleroma, so no reason to not do this +\ No newline at end of file diff --git a/changelog.d/exile-bsds.skip b/changelog.d/exile-bsds.skip diff --git a/changelog.d/exile-freebsd.skip b/changelog.d/exile-freebsd.skip diff --git a/changelog.d/exile-macos.skip b/changelog.d/exile-macos.skip diff --git a/changelog.d/exile.skip b/changelog.d/exile.skip diff --git a/changelog.d/federator-modules.remove b/changelog.d/federator-modules.remove @@ -0,0 +1 @@ +Removed support for multiple federator modules as we only support ActivityPub diff --git a/changelog.d/federator.skip b/changelog.d/federator.skip diff --git a/changelog.d/finch_redirects.fix b/changelog.d/finch_redirects.fix @@ -0,0 +1 @@ +Following HTTP Redirects when the HTTP Adapter is Finch diff --git a/changelog.d/fix-duplicate-inbox-deliveries.fix b/changelog.d/fix-duplicate-inbox-deliveries.fix diff --git a/changelog.d/force-mention-mrf.add b/changelog.d/force-mention-mrf.add @@ -0,0 +1 @@ +Add ForceMention MRF +\ No newline at end of file diff --git a/changelog.d/framegrabs.fix b/changelog.d/framegrabs.fix @@ -0,0 +1 @@ +Video framegrabs were not working correctly after the change to use Exile to execute ffmpeg diff --git a/changelog.d/group-actor.add b/changelog.d/group-actor.add @@ -0,0 +1 @@ +Implement group actors diff --git a/changelog.d/gun-logs.skip b/changelog.d/gun-logs.skip diff --git a/changelog.d/gun_pool.fix b/changelog.d/gun_pool.fix @@ -0,0 +1 @@ +Fix logic error in Gun connection pooling which prevented retries even when the worker was launched with retry = true diff --git a/changelog.d/gun_pool2.fix b/changelog.d/gun_pool2.fix @@ -0,0 +1 @@ +Connection pool errors when publishing an activity is a soft-error that will be retried shortly. diff --git a/changelog.d/gun_pool3.skip b/changelog.d/gun_pool3.skip diff --git a/changelog.d/handle_object_fetch_failures.change b/changelog.d/handle_object_fetch_failures.change @@ -0,0 +1 @@ +Remote object fetch failures will prevent the object fetch job from retrying if the object request returns 401, 403, 404, 410, or exceeds the maximum thread depth. diff --git a/changelog.d/instance-contact-account.add b/changelog.d/instance-contact-account.add @@ -0,0 +1 @@ +Add contact account to InstanceView +\ No newline at end of file diff --git a/changelog.d/instance-defdelegates.skip b/changelog.d/instance-defdelegates.skip diff --git a/changelog.d/instance-v2.skip b/changelog.d/instance-v2.skip diff --git a/changelog.d/issue-3241.fix b/changelog.d/issue-3241.fix @@ -0,0 +1 @@ +Handle cases when users.inbox is nil. diff --git a/changelog.d/link-verification.add b/changelog.d/link-verification.add @@ -0,0 +1 @@ +Verify profile link ownership with rel="me" +\ No newline at end of file diff --git a/changelog.d/local-webfinger.fix b/changelog.d/local-webfinger.fix @@ -0,0 +1 @@ +Use correct domain for fqn and InstanceView +\ No newline at end of file diff --git a/changelog.d/mastodon_directory.fix b/changelog.d/mastodon_directory.fix @@ -0,0 +1 @@ +Mastodon API /api/v1/directory: Fix listing directory contents when not authenticated diff --git a/changelog.d/memleak.fix b/changelog.d/memleak.fix @@ -0,0 +1 @@ +Fix a memory leak caused by Websocket connections that would not enter a state where a full garbage collection run could be triggered. diff --git a/changelog.d/mergeback-2.6.2.skip b/changelog.d/mergeback-2.6.2.skip diff --git a/changelog.d/mrf-regex-error.fix b/changelog.d/mrf-regex-error.fix @@ -0,0 +1 @@ +MRF: Log sensible error for subdomains_regex diff --git a/changelog.d/mrf-steal-emoji-extname.fix b/changelog.d/mrf-steal-emoji-extname.fix @@ -0,0 +1 @@ +MRF.StealEmojiPolicy: Properly add fallback extension to filenames missing one diff --git a/changelog.d/mrf_hashtags.fix b/changelog.d/mrf_hashtags.fix @@ -0,0 +1 @@ +Federated timeline removal of hashtags via MRF HashtagPolicy diff --git a/changelog.d/nil-content-map.fix b/changelog.d/nil-content-map.fix @@ -0,0 +1 @@ +Support objects with a null contentMap (firefish) diff --git a/changelog.d/notifications-index.fix b/changelog.d/notifications-index.fix @@ -0,0 +1 @@ +Fix notifications query which was not using the index properly diff --git a/changelog.d/notifications.fix b/changelog.d/notifications.fix @@ -0,0 +1 @@ +Notifications: improve performance by filtering on users table instead of activities table +\ No newline at end of file diff --git a/changelog.d/oauth-nickname.skip b/changelog.d/oauth-nickname.skip @@ -0,0 +1 @@ +Use User.full_nickname/1 in oauth html template +\ No newline at end of file diff --git a/changelog.d/otp26.add b/changelog.d/otp26.add @@ -0,0 +1 @@ +Support for Erlang OTP 26 diff --git a/changelog.d/public-polls.add b/changelog.d/public-polls.add @@ -0,0 +1 @@ +Expose nonAnonymous field from Smithereen polls +\ No newline at end of file diff --git a/changelog.d/publisher_discard.change b/changelog.d/publisher_discard.change @@ -0,0 +1 @@ +Activity publishing failures will prevent the job from retrying if the publishing request returns a 403 or 410 diff --git a/changelog.d/publisher_log.change b/changelog.d/publisher_log.change @@ -0,0 +1 @@ +Publisher errors will now emit logs indicating the inbox that was not available for delivery. diff --git a/changelog.d/qtfaststart.fix b/changelog.d/qtfaststart.fix @@ -0,0 +1 @@ +MediaProxy Preview failures prevented when encountering certain video files diff --git a/changelog.d/remote-fetcher-error.skip b/changelog.d/remote-fetcher-error.skip diff --git a/changelog.d/rich_media.fix b/changelog.d/rich_media.fix @@ -0,0 +1 @@ +Rich Media Preview cache eviction when the activity is updated. diff --git a/changelog.d/rich_media_tests.skip b/changelog.d/rich_media_tests.skip diff --git a/changelog.d/tesla.deps b/changelog.d/tesla.deps @@ -0,0 +1 @@ +Update Tesla HTTP client middleware to 1.8.0 diff --git a/changelog.d/typo.skip b/changelog.d/typo.skip diff --git a/changelog.d/websocket-refactor.change b/changelog.d/websocket-refactor.change @@ -0,0 +1 @@ +Refactor the Mastodon /api/v1/streaming websocket handler to use Phoenix.Socket.Transport diff --git a/ci/build_and_push.sh b/ci/build_and_push.sh @@ -1 +0,0 @@ -docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t git.pleroma.social:5050/pleroma/pleroma/ci-base:latest --push . diff --git a/ci/Dockerfile b/ci/elixir-1.12/Dockerfile diff --git a/ci/elixir-1.12/build_and_push.sh b/ci/elixir-1.12/build_and_push.sh @@ -0,0 +1 @@ +docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.12 --push . diff --git a/ci/elixir-1.15-otp25/Dockerfile b/ci/elixir-1.15-otp25/Dockerfile @@ -0,0 +1,8 @@ +FROM elixir:1.15.7-otp-25 + +# Single RUN statement, otherwise intermediate images are created +# https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run +RUN apt-get update &&\ + apt-get install -y libmagic-dev cmake libimage-exiftool-perl ffmpeg &&\ + mix local.hex --force &&\ + mix local.rebar --force diff --git a/ci/elixir-1.15-otp25/build_and_push.sh b/ci/elixir-1.15-otp25/build_and_push.sh @@ -0,0 +1 @@ +docker buildx build --platform linux/amd64 -t git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.15-otp25 --push . diff --git a/ci/postgres_rum/Dockerfile b/ci/postgres-with-rum-13/Dockerfile diff --git a/ci/postgres_rum/build_and_push.sh b/ci/postgres-with-rum-13/build_and_push.sh diff --git a/config/benchmark.exs b/config/benchmark.exs @@ -79,6 +79,10 @@ IO.puts("RUM enabled: #{rum_enabled}") config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock +config :pleroma, Pleroma.Application, + background_migrators: false, + streamer_registry: false + if File.exists?("./config/benchmark.secret.exs") do import_config "benchmark.secret.exs" else diff --git a/config/config.exs b/config/config.exs @@ -114,14 +114,7 @@ config :pleroma, :uri_schemes, config :pleroma, Pleroma.Web.Endpoint, url: [host: "localhost"], http: [ - ip: {127, 0, 0, 1}, - dispatch: [ - {:_, - [ - {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, - {:_, Plug.Cowboy.Handler, {Pleroma.Web.Endpoint, []}} - ]} - ] + ip: {127, 0, 0, 1} ], protocol: "https", secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl", @@ -192,9 +185,6 @@ config :pleroma, :instance, federating: true, federation_incoming_replies_max_depth: 100, federation_reachability_timeout_days: 7, - federation_publisher_modules: [ - Pleroma.Web.ActivityPub.Publisher - ], allow_relay: true, public: true, quarantined_instances: [], @@ -425,6 +415,10 @@ config :pleroma, :mrf_follow_bot, follower_nickname: nil config :pleroma, :mrf_inline_quote, template: "<bdi>RT:</bdi> {url}" +config :pleroma, :mrf_force_mention, + mention_parent: true, + mention_quoted: true + config :pleroma, :rich_media, enabled: true, ignore_hosts: [], @@ -907,6 +901,15 @@ config :pleroma, Pleroma.Search.Meilisearch, private_key: nil, initial_indexing_chunk_size: 100_000 +config :pleroma, Pleroma.Application, + background_migrators: true, + internal_fetch: true, + load_custom_modules: true, + max_restarts: 3, + streamer_registry: true + +config :pleroma, Pleroma.Uploaders.Uploader, timeout: 30_000 + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/config/description.exs b/config/description.exs @@ -567,6 +567,20 @@ config :pleroma, :config_description, [ ] }, %{ + key: :status_page, + type: :string, + description: "A page where people can see the status of the server during an outage", + suggestions: [ + "https://status.pleroma.example.org" + ] + }, + %{ + key: :contact_username, + type: :string, + description: "Instance owner username", + suggestions: ["admin"] + }, + %{ key: :limit, type: :integer, description: "Posts character limit (CW/Subject included in the counter)", @@ -1444,7 +1458,7 @@ config :pleroma, :config_description, [ label: "Subject line behavior", type: :string, description: "Allows changing the default behaviour of subject lines in replies. - `email`: copy and preprend re:, as in email, + `email`: copy and prepend re:, as in email, `masto`: copy verbatim, as in Mastodon, `noop`: don't copy the subject.", suggestions: ["email", "masto", "noop"] @@ -3096,7 +3110,7 @@ config :pleroma, :config_description, [ 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", + "Maximum number of requests waiting for other requests to finish. After this number is reached, the pool will start returning errors when a new request is made", suggestions: [10] }, %{ @@ -3362,7 +3376,7 @@ config :pleroma, :config_description, [ %{ key: :purge_after_days, type: :integer, - description: "Remove backup achives after N days", + description: "Remove backup archives after N days", suggestions: [30] }, %{ diff --git a/config/dev.exs b/config/dev.exs @@ -8,8 +8,7 @@ import Config # with brunch.io to recompile .js and .css sources. config :pleroma, Pleroma.Web.Endpoint, http: [ - port: 4000, - protocol_options: [max_request_line_length: 8192, max_header_value_length: 8192] + port: 4000 ], protocol: "http", debug_errors: true, diff --git a/config/test.exs b/config/test.exs @@ -162,6 +162,18 @@ peer_module = config :pleroma, Pleroma.Cluster, peer_module: peer_module +config :pleroma, Pleroma.Application, + background_migrators: false, + internal_fetch: false, + load_custom_modules: false, + max_restarts: 100, + streamer_registry: false, + test_http_pools: true + +config :pleroma, Pleroma.Uploaders.Uploader, timeout: 1_000 + +config :pleroma, Pleroma.Emoji.Loader, test_emoji: true + if File.exists?("./config/test.secret.exs") do import_config "test.secret.exs" else diff --git a/docs/administration/CLI_tasks/config.md b/docs/administration/CLI_tasks/config.md @@ -1,4 +1,4 @@ -# Transfering the config to/from the database +# Transferring the config to/from the database {! backend/administration/CLI_tasks/general_cli_task_info.include !} @@ -34,7 +34,7 @@ Options: -- `<path>` - where to save migrated config. E.g. `--path=/tmp`. If file saved into non standart folder, you must manually copy file into directory where Pleroma can read it. For OTP install path will be `PLEROMA_CONFIG_PATH` or `/etc/pleroma`. For installation from source - `config` directory in the pleroma folder. +- `<path>` - where to save migrated config. E.g. `--path=/tmp`. If file saved into non-standard folder, you must manually copy file into directory where Pleroma can read it. For OTP install path will be `PLEROMA_CONFIG_PATH` or `/etc/pleroma`. For installation from source - `config` directory in the pleroma folder. - `<env>` - environment, for which is migrated config. By default is `prod`. - To delete transferred settings from database optional flag `-d` can be used diff --git a/docs/administration/backup.md b/docs/administration/backup.md @@ -31,7 +31,7 @@ 1. Optionally you can remove the users of your instance. This will trigger delete requests for their accounts and posts. Note that this is 'best effort' and doesn't mean that all traces of your instance will be gone from the fediverse. * You can do this from the admin-FE where you can select all local users and delete the accounts using the *Moderate multiple users* dropdown. - * You can also list local users and delete them individualy using the CLI tasks for [Managing users](./CLI_tasks/user.md). + * You can also list local users and delete them individually using the CLI tasks for [Managing users](./CLI_tasks/user.md). 2. Stop the Pleroma service `systemctl stop pleroma` 3. Disable pleroma from systemd `systemctl disable pleroma` 4. Remove the files and folders you created during installation (see installation guide). This includes the pleroma, nginx and systemd files and folders. diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md @@ -154,14 +154,15 @@ To add configuration to your config file, you can copy it from the base config. * `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (See [`:mrf_mention`](#mrf_mention)). * `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (See [`:mrf_vocabulary`](#mrf_vocabulary)). * `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)). - * `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.Workers.PurgeExpiredActivity` to be enabled for processing the scheduled delections. + * `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.Workers.PurgeExpiredActivity` to be enabled for processing the scheduled deletions. * `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines. * `Pleroma.Web.ActivityPub.MRF.FollowBotPolicy`: Automatically follows newly discovered users from the specified bot account. Local accounts, locked accounts, and users with "#nobot" in their bio are respected and excluded from being followed. * `Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy`: Drops follow requests from followbots. Users can still allow bots to follow them by first following the bot. * `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)). * `Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent`: Forces every mentioned user to be reflected in the post content. * `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Forces quote post URLs to be reflected in the message content inline. - * `Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy`: Force a Link tag for posts quoting another post. (may break outgoing federation of quote posts with older Pleroma versions) + * `Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy`: Force a Link tag for posts quoting another post. (may break outgoing federation of quote posts with older Pleroma versions). + * `Pleroma.Web.ActivityPub.MRF.ForceMention`: Forces posts to include a mention of the author of parent post or the author of quoted post. * `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo). * `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. @@ -272,6 +273,10 @@ Notes: #### :mrf_inline_quote * `template`: The template to append to the post. `{url}` will be replaced with the actual link to the quoted post. Default: `<bdi>RT:</bdi> {url}` +#### :mrf_force_mention +* `mention_parent`: Whether to append mention of parent post author +* `mention_quoted`: Whether to append mention of parent quoted author + ### :activitypub * `unfollow_blocked`: Whether blocks result in people getting unfollowed * `outgoing_blocks`: Whether to federate blocks to other instances @@ -506,7 +511,7 @@ config :pleroma, :rate_limit, Means that: 1. In 60 seconds, 15 authentication attempts can be performed from the same IP address. -2. In 1 second, 10 search requests can be performed from the same IP adress by unauthenticated users, while authenticated users can perform 30 search requests per second. +2. In 1 second, 10 search requests can be performed from the same IP address by unauthenticated users, while authenticated users can perform 30 search requests per second. Supported rate limiters: @@ -1081,7 +1086,7 @@ config :pleroma, Pleroma.Formatter, ## :configurable_from_database -Boolean, enables/disables in-database configuration. Read [Transfering the config to/from the database](../administration/CLI_tasks/config.md) for more information. +Boolean, enables/disables in-database configuration. Read [Transferring the config to/from the database](../administration/CLI_tasks/config.md) for more information. ## :database_config_whitelist @@ -1142,7 +1147,7 @@ Control favicons for instances. !!! note Requires enabled email -* `:purge_after_days` an integer, remove backup achives after N days. +* `:purge_after_days` an integer, remove backup achieves after N days. * `:limit_days` an integer, limit user to export not more often than once per N days. * `:dir` a string with a path to backup temporary directory or `nil` to let Pleroma choose temporary directory in the following order: 1. the directory named by the TMPDIR environment variable diff --git a/docs/configuration/custom_emoji.md b/docs/configuration/custom_emoji.md @@ -29,7 +29,7 @@ foo, /emoji/custom/foo.png The files should be PNG (APNG is okay with `.png` for `image/png` Content-type) and under 50kb for compatibility with mastodon. -Default file extentions and locations for emojis are set in `config.exs`. To use different locations or file-extentions, add the `shortcode_globs` to your secrets file (`prod.secret.exs` or `dev.secret.exs`) and edit it. Note that not all fediverse-software will show emojis with other file extentions: +Default file extensions and locations for emojis are set in `config.exs`. To use different locations or file-extensions, add the `shortcode_globs` to your secrets file (`prod.secret.exs` or `dev.secret.exs`) and edit it. Note that not all fediverse-software will show emojis with other file extensions: ```elixir config :pleroma, :emoji, shortcode_globs: ["/emoji/custom/**/*.png", "/emoji/custom/**/*.gif"] ``` diff --git a/docs/configuration/i2p.md b/docs/configuration/i2p.md @@ -1,4 +1,4 @@ -# I2P Federation and Accessability +# I2P Federation and Accessibility This guide is going to focus on the Pleroma federation aspect. The actual installation is neatly explained in the official documentation, and more likely to remain up-to-date. It might be added to this guide if there will be a need for that. diff --git a/docs/configuration/onion_federation.md b/docs/configuration/onion_federation.md @@ -29,7 +29,7 @@ HiddenServiceDir /var/lib/tor/pleroma_hidden_service/ HiddenServicePort 80 127.0.0.1:8099 HiddenServiceVersion 3 # Remove if Tor version is below 0.3 ( tor --version ) ``` -Restart Tor to generate an adress: +Restart Tor to generate an address: ``` systemctl restart tor@default.service ``` diff --git a/docs/configuration/optimizing_beam.md b/docs/configuration/optimizing_beam.md @@ -1,6 +1,6 @@ # Optimizing the BEAM -Pleroma is built upon the Erlang/OTP VM known as BEAM. The BEAM VM is highly optimized for latency, but this has drawbacks in environments without dedicated hardware. One of the tricks used by the BEAM VM is [busy waiting](https://en.wikipedia.org/wiki/Busy_waiting). This allows the application to pretend to be busy working so the OS kernel does not pause the application process and switch to another process waiting for the CPU to execute its workload. It does this by spinning for a period of time which inflates the apparent CPU usage of the application so it is immediately ready to execute another task. This can be observed with utilities like **top(1)** which will show consistently high CPU usage for the process. Switching between procesess is a rather expensive operation and also clears CPU caches further affecting latency and performance. The goal of busy waiting is to avoid this penalty. +Pleroma is built upon the Erlang/OTP VM known as BEAM. The BEAM VM is highly optimized for latency, but this has drawbacks in environments without dedicated hardware. One of the tricks used by the BEAM VM is [busy waiting](https://en.wikipedia.org/wiki/Busy_waiting). This allows the application to pretend to be busy working so the OS kernel does not pause the application process and switch to another process waiting for the CPU to execute its workload. It does this by spinning for a period of time which inflates the apparent CPU usage of the application so it is immediately ready to execute another task. This can be observed with utilities like **top(1)** which will show consistently high CPU usage for the process. Switching between processes is a rather expensive operation and also clears CPU caches further affecting latency and performance. The goal of busy waiting is to avoid this penalty. This strategy is very successful in making a performant and responsive application, but is not desirable on Virtual Machines or hardware with few CPU cores. Pleroma instances are often deployed on the same server as the required PostgreSQL database which can lead to situations where the Pleroma application is holding the CPU in a busy-wait loop and as a result the database cannot process requests in a timely manner. The fewer CPUs available, the more this problem is exacerbated. The latency is further amplified by the OS being installed on a Virtual Machine as the Hypervisor uses CPU time-slicing to pause the entire OS and switch between other tasks. diff --git a/docs/configuration/postgresql.md b/docs/configuration/postgresql.md @@ -22,7 +22,7 @@ config :pleroma, Pleroma.Repo, ] ``` -A more detailed explaination of the issue can be found at <https://blog.soykaf.com/post/postgresql-elixir-troubles/>. +A more detailed explanation of the issue can be found at <https://blog.soykaf.com/post/postgresql-elixir-troubles/>. ## Example configurations diff --git a/docs/configuration/search.md b/docs/configuration/search.md @@ -38,7 +38,7 @@ indexes faster when it can process many posts in a single batch. Information about setting up meilisearch can be found in the [official documentation](https://docs.meilisearch.com/learn/getting_started/installation.html). You probably want to start it with `MEILI_NO_ANALYTICS=true` environment variable to disable analytics. -At least version 0.25.0 is required, but you are strongly adviced to use at least 0.26.0, as it introduces +At least version 0.25.0 is required, but you are strongly advised to use at least 0.26.0, as it introduces the `--enable-auto-batching` option which drastically improves performance. Without this option, the search is hardly usable on a somewhat big instance. @@ -61,7 +61,7 @@ You will see a "Default Admin API Key", this is the key you actually put into yo ### Initial indexing -After setting up the configuration, you'll want to index all of your already existsing posts. Only public posts are indexed. You'll only +After setting up the configuration, you'll want to index all of your already existing posts. Only public posts are indexed. You'll only have to do it one time, but it might take a while, depending on the amount of posts your instance has seen. This is also a fairly RAM consuming process for `meilisearch`, and it will take a lot of RAM when running if you have a lot of posts (seems to be around 5G for ~1.2 million posts while idle and up to 7G while indexing initially, but your experience may be different). diff --git a/docs/development/API/admin_api.md b/docs/development/API/admin_api.md @@ -303,7 +303,7 @@ Removes the user(s) from follower recommendations. ## `GET /api/v1/pleroma/admin/users/:nickname_or_id` -### Retrive the details of a user +### Retrieve the details of a user - Params: - `nickname` or `id` @@ -313,7 +313,7 @@ Removes the user(s) from follower recommendations. ## `GET /api/v1/pleroma/admin/users/:nickname_or_id/statuses` -### Retrive user's latest statuses +### Retrieve user's latest statuses - Params: - `nickname` or `id` @@ -337,7 +337,7 @@ Removes the user(s) from follower recommendations. ## `GET /api/v1/pleroma/admin/instances/:instance/statuses` -### Retrive instance's latest statuses +### Retrieve instance's latest statuses - Params: - `instance`: instance name @@ -377,7 +377,7 @@ It may take some time. ## `GET /api/v1/pleroma/admin/statuses` -### Retrives all latest statuses +### Retrieves all latest statuses - Params: - *optional* `page_size`: number of statuses to return (default is `20`) @@ -541,7 +541,7 @@ Response: ## `PATCH /api/v1/pleroma/admin/users/force_password_reset` -### Force passord reset for a user with a given nickname +### Force password reset for a user with a given nickname - Params: - `nicknames` diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md @@ -1,6 +1,6 @@ # Differences in Mastodon API responses from vanilla Mastodon -A Pleroma instance can be identified by "<Mastodon version> (compatible; Pleroma <version>)" present in `version` field in response from `/api/v1/instance` +A Pleroma instance can be identified by "<Mastodon version> (compatible; Pleroma <version>)" present in `version` field in response from `/api/v1/instance` and `/api/v2/instance` ## Flake IDs @@ -39,6 +39,8 @@ Has these additional fields under the `pleroma` object: - `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint. - `parent_visible`: If the parent of this post is visible to the user or not. - `pinned_at`: a datetime (iso8601) when status was pinned, `null` otherwise. +- `quotes_count`: the count of status quotes. +- `non_anonymous`: true if the source post specifies the poll results are not anonymous. Currently only implemented by Smithereen. The `GET /api/v1/statuses/:id/source` endpoint additionally has the following attributes: @@ -304,19 +306,27 @@ Has these additional parameters (which are the same as in Pleroma-API): `GET /api/v1/instance` has additional fields - `max_toot_chars`: The maximum characters per post +- `max_media_attachments`: Maximum number of post media attachments - `chat_limit`: The maximum characters per chat message - `description_limit`: The maximum characters per image description - `poll_limits`: The limits of polls +- `shout_limit`: The maximum characters per Shoutbox message - `upload_limit`: The maximum upload file size - `avatar_upload_limit`: The same for avatars - `background_upload_limit`: The same for backgrounds - `banner_upload_limit`: The same for banners - `background_image`: A background image that frontends can use +- `pleroma.metadata.account_activation_required`: Whether users are required to confirm their emails before signing in +- `pleroma.metadata.birthday_required`: Whether users are required to provide their birth day when signing in +- `pleroma.metadata.birthday_min_age`: The minimum user age (in days) - `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 +- `pleroma.stats.mau`: Monthly active user count +- `pleroma.vapid_public_key`: The public key needed for push messages + +In, `GET /api/v2/instance` Pleroma-specific fields are all moved into `pleroma` object. `max_toot_chars`, `poll_limits` and `upload_limit` are replaced with their MastoAPI counterparts. ## Push Subscription diff --git a/docs/development/API/pleroma_api.md b/docs/development/API/pleroma_api.md @@ -129,7 +129,7 @@ The `/api/v1/pleroma/*` path is backwards compatible with `/api/pleroma/*` (`/ap * method: `GET` * Authentication: required * OAuth scope: `write:security` -* Response: JSON. Returns `{"codes": codes}`when successful, otherwise HTTP 422 `{"error": "[error message]"}` +* Response: JSON. Returns `{"codes": codes}` when successful, otherwise HTTP 422 `{"error": "[error message]"}` ## `/api/v1/pleroma/admin/` See [Admin-API](admin_api.md) @@ -251,6 +251,15 @@ See [Admin-API](admin_api.md) ] ``` + +## `/api/v1/pleroma/accounts/:id/endorsements` +### Returns users endorsed by a user +* Method `GET` +* Authentication: not required +* Params: + * `id`: the id of the account for whom to return results +* Response: JSON, returns a list of Mastodon Account entities + ## `/api/v1/pleroma/accounts/update_*` ### Set and clear account avatar, banner, and background @@ -266,6 +275,14 @@ See [Admin-API](admin_api.md) * Authentication: not required * Response: 204 No Content +## `/api/v1/pleroma/statuses/:id/quotes` +### Gets quotes for a given status +* Method `GET` +* Authentication: not required +* Params: + * `id`: the id of the status +* Response: JSON, returns a list of Mastodon Status entities + ## `/api/v1/pleroma/mascot` ### Gets user mascot image * Method `GET` @@ -372,6 +389,15 @@ See [Admin-API](admin_api.md) * `alias`: the nickname of the alias to delete, e.g. `foo@example.org`. * Response: JSON. Returns `{"status": "success"}` if the change was successful, `{"error": "[error message]"}` otherwise +## `/api/v1/pleroma/remote_interaction` +## Interact with profile or status from remote account +* Metod `POST` +* Authentication: not required +* Params: + * `ap_id`: Profile or status ActivityPub ID + * `profile`: Remote profile webfinger +* Response: JSON. Returns `{"url": "[redirect url]"}` on success, `{"error": "[error message]"}` otherwise + # Pleroma Conversations Pleroma Conversations have the same general structure that Mastodon Conversations have. The behavior differs in the following ways when using these endpoints: @@ -382,7 +408,7 @@ Pleroma Conversations have the same general structure that Mastodon Conversation Conversations have the additional field `recipients` under the `pleroma` key. This holds a list of all the accounts that will receive a message in this conversation. -The status posting endpoint takes an additional parameter, `in_reply_to_conversation_id`, which, when set, will set the visiblity to direct and address only the people who are the recipients of that Conversation. +The status posting endpoint takes an additional parameter, `in_reply_to_conversation_id`, which, when set, will set the visibility to direct and address only the people who are the recipients of that Conversation. ⚠ Conversation IDs can be found in direct messages with the `pleroma.direct_conversation_id` key, do not confuse it with `pleroma.conversation_id`. diff --git a/docs/development/ap_extensions.md b/docs/development/ap_extensions.md @@ -20,16 +20,16 @@ Content-Type: multipart/form-data Parameters: - (required) `file`: The file being uploaded -- (optionnal) `description`: A plain-text description of the media, for accessibility purposes. +- (optional) `description`: A plain-text description of the media, for accessibility purposes. Response: HTTP 201 Created with the object into the body, no `Location` header provided as it doesn't have an `id` -The object given in the reponse should then be inserted into an Object's `attachment` field. +The object given in the response should then be inserted into an Object's `attachment` field. ## ChatMessages `ChatMessage`s are the messages sent in 1-on-1 chats. They are similar to -`Note`s, but the addresing is done by having a single AP actor in the `to` +`Note`s, but the addressing is done by having a single AP actor in the `to` field. Addressing multiple actors is not allowed. These messages are always private, there is no public version of them. They are created with a `Create` activity. diff --git a/docs/development/setting_up_pleroma_dev.md b/docs/development/setting_up_pleroma_dev.md @@ -15,7 +15,7 @@ Pleroma requires some adjustments from the defaults for running the instance loc 2. Change the dev.secret.exs * Change the scheme in `config :pleroma, Pleroma.Web.Endpoint` to http (see examples below) * If you want to change other settings, you can do that too -3. You can now start the server `mix phx.server`. Once it's build and started, you can access the instance on `http://<host>:<port>` (e.g.http://localhost:4000 ) and should be able to do everything locally you normaly can. +3. You can now start the server `mix phx.server`. Once it's build and started, you can access the instance on `http://<host>:<port>` (e.g.http://localhost:4000 ) and should be able to do everything locally you normally can. Example config to change the scheme to http. Change the port if you want to run on another port. ```elixir diff --git a/docs/installation/generic_dependencies.include b/docs/installation/generic_dependencies.include @@ -2,7 +2,7 @@ * PostgreSQL >=9.6 * Elixir >=1.11.0 <1.15 -* Erlang OTP >=22.2.0 <26 +* Erlang OTP >=22.2.0 (supported: <27) * git * file / libmagic * gcc or clang diff --git a/docs/installation/gentoo_en.md b/docs/installation/gentoo_en.md @@ -59,7 +59,7 @@ Gentoo quite pointedly does not come with a cron daemon installed, and as such i If you would not like to install the optional packages, remove them from this line. -If you're running this from a low-powered virtual machine, it should work though it will take some time. There were no issues on a VPS with a single core and 1GB of RAM; if you are using an even more limited device and run into issues, you can try creating a swapfile or use a more powerful machine running Gentoo to [cross build](https://wiki.gentoo.org/wiki/Cross_build_environment). If you have a wait ahead of you, now would be a good time to take a break, strech a bit, refresh your beverage of choice and/or get a snack, and reply to Arch users' posts with "I use Gentoo btw" as we do. +If you're running this from a low-powered virtual machine, it should work though it will take some time. There were no issues on a VPS with a single core and 1GB of RAM; if you are using an even more limited device and run into issues, you can try creating a swapfile or use a more powerful machine running Gentoo to [cross build](https://wiki.gentoo.org/wiki/Cross_build_environment). If you have a wait ahead of you, now would be a good time to take a break, stretch a bit, refresh your beverage of choice and/or get a snack, and reply to Arch users' posts with "I use Gentoo btw" as we do. ### Install PostgreSQL @@ -104,7 +104,7 @@ Not only does this make it much easier to deploy changes you make, as you can co * Add a new system user for the Pleroma service and set up default directories: -Remove `,wheel` if you do not want this user to be able to use `sudo`, however note that being able to `sudo` as the `pleroma` user will make finishing the insallation and common maintenence tasks somewhat easier: +Remove `,wheel` if you do not want this user to be able to use `sudo`, however note that being able to `sudo` as the `pleroma` user will make finishing the installation and common maintenance tasks somewhat easier: ```shell # useradd -m -G users,wheel -s /bin/bash pleroma diff --git a/docs/installation/gentoo_otp_en.md b/docs/installation/gentoo_otp_en.md @@ -49,7 +49,7 @@ Gentoo quite pointedly does not come with a cron daemon installed, and as such i If you would not like to install the optional packages, remove them from this line. -If you're running this from a low-powered virtual machine, it should work though it will take some time. There were no issues on a VPS with a single core and 1GB of RAM; if you are using an even more limited device and run into issues, you can try creating a swapfile or use a more powerful machine running Gentoo to [cross build](https://wiki.gentoo.org/wiki/Cross_build_environment). If you have a wait ahead of you, now would be a good time to take a break, strech a bit, refresh your beverage of choice and/or get a snack, and reply to Arch users' posts with "I use Gentoo btw" as we do. +If you're running this from a low-powered virtual machine, it should work though it will take some time. There were no issues on a VPS with a single core and 1GB of RAM; if you are using an even more limited device and run into issues, you can try creating a swapfile or use a more powerful machine running Gentoo to [cross build](https://wiki.gentoo.org/wiki/Cross_build_environment). If you have a wait ahead of you, now would be a good time to take a break, stretch a bit, refresh your beverage of choice and/or get a snack, and reply to Arch users' posts with "I use Gentoo btw" as we do. ### Setup PostgreSQL diff --git a/docs/installation/openbsd_en.md b/docs/installation/openbsd_en.md @@ -62,7 +62,7 @@ rcctl start postgresql To check that it started properly and didn't fail right after starting, you can run `ps aux | grep postgres`, there should be multiple lines of output. #### httpd -httpd will have three fuctions: +httpd will have three functions: * redirect requests trying to reach the instance over http to the https URL * serve a robots.txt file @@ -225,7 +225,7 @@ pass in quick on $if inet6 proto icmp6 to ($if) icmp6-type { echoreq unreach par pass in quick on $if proto tcp to ($if) port { http https } # relayd/httpd pass in quick on $if proto tcp from $authorized_ssh_clients to ($if) port ssh ``` -Replace *<network interface\>* by your server's network interface name (which you can get with ifconfig). Consider replacing the content of the authorized\_ssh\_clients macro by, for exemple, your home IP address, to avoid SSH connection attempts from bots. +Replace *<network interface\>* by your server's network interface name (which you can get with ifconfig). Consider replacing the content of the authorized\_ssh\_clients macro by, for example, your home IP address, to avoid SSH connection attempts from bots. Check pf's configuration by running `pfctl -nf /etc/pf.conf`, load it with `pfctl -f /etc/pf.conf` and enable pf at boot with `rcctl enable pf`. diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md @@ -238,7 +238,7 @@ At this point if you open your (sub)domain in a browser you should see a 502 err systemctl enable pleroma ``` -If everything worked, you should see Pleroma-FE when visiting your domain. If that didn't happen, try reviewing the installation steps, starting Pleroma in the foreground and seeing if there are any errrors. +If everything worked, you should see Pleroma-FE when visiting your domain. If that didn't happen, try reviewing the installation steps, starting Pleroma in the foreground and seeing if there are any errors. Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC, you can also [file an issue on our Gitlab](https://git.pleroma.social/pleroma/pleroma-support/issues/new). diff --git a/installation/pleroma-mongooseim.cfg b/installation/pleroma-mongooseim.cfg @@ -204,7 +204,7 @@ ]} ]}, - %% Following HTTP API is deprected, the new one abouve should be used instead + %% Following HTTP API is deprecated, the new one above should be used instead { {5288, "127.0.0.1"} , ejabberd_cowboy, [ {num_acceptors, 10}, @@ -824,7 +824,7 @@ %% Enable archivization for private messages (default) % {pm, [ - %% Top-level options can be overriden here if needed, for example: + %% Top-level options can be overridden here if needed, for example: % {async_writer, false} % ]}, @@ -834,7 +834,7 @@ %% % {muc, [ % {host, "muc.@HOST@"} - %% As with pm, top-level options can be overriden for MUC archive + %% As with pm, top-level options can be overridden for MUC archive % ]}, % %% Do not use a <stanza-id/> element (by default stanzaid is used) diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex @@ -193,7 +193,7 @@ defmodule Mix.Tasks.Pleroma.Database do "ALTER DATABASE #{db} SET default_text_search_config = '#{tsconfig}';" ) - # non-exist config will not raise excpetion but only give >0 messages + # non-exist config will not raise exception but only give >0 messages if length(msg) > 0 do shell_info("Error: #{inspect(msg, pretty: true)}") else diff --git a/lib/mix/tasks/pleroma/digest.ex b/lib/mix/tasks/pleroma/digest.ex @@ -30,7 +30,7 @@ defmodule Mix.Tasks.Pleroma.Digest do shell_info("Digest email have been sent to #{nickname} (#{user.email})") else _ -> - shell_info("Cound't find any mentions for #{nickname} since #{last_digest_emailed_at}") + shell_info("Couldn't find any mentions for #{nickname} since #{last_digest_emailed_at}") end end end diff --git a/lib/mix/tasks/pleroma/ecto/rollback.ex b/lib/mix/tasks/pleroma/ecto/rollback.ex @@ -61,7 +61,7 @@ defmodule Mix.Tasks.Pleroma.Ecto.Rollback do Logger.configure(level: :info) if opts[:env] == "test" do - Logger.info("Rollback succesfully") + Logger.info("Rollback successfully") else {:ok, _, _} = Ecto.Migrator.with_repo(Pleroma.Repo, &Ecto.Migrator.run(&1, path, :down, opts)) diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex @@ -111,7 +111,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do {:ok, _} = :zip.unzip(binary_archive, - cwd: pack_path, + cwd: String.to_charlist(pack_path), file_list: files_to_unzip ) diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex @@ -292,7 +292,7 @@ defmodule Mix.Tasks.Pleroma.Instance do if db_configurable? do shell_info( - " Please transfer your config to the database after running database migrations. Refer to \"Transfering the config to/from the database\" section of the docs for more information." + " Please transfer your config to the database after running database migrations. Refer to \"Transferring the config to/from the database\" section of the docs for more information." ) end else @@ -352,6 +352,4 @@ defmodule Mix.Tasks.Pleroma.Instance do enabled_filters end - - defp upload_filters(_), do: [] end diff --git a/lib/phoenix/transports/web_socket/raw.ex b/lib/phoenix/transports/web_socket/raw.ex @@ -1,93 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Phoenix.Transports.WebSocket.Raw do - import Plug.Conn, - only: [ - fetch_query_params: 1, - send_resp: 3 - ] - - alias Phoenix.Socket.Transport - - def default_config do - [ - timeout: 60_000, - transport_log: false, - cowboy: Phoenix.Endpoint.CowboyWebSocket - ] - end - - def init(%Plug.Conn{method: "GET"} = conn, {endpoint, handler, transport}) do - {_, opts} = handler.__transport__(transport) - - conn = - conn - |> fetch_query_params - |> Transport.transport_log(opts[:transport_log]) - |> Transport.check_origin(handler, endpoint, opts) - - case conn do - %{halted: false} = conn -> - case handler.connect(%{ - endpoint: endpoint, - transport: transport, - options: [serializer: nil], - params: conn.params - }) do - {:ok, socket} -> - {:ok, conn, {__MODULE__, {socket, opts}}} - - :error -> - send_resp(conn, :forbidden, "") - {:error, conn} - end - - _ -> - {:error, conn} - end - end - - def init(conn, _) do - send_resp(conn, :bad_request, "") - {:error, conn} - end - - def ws_init({socket, config}) do - Process.flag(:trap_exit, true) - {:ok, %{socket: socket}, config[:timeout]} - end - - def ws_handle(op, data, state) do - state.socket.handler - |> apply(:handle, [op, data, state]) - |> case do - {op, data} -> - {:reply, {op, data}, state} - - {op, data, state} -> - {:reply, {op, data}, state} - - %{} = state -> - {:ok, state} - - _ -> - {:ok, state} - end - end - - def ws_info({_, _} = tuple, state) do - {:reply, tuple, state} - end - - def ws_info(_tuple, state), do: {:ok, state} - - def ws_close(state) do - ws_handle(:closed, :normal, state) - end - - def ws_terminate(reason, state) do - ws_handle(:closed, reason, state) - end -end diff --git a/lib/pleroma/activity/html.ex b/lib/pleroma/activity/html.ex @@ -28,7 +28,7 @@ defmodule Pleroma.Activity.HTML do end end - defp add_cache_key_for(activity_id, additional_key) do + def add_cache_key_for(activity_id, additional_key) do current = get_cache_keys_for(activity_id) unless additional_key in current do diff --git a/lib/pleroma/activity/queries.ex b/lib/pleroma/activity/queries.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Activity.Queries do import Ecto.Query, only: [from: 2, where: 3] - @type query :: Ecto.Queryable.t() | Activity.t() + @type query :: Ecto.Queryable.t() | Pleroma.Activity.t() alias Pleroma.Activity alias Pleroma.User diff --git a/lib/pleroma/announcement.ex b/lib/pleroma/announcement.ex @@ -23,19 +23,21 @@ defmodule Pleroma.Announcement do timestamps(type: :utc_datetime) end - def change(struct, params \\ %{}) do - struct - |> cast(validate_params(struct, params), [:data, :starts_at, :ends_at, :rendered]) + @doc "Generates changeset for %Pleroma.Announcement{}" + @spec changeset(%__MODULE__{}, map()) :: %Ecto.Changeset{} + def changeset(announcement \\ %__MODULE__{}, params \\ %{data: %{}}) do + announcement + |> cast(validate_params(announcement, params), [:data, :starts_at, :ends_at, :rendered]) |> validate_required([:data]) end - defp validate_params(struct, params) do + defp validate_params(announcement, params) do base_data = %{ "content" => "", "all_day" => false } - |> Map.merge((struct && struct.data) || %{}) + |> Map.merge((announcement && announcement.data) || %{}) merged_data = Map.merge(base_data, params.data) @@ -61,13 +63,13 @@ defmodule Pleroma.Announcement do end def add(params) do - changeset = change(%__MODULE__{}, params) + changeset = changeset(%__MODULE__{}, params) Repo.insert(changeset) end def update(announcement, params) do - changeset = change(announcement, params) + changeset = changeset(announcement, params) Repo.update(changeset) end diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex @@ -14,7 +14,6 @@ defmodule Pleroma.Application do @name Mix.Project.config()[:name] @version Mix.Project.config()[:version] @repository Mix.Project.config()[:source_url] - @mix_env Mix.env() def name, do: @name def version, do: @version @@ -98,7 +97,7 @@ defmodule Pleroma.Application do {Task.Supervisor, name: Pleroma.TaskSupervisor} ] ++ cachex_children() ++ - http_children(adapter, @mix_env) ++ + http_children(adapter) ++ [ Pleroma.Stats, Pleroma.JobQueueMonitor, @@ -106,8 +105,9 @@ defmodule Pleroma.Application do {Oban, Config.get(Oban)}, Pleroma.Web.Endpoint ] ++ - task_children(@mix_env) ++ - dont_run_in_test(@mix_env) ++ + task_children() ++ + streamer_registry() ++ + background_migrators() ++ shout_child(shout_enabled?()) ++ [Pleroma.Gopher.Server] @@ -116,12 +116,7 @@ defmodule Pleroma.Application do # If we have a lot of caches, default max_restarts can cause test # resets to fail. # Go for the default 3 unless we're in test - max_restarts = - if @mix_env == :test do - 100 - else - 3 - end + max_restarts = Application.get_env(:pleroma, __MODULE__)[:max_restarts] opts = [strategy: :one_for_one, name: Pleroma.Supervisor, max_restarts: max_restarts] result = Supervisor.start_link(children, opts) @@ -159,7 +154,7 @@ defmodule Pleroma.Application do raise "Invalid custom modules" {:ok, modules, _warnings} -> - if @mix_env != :test do + if Application.get_env(:pleroma, __MODULE__)[:load_custom_modules] do Enum.each(modules, fn mod -> Logger.info("Custom module loaded: #{inspect(mod)}") end) @@ -213,24 +208,30 @@ defmodule Pleroma.Application do defp shout_enabled?, do: Config.get([:shout, :enabled]) - defp dont_run_in_test(env) when env in [:test, :benchmark], do: [] - - defp dont_run_in_test(_) do - [ - {Registry, - [ - name: Pleroma.Web.Streamer.registry(), - keys: :duplicate, - partitions: System.schedulers_online() - ]} - ] ++ background_migrators() + defp streamer_registry do + if Application.get_env(:pleroma, __MODULE__)[:streamer_registry] do + [ + {Registry, + [ + name: Pleroma.Web.Streamer.registry(), + keys: :duplicate, + partitions: System.schedulers_online() + ]} + ] + else + [] + end end defp background_migrators do - [ - Pleroma.Migrators.HashtagsTableMigrator, - Pleroma.Migrators.ContextObjectsDeletionMigrator - ] + if Application.get_env(:pleroma, __MODULE__)[:background_migrators] do + [ + Pleroma.Migrators.HashtagsTableMigrator, + Pleroma.Migrators.ContextObjectsDeletionMigrator + ] + else + [] + end end defp shout_child(true) do @@ -242,37 +243,43 @@ defmodule Pleroma.Application do defp shout_child(_), do: [] - defp task_children(:test) do - [ + defp task_children do + children = [ %{ id: :web_push_init, start: {Task, :start_link, [&Pleroma.Web.Push.init/0]}, restart: :temporary } ] - end - defp task_children(_) do - [ - %{ - id: :web_push_init, - start: {Task, :start_link, [&Pleroma.Web.Push.init/0]}, - restart: :temporary - }, - %{ - id: :internal_fetch_init, - start: {Task, :start_link, [&Pleroma.Web.ActivityPub.InternalFetchActor.init/0]}, - restart: :temporary - } - ] + if Application.get_env(:pleroma, __MODULE__)[:internal_fetch] do + children ++ + [ + %{ + id: :internal_fetch_init, + start: {Task, :start_link, [&Pleroma.Web.ActivityPub.InternalFetchActor.init/0]}, + restart: :temporary + } + ] + else + children + end end # start hackney and gun pools in tests - defp http_children(_, :test) do - http_children(Tesla.Adapter.Hackney, nil) ++ http_children(Tesla.Adapter.Gun, nil) + defp http_children(adapter) do + if Application.get_env(:pleroma, __MODULE__)[:test_http_pools] do + http_children_hackney() ++ http_children_gun() + else + cond do + match?(Tesla.Adapter.Hackney, adapter) -> http_children_hackney() + match?(Tesla.Adapter.Gun, adapter) -> http_children_gun() + true -> [] + end + end end - defp http_children(Tesla.Adapter.Hackney, _) do + defp http_children_hackney do pools = [:federation, :media] pools = @@ -288,13 +295,11 @@ defmodule Pleroma.Application do end end - defp http_children(Tesla.Adapter.Gun, _) do + defp http_children_gun do Pleroma.Gun.ConnectionPool.children() ++ [{Task, &Pleroma.HTTP.AdapterHelper.Gun.limiter_setup/0}] end - defp http_children(_, _), do: [] - @spec limiters_setup() :: :ok def limiters_setup do config = Config.get(ConcurrentLimiter, []) diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex @@ -7,7 +7,10 @@ defmodule Pleroma.ApplicationRequirements do The module represents the collection of validations to runs before start server. """ - defmodule VerifyError, do: defexception([:message]) + defmodule VerifyError do + defexception([:message]) + @type t :: %__MODULE__{} + end alias Pleroma.Config alias Pleroma.Helpers.MediaHelper @@ -193,8 +196,6 @@ defmodule Pleroma.ApplicationRequirements do end end - defp check_system_commands!(result), do: result - defp check_repo_pool_size!(:ok) do if Pleroma.Config.get([Pleroma.Repo, :pool_size], 10) != 10 and not Pleroma.Config.get([:dangerzone, :override_repo_pool_size], false) do diff --git a/lib/pleroma/bookmark.ex b/lib/pleroma/bookmark.ex @@ -22,8 +22,8 @@ defmodule Pleroma.Bookmark do timestamps() end - @spec create(FlakeId.Ecto.CompatType.t(), FlakeId.Ecto.CompatType.t()) :: - {:ok, Bookmark.t()} | {:error, Changeset.t()} + @spec create(Ecto.UUID.t(), Ecto.UUID.t()) :: + {:ok, Bookmark.t()} | {:error, Ecto.Changeset.t()} def create(user_id, activity_id) do attrs = %{ user_id: user_id, @@ -37,7 +37,7 @@ defmodule Pleroma.Bookmark do |> Repo.insert() end - @spec for_user_query(FlakeId.Ecto.CompatType.t()) :: Ecto.Query.t() + @spec for_user_query(Ecto.UUID.t()) :: Ecto.Query.t() def for_user_query(user_id) do Bookmark |> where(user_id: ^user_id) @@ -52,8 +52,8 @@ defmodule Pleroma.Bookmark do |> Repo.one() end - @spec destroy(FlakeId.Ecto.CompatType.t(), FlakeId.Ecto.CompatType.t()) :: - {:ok, Bookmark.t()} | {:error, Changeset.t()} + @spec destroy(Ecto.UUID.t(), Ecto.UUID.t()) :: + {:ok, Bookmark.t()} | {:error, Ecto.Changeset.t()} def destroy(user_id, activity_id) do from(b in Bookmark, where: b.user_id == ^user_id, diff --git a/lib/pleroma/caching.ex b/lib/pleroma/caching.ex @@ -8,10 +8,13 @@ defmodule Pleroma.Caching do @callback put(Cachex.cache(), any(), any(), Keyword.t()) :: {Cachex.status(), boolean()} @callback put(Cachex.cache(), any(), any()) :: {Cachex.status(), boolean()} @callback fetch!(Cachex.cache(), any(), function() | nil) :: any() + @callback fetch(Cachex.cache(), any(), function() | nil) :: + {atom(), any()} | {atom(), any(), any()} # @callback del(Cachex.cache(), any(), Keyword.t()) :: {Cachex.status(), boolean()} @callback del(Cachex.cache(), any()) :: {Cachex.status(), boolean()} @callback stream!(Cachex.cache(), any()) :: Enumerable.t() @callback expire_at(Cachex.cache(), binary(), number()) :: {Cachex.status(), boolean()} + @callback expire(Cachex.cache(), binary(), number()) :: {Cachex.status(), boolean()} @callback exists?(Cachex.cache(), any()) :: {Cachex.status(), boolean()} @callback execute!(Cachex.cache(), function()) :: any() @callback get_and_update(Cachex.cache(), any(), function()) :: diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex @@ -29,7 +29,7 @@ defmodule Pleroma.Captcha.Kocaptcha do @impl Service def validate(_token, captcha, answer_data) do - # Here the token is unsed, because the unencrypted captcha answer is just passed to method + # Here the token is unused, because the unencrypted captcha answer is just passed to method if not is_nil(captcha) and :crypto.hash(:md5, captcha) |> Base.encode16() == String.upcase(answer_data), do: :ok, diff --git a/lib/pleroma/chat.ex b/lib/pleroma/chat.ex @@ -42,7 +42,7 @@ defmodule Pleroma.Chat do |> unique_constraint(:user_id, name: :chats_user_id_recipient_index) end - @spec get_by_user_and_id(User.t(), FlakeId.Ecto.CompatType.t()) :: + @spec get_by_user_and_id(User.t(), Ecto.UUID.t()) :: {:ok, t()} | {:error, :not_found} def get_by_user_and_id(%User{id: user_id}, id) do from(c in __MODULE__, @@ -52,17 +52,17 @@ defmodule Pleroma.Chat do |> Repo.find_resource() end - @spec get_by_id(FlakeId.Ecto.CompatType.t()) :: t() | nil + @spec get_by_id(Ecto.UUID.t()) :: t() | nil def get_by_id(id) do Repo.get(__MODULE__, id) end - @spec get(FlakeId.Ecto.CompatType.t(), String.t()) :: t() | nil + @spec get(Ecto.UUID.t(), String.t()) :: t() | nil def get(user_id, recipient) do Repo.get_by(__MODULE__, user_id: user_id, recipient: recipient) end - @spec get_or_create(FlakeId.Ecto.CompatType.t(), String.t()) :: + @spec get_or_create(Ecto.UUID.t(), String.t()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} def get_or_create(user_id, recipient) do %__MODULE__{} @@ -75,7 +75,7 @@ defmodule Pleroma.Chat do ) end - @spec bump_or_create(FlakeId.Ecto.CompatType.t(), String.t()) :: + @spec bump_or_create(Ecto.UUID.t(), String.t()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} def bump_or_create(user_id, recipient) do %__MODULE__{} @@ -87,7 +87,7 @@ defmodule Pleroma.Chat do ) end - @spec for_user_query(FlakeId.Ecto.CompatType.t()) :: Ecto.Query.t() + @spec for_user_query(Ecto.UUID.t()) :: Ecto.Query.t() def for_user_query(user_id) do from(c in Chat, where: c.user_id == ^user_id, diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex @@ -172,7 +172,7 @@ defmodule Pleroma.Config.DeprecationWarnings do ``` config :pleroma, :mrf, - transparency_exclusions: [{"instance.tld", "Reason to exlude transparency"}] + transparency_exclusions: [{"instance.tld", "Reason to exclude transparency"}] ``` """) @@ -213,7 +213,7 @@ defmodule Pleroma.Config.DeprecationWarnings do check_gun_pool_options(), check_activity_expiration_config(), check_remote_ip_plug_name(), - check_uploders_s3_public_endpoint(), + check_uploaders_s3_public_endpoint(), check_old_chat_shoutbox(), check_quarantined_instances_tuples(), check_transparency_exclusions_tuples(), @@ -256,7 +256,7 @@ defmodule Pleroma.Config.DeprecationWarnings do move_namespace_and_warn(@mrf_config_map, warning_preface) end - @spec move_namespace_and_warn([config_map()], String.t()) :: :ok | nil + @spec move_namespace_and_warn([config_map()], String.t()) :: :ok | :error def move_namespace_and_warn(config_map, warning_preface) do warning = Enum.reduce(config_map, "", fn @@ -279,7 +279,7 @@ defmodule Pleroma.Config.DeprecationWarnings do end end - @spec check_media_proxy_whitelist_config() :: :ok | nil + @spec check_media_proxy_whitelist_config() :: :ok | :error def check_media_proxy_whitelist_config do whitelist = Config.get([:media_proxy, :whitelist]) @@ -340,7 +340,7 @@ defmodule Pleroma.Config.DeprecationWarnings do end end - @spec check_activity_expiration_config() :: :ok | nil + @spec check_activity_expiration_config() :: :ok | :error def check_activity_expiration_config do warning_preface = """ !!!DEPRECATION WARNING!!! @@ -356,7 +356,7 @@ defmodule Pleroma.Config.DeprecationWarnings do ) end - @spec check_remote_ip_plug_name() :: :ok | nil + @spec check_remote_ip_plug_name() :: :ok | :error def check_remote_ip_plug_name do warning_preface = """ !!!DEPRECATION WARNING!!! @@ -372,8 +372,8 @@ defmodule Pleroma.Config.DeprecationWarnings do ) end - @spec check_uploders_s3_public_endpoint() :: :ok | nil - def check_uploders_s3_public_endpoint do + @spec check_uploaders_s3_public_endpoint() :: :ok | :error + def check_uploaders_s3_public_endpoint do s3_config = Pleroma.Config.get([Pleroma.Uploaders.S3]) use_old_config = Keyword.has_key?(s3_config, :public_endpoint) @@ -393,7 +393,7 @@ defmodule Pleroma.Config.DeprecationWarnings do end end - @spec check_old_chat_shoutbox() :: :ok | nil + @spec check_old_chat_shoutbox() :: :ok | :error def check_old_chat_shoutbox do instance_config = Pleroma.Config.get([:instance]) chat_config = Pleroma.Config.get([:chat]) || [] diff --git a/lib/pleroma/config/release_runtime_provider.ex b/lib/pleroma/config/release_runtime_provider.ex @@ -21,7 +21,7 @@ defmodule Pleroma.Config.ReleaseRuntimeProvider do with_runtime_config = if File.exists?(config_path) do # <https://git.pleroma.social/pleroma/pleroma/-/issues/3135> - %File.Stat{mode: mode} = File.lstat!(config_path) + %File.Stat{mode: mode} = File.stat!(config_path) if Bitwise.band(mode, 0o007) > 0 do raise "Configuration at #{config_path} has world-permissions, execute the following: chmod o= #{config_path}" diff --git a/lib/pleroma/config_db.ex b/lib/pleroma/config_db.ex @@ -54,7 +54,7 @@ defmodule Pleroma.ConfigDB do @spec get_by_params(map()) :: ConfigDB.t() | nil def get_by_params(%{group: _, key: _} = params), do: Repo.get_by(ConfigDB, params) - @spec changeset(ConfigDB.t(), map()) :: Changeset.t() + @spec changeset(ConfigDB.t(), map()) :: Ecto.Changeset.t() def changeset(config, params \\ %{}) do config |> cast(params, [:key, :group, :value]) @@ -138,7 +138,7 @@ defmodule Pleroma.ConfigDB do end end - @spec update_or_create(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()} + @spec update_or_create(map()) :: {:ok, ConfigDB.t()} | {:error, Ecto.Changeset.t()} def update_or_create(params) do params = Map.put(params, :value, to_elixir_types(params[:value])) search_opts = Map.take(params, [:group, :key]) @@ -175,7 +175,7 @@ defmodule Pleroma.ConfigDB do end) end - @spec delete(ConfigDB.t() | map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()} + @spec delete(ConfigDB.t() | map()) :: {:ok, ConfigDB.t()} | {:error, Ecto.Changeset.t()} def delete(%ConfigDB{} = config), do: Repo.delete(config) def delete(params) do diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex @@ -77,6 +77,14 @@ defmodule Pleroma.Constants do ] ) + const(allowed_user_actor_types, + do: [ + "Person", + "Service", + "Group" + ] + ) + # basic regex, just there to weed out potential mistakes # https://datatracker.ietf.org/doc/html/rfc2045#section-5.1 const(mime_regex, diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex @@ -57,7 +57,7 @@ defmodule Pleroma.Conversation do 3. Bump all relevant participations to 'unread' """ def create_or_bump_for(activity, opts \\ []) do - with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity), + with true <- Pleroma.Web.ActivityPub.Visibility.direct?(activity), "Create" <- activity.data["type"], %Object{} = object <- Object.normalize(activity, fetch: false), true <- object.data["type"] in ["Note", "Question"], diff --git a/lib/pleroma/data_migration.ex b/lib/pleroma/data_migration.ex @@ -12,6 +12,8 @@ defmodule Pleroma.DataMigration do import Ecto.Changeset import Ecto.Query + @type t :: %__MODULE__{} + schema "data_migrations" do field(:name, :string) field(:state, State, default: :pending) diff --git a/lib/pleroma/docs/generator.ex b/lib/pleroma/docs/generator.ex @@ -15,7 +15,7 @@ defmodule Pleroma.Docs.Generator 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` + # but in test environments some transient modules `:elixir_compiler_XX` # are loaded for some reason (where XX is a random integer). Code.ensure_loaded(module) diff --git a/lib/pleroma/docs/json.ex b/lib/pleroma/docs/json.ex @@ -18,7 +18,7 @@ defmodule Pleroma.Docs.JSON do :persistent_term.put(@term, Pleroma.Docs.Generator.convert_to_strings(descriptions)) end - @spec compiled_descriptions :: Map.t() + @spec compiled_descriptions :: map() def compiled_descriptions do :persistent_term.get(@term) end diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/bare_uri.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/bare_uri.ex @@ -8,10 +8,12 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.BareUri do def type, do: :string def cast(uri) when is_binary(uri) do - case URI.parse(uri) do - %URI{scheme: nil} -> :error - %URI{} -> {:ok, uri} - _ -> :error + parsed = URI.parse(uri) + + if is_nil(parsed.scheme) do + :error + else + {:ok, uri} end end diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex @@ -24,6 +24,8 @@ defmodule Pleroma.Emoji do defstruct [:code, :file, :tags, :safe_code, :safe_file] + @type t :: %__MODULE__{} + @doc "Build emoji struct" def build({code, file, tags}) do %__MODULE__{ @@ -49,12 +51,12 @@ defmodule Pleroma.Emoji do end @doc "Returns the path of the emoji `name`." - @spec get(String.t()) :: String.t() | nil + @spec get(String.t()) :: Pleroma.Emoji.t() | nil def get(name) do name = maybe_strip_name(name) case :ets.lookup(@ets, name) do - [{_, path}] -> path + [{_, emoji}] -> emoji _ -> nil end end @@ -136,23 +138,23 @@ defmodule Pleroma.Emoji do emojis = emojis ++ regional_indicators for emoji <- emojis do - def is_unicode_emoji?(unquote(emoji)), do: true + def unicode?(unquote(emoji)), do: true end - def is_unicode_emoji?(_), do: false + def unicode?(_), do: false @emoji_regex ~r/:[A-Za-z0-9_-]+(@.+)?:/ - def is_custom_emoji?(s) when is_binary(s), do: Regex.match?(@emoji_regex, s) + def custom?(s) when is_binary(s), do: Regex.match?(@emoji_regex, s) - def is_custom_emoji?(_), do: false + def custom?(_), do: false def maybe_strip_name(name) when is_binary(name), do: String.trim(name, ":") def maybe_strip_name(name), do: name def maybe_quote(name) when is_binary(name) do - if is_unicode_emoji?(name) do + if unicode?(name) do name else if String.starts_with?(name, ":") do diff --git a/lib/pleroma/emoji/loader.ex b/lib/pleroma/emoji/loader.ex @@ -15,8 +15,6 @@ defmodule Pleroma.Emoji.Loader do require Logger - @mix_env Mix.env() - @type pattern :: Regex.t() | module() | String.t() @type patterns :: pattern() | [pattern()] @type group_patterns :: keyword(patterns()) @@ -79,7 +77,7 @@ defmodule Pleroma.Emoji.Loader do # for testing emoji.txt entries we do not want exposed in normal operation test_emoji = - if @mix_env == :test do + if Application.get_env(:pleroma, __MODULE__)[:test_emoji] do load_from_file("test/config/emoji.txt", emoji_groups) else [] diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex @@ -100,7 +100,7 @@ defmodule Pleroma.Emoji.Pack do {:ok, _emoji_files} = :zip.unzip( to_charlist(file.path), - [{:file_list, Enum.map(emojies, & &1[:path])}, {:cwd, tmp_dir}] + [{:file_list, Enum.map(emojies, & &1[:path])}, {:cwd, String.to_charlist(tmp_dir)}] ) {_, updated_pack} = @@ -209,7 +209,9 @@ defmodule Pleroma.Emoji.Pack do with :ok <- validate_shareable_packs_available(uri) do uri - |> URI.merge("/api/pleroma/emoji/packs?page=#{opts[:page]}&page_size=#{opts[:page_size]}") + |> URI.merge( + "/api/v1/pleroma/emoji/packs?page=#{opts[:page]}&page_size=#{opts[:page_size]}" + ) |> http_get() end end @@ -249,8 +251,12 @@ defmodule Pleroma.Emoji.Pack do uri = url |> String.trim() |> URI.parse() with :ok <- validate_shareable_packs_available(uri), + {:ok, %{"files_count" => files_count}} <- + uri |> URI.merge("/api/v1/pleroma/emoji/pack?name=#{name}&page_size=0") |> http_get(), {:ok, remote_pack} <- - uri |> URI.merge("/api/pleroma/emoji/pack?name=#{name}") |> http_get(), + uri + |> URI.merge("/api/v1/pleroma/emoji/pack?name=#{name}&page_size=#{files_count}") + |> http_get(), {:ok, %{sha: sha, url: url} = pack_info} <- fetch_pack_info(remote_pack, uri, name), {:ok, archive} <- download_archive(url, sha), pack <- copy_as(remote_pack, as || name), @@ -592,7 +598,7 @@ defmodule Pleroma.Emoji.Pack do {:ok, %{ sha: sha, - url: URI.merge(uri, "/api/pleroma/emoji/packs/archive?name=#{name}") |> to_string() + url: URI.merge(uri, "/api/v1/pleroma/emoji/packs/archive?name=#{name}") |> to_string() }} %{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) -> diff --git a/lib/pleroma/filter.ex b/lib/pleroma/filter.ex @@ -216,9 +216,6 @@ defmodule Pleroma.Filter do :re -> ~r/\b#{phrases}\b/i - - _ -> - nil end end diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex @@ -241,13 +241,13 @@ defmodule Pleroma.FollowingRelationship do end @doc """ - For a query with joined activity, - keeps rows where activity's actor is followed by user -or- is NOT domain-blocked by user. + For a query with joined activity's actor, + keeps rows where actor is followed by user -or- is NOT domain-blocked by user. """ def keep_following_or_not_domain_blocked(query, user) do where( query, - [_, activity], + [_, user_actor: user_actor], fragment( # "(actor's domain NOT in domain_blocks) OR (actor IS in followed AP IDs)" """ @@ -255,9 +255,9 @@ defmodule Pleroma.FollowingRelationship do ? = ANY(SELECT ap_id FROM users AS u INNER JOIN following_relationships AS fr ON u.id = fr.following_id WHERE fr.follower_id = ? AND fr.state = ?) """, - activity.actor, + user_actor.ap_id, ^user.domain_blocks, - activity.actor, + user_actor.ap_id, ^User.binary_id(user.id), ^accept_state_code() ) diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex @@ -114,7 +114,7 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do def response("/notices/" <> id) do with %Activity{} = activity <- Activity.get_by_id(id), - true <- Visibility.is_public?(activity) do + true <- Visibility.public?(activity) do activities = ActivityPub.fetch_activities_for_context(activity.data["context"]) |> render_activities diff --git a/lib/pleroma/gun/connection_pool/reclaimer.ex b/lib/pleroma/gun/connection_pool/reclaimer.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Gun.ConnectionPool.Reclaimer do def start_monitor do pid = - case :gen_server.start(__MODULE__, [], name: {:via, Registry, {registry(), "reclaimer"}}) do + case GenServer.start_link(__MODULE__, [], name: {:via, Registry, {registry(), "reclaimer"}}) do {:ok, pid} -> pid diff --git a/lib/pleroma/gun/connection_pool/worker_supervisor.ex b/lib/pleroma/gun/connection_pool/worker_supervisor.ex @@ -18,10 +18,12 @@ defmodule Pleroma.Gun.ConnectionPool.WorkerSupervisor do ) end - def start_worker(opts, retry \\ false) do + def start_worker(opts, last_attempt \\ false) do case DynamicSupervisor.start_child(__MODULE__, {Pleroma.Gun.ConnectionPool.Worker, opts}) do {:error, :max_children} -> - if retry or free_pool() == :error do + funs = [fn -> last_attempt end, fn -> match?(:error, free_pool()) end] + + if Enum.any?(funs, fn fun -> fun.() end) do :telemetry.execute([:pleroma, :connection_pool, :provision_failure], %{opts: opts}) {:error, :pool_full} else diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex @@ -40,92 +40,35 @@ defmodule Pleroma.Helpers.MediaHelper do end # Note: video thumbnail is intentionally not resized (always has original dimensions) + @spec video_framegrab(String.t()) :: {:ok, binary()} | {:error, any()} def video_framegrab(url) do with executable when is_binary(executable) <- System.find_executable("ffmpeg"), {:ok, env} <- HTTP.get(url, [], pool: :media), - {:ok, fifo_path} <- mkfifo(), - args = [ - "-y", - "-i", - fifo_path, - "-vframes", - "1", - "-f", - "mjpeg", - "-loglevel", - "error", - "-" - ] do - run_fifo(fifo_path, env, executable, args) + {:ok, pid} <- StringIO.open(env.body) do + body_stream = IO.binstream(pid, 1) + + result = + Exile.stream!( + [ + executable, + "-i", + "pipe:0", + "-vframes", + "1", + "-f", + "mjpeg", + "pipe:1" + ], + input: body_stream, + ignore_epipe: true, + stderr: :disable + ) + |> Enum.into(<<>>) + + {:ok, result} else nil -> {:error, {:ffmpeg, :command_not_found}} {:error, _} = error -> error end end - - defp run_fifo(fifo_path, env, executable, args) do - pid = - Port.open({:spawn_executable, executable}, [ - :use_stdio, - :stream, - :exit_status, - :binary, - args: args - ]) - - fifo = Port.open(to_charlist(fifo_path), [:eof, :binary, :stream, :out]) - fix = Pleroma.Helpers.QtFastStart.fix(env.body) - true = Port.command(fifo, fix) - :erlang.port_close(fifo) - loop_recv(pid) - after - File.rm(fifo_path) - end - - defp mkfifo do - path = Path.join(System.tmp_dir!(), "pleroma-media-preview-pipe-#{Ecto.UUID.generate()}") - - case System.cmd("mkfifo", [path]) do - {_, 0} -> - spawn(fifo_guard(path)) - {:ok, path} - - {_, err} -> - {:error, {:fifo_failed, err}} - end - end - - defp fifo_guard(path) do - pid = self() - - fn -> - ref = Process.monitor(pid) - - receive do - {:DOWN, ^ref, :process, ^pid, _} -> - File.rm(path) - end - end - end - - defp loop_recv(pid) do - loop_recv(pid, <<>>) - end - - defp loop_recv(pid, acc) do - receive do - {^pid, {:data, data}} -> - loop_recv(pid, acc <> data) - - {^pid, {:exit_status, 0}} -> - {:ok, acc} - - {^pid, {:exit_status, status}} -> - {:error, status} - after - 5000 -> - :erlang.port_close(pid) - {:error, :timeout} - end - end end diff --git a/lib/pleroma/helpers/qt_fast_start.ex b/lib/pleroma/helpers/qt_fast_start.ex @@ -40,16 +40,21 @@ defmodule Pleroma.Helpers.QtFastStart do got_mdat, acc ) do - full_size = (size - 8) * 8 - <<data::bits-size(full_size), rest::bits>> = rest - - acc = [ - {fourcc, pos, pos + size, size, - <<size::integer-big-size(32), fourcc::bits-size(32), data::bits>>} - | acc - ] - - fix(rest, pos + size, got_moov || fourcc == "moov", got_mdat || fourcc == "mdat", acc) + try do + full_size = (size - 8) * 8 + <<data::bits-size(full_size), rest::bits>> = rest + + acc = [ + {fourcc, pos, pos + size, size, + <<size::integer-big-size(32), fourcc::bits-size(32), data::bits>>} + | acc + ] + + fix(rest, pos + size, got_moov || fourcc == "moov", got_mdat || fourcc == "mdat", acc) + rescue + _ -> + :abort + end end defp fix(<<>>, _pos, _, _, acc) do @@ -121,9 +126,15 @@ defmodule Pleroma.Helpers.QtFastStart do <<pos::integer-big-size(unquote(size)), rest::bits>>, acc ) do - rewrite_entries(unquote(size), offset, rest, [ - acc | <<pos + offset::integer-big-size(unquote(size))>> - ]) + rewrite_entries( + unquote(size), + offset, + rest, + acc ++ + [ + <<pos + offset::integer-big-size(unquote(size))>> + ] + ) end end diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex @@ -6,8 +6,6 @@ defmodule Pleroma.HTML do # Scrubbers are compiled on boot so they can be configured in OTP releases # @on_load :compile_scrubbers - @cachex Pleroma.Config.get([:cachex, :provider], Cachex) - def compile_scrubbers do dir = Path.join(:code.priv_dir(:pleroma), "scrubbers") @@ -67,27 +65,20 @@ defmodule Pleroma.HTML do end end - def extract_first_external_url_from_object(%{data: %{"content" => content}} = object) + @spec extract_first_external_url_from_object(Pleroma.Object.t()) :: + {:ok, String.t()} | {:error, :no_content} + def extract_first_external_url_from_object(%{data: %{"content" => content}}) when is_binary(content) do - unless object.data["fake"] do - key = "URL|#{object.id}" + url = + content + |> Floki.parse_fragment!() + |> Floki.find("a:not(.mention,.hashtag,.attachment,[rel~=\"tag\"])") + |> Enum.take(1) + |> Floki.attribute("href") + |> Enum.at(0) - @cachex.fetch!(:scrubber_cache, key, fn _key -> - {:commit, {:ok, extract_first_external_url(content)}} - end) - else - {:ok, extract_first_external_url(content)} - end + {:ok, url} end def extract_first_external_url_from_object(_), do: {:error, :no_content} - - def extract_first_external_url(content) do - content - |> Floki.parse_fragment!() - |> Floki.find("a:not(.mention,.hashtag,.attachment,[rel~=\"tag\"])") - |> Enum.take(1) - |> Floki.attribute("href") - |> Enum.at(0) - end end diff --git a/lib/pleroma/http.ex b/lib/pleroma/http.ex @@ -106,6 +106,10 @@ defmodule Pleroma.HTTP do [Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.ConnectionPool] end + defp adapter_middlewares({Tesla.Adapter.Finch, _}) do + [Tesla.Middleware.FollowRedirects] + end + defp adapter_middlewares(_) do if Pleroma.Config.get(:env) == :test do # Emulate redirects in test env, which are handled by adapters in other environments diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex @@ -15,8 +15,8 @@ defmodule Pleroma.HTTP.AdapterHelper do require Logger @type proxy :: - {Connection.host(), pos_integer()} - | {Connection.proxy_type(), Connection.host(), pos_integer()} + {host(), pos_integer()} + | {proxy_type(), host(), pos_integer()} @callback options(keyword(), URI.t()) :: keyword() diff --git a/lib/pleroma/http/request_builder.ex b/lib/pleroma/http/request_builder.ex @@ -54,12 +54,12 @@ defmodule Pleroma.HTTP.RequestBuilder do @doc """ Add optional parameters to the request """ - @spec add_param(Request.t(), atom(), atom(), any()) :: Request.t() + @spec add_param(Request.t(), atom(), atom() | String.t(), any()) :: Request.t() def add_param(request, :query, :query, values), do: %{request | query: values} def add_param(request, :body, :body, value), do: %{request | body: value} - def add_param(request, :body, key, value) do + def add_param(request, :body, key, value) when is_binary(key) do request |> Map.put(:body, Multipart.new()) |> Map.update!( diff --git a/lib/pleroma/instances.ex b/lib/pleroma/instances.ex @@ -7,16 +7,15 @@ defmodule Pleroma.Instances do alias Pleroma.Instances.Instance - def filter_reachable(urls_or_hosts), do: Instance.filter_reachable(urls_or_hosts) + defdelegate filter_reachable(urls_or_hosts), to: Instance - def reachable?(url_or_host), do: Instance.reachable?(url_or_host) + defdelegate reachable?(url_or_host), to: Instance - def set_reachable(url_or_host), do: Instance.set_reachable(url_or_host) + defdelegate set_reachable(url_or_host), to: Instance - def set_unreachable(url_or_host, unreachable_since \\ nil), - do: Instance.set_unreachable(url_or_host, unreachable_since) + defdelegate set_unreachable(url_or_host, unreachable_since \\ nil), to: Instance - def get_consistently_unreachable, do: Instance.get_consistently_unreachable() + defdelegate get_consistently_unreachable, to: Instance def set_consistently_unreachable(url_or_host), do: set_unreachable(url_or_host, reachability_datetime_threshold()) diff --git a/lib/pleroma/maps.ex b/lib/pleroma/maps.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Maps do @@ -18,4 +18,17 @@ defmodule Pleroma.Maps do rescue _ -> data end + + def filter_empty_values(data) do + # TODO: Change to Map.filter in Elixir 1.13+ + data + |> Enum.filter(fn + {_k, nil} -> false + {_k, ""} -> false + {_k, []} -> false + {_k, %{} = v} -> Map.keys(v) != [] + {_k, _v} -> true + end) + |> Map.new() + end end diff --git a/lib/pleroma/mfa.ex b/lib/pleroma/mfa.ex @@ -77,7 +77,7 @@ defmodule Pleroma.MFA do {:ok, codes} else {:error, msg} -> - %{error: msg} + {:error, msg} end end diff --git a/lib/pleroma/mfa/totp.ex b/lib/pleroma/mfa/totp.ex @@ -14,6 +14,7 @@ defmodule Pleroma.MFA.TOTP do @doc """ https://github.com/google/google-authenticator/wiki/Key-Uri-Format """ + @spec provisioning_uri(String.t(), String.t(), list()) :: String.t() def provisioning_uri(secret, label, opts \\ []) do query = %{ @@ -27,7 +28,7 @@ defmodule Pleroma.MFA.TOTP do |> URI.encode_query() %URI{scheme: "otpauth", host: "totp", path: "/" <> label, query: query} - |> URI.to_string() + |> to_string() end defp default_period, do: Config.get(@config_ns ++ [:period]) diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex @@ -100,7 +100,7 @@ defmodule Pleroma.Migrators.HashtagsTableMigrator do |> where([_o, hashtags_objects], is_nil(hashtags_objects.object_id)) end - @spec transfer_object_hashtags(Map.t()) :: {:noop | :ok | :error, integer()} + @spec transfer_object_hashtags(map()) :: {:noop | :ok | :error, integer()} defp transfer_object_hashtags(object) do embedded_tags = if Map.has_key?(object, :tag), do: object.tag, else: object.data["tag"] hashtags = Object.object_data_hashtags(%{"tag" => embedded_tags}) diff --git a/lib/pleroma/migrators/support/base_migrator.ex b/lib/pleroma/migrators/support/base_migrator.ex @@ -188,10 +188,11 @@ defmodule Pleroma.Migrators.Support.BaseMigrator do end defp fault_rate do - with failures_count when is_integer(failures_count) <- failures_count() do + with failures_count when is_integer(failures_count) <- failures_count(), + true <- failures_count > 0 do failures_count / Enum.max([get_stat(:affected_count, 0), 1]) else - _ -> :error + _ -> 0 end end diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex @@ -121,7 +121,7 @@ defmodule Pleroma.ModerationLog do defp prepare_log_data(attrs), do: attrs - @spec insert_log(log_params()) :: {:ok, ModerationLog} | {:error, any} + @spec insert_log(log_params()) :: {:ok, ModerationLog.t()} | {:error, any} def insert_log(%{actor: %User{}, subject: subjects, permission: permission} = attrs) do data = attrs @@ -248,7 +248,8 @@ defmodule Pleroma.ModerationLog do |> insert_log_entry_with_message() end - @spec insert_log_entry_with_message(ModerationLog) :: {:ok, ModerationLog} | {:error, any} + @spec insert_log_entry_with_message(ModerationLog.t()) :: + {:ok, ModerationLog.t()} | {:error, any} defp insert_log_entry_with_message(entry) do entry.data["message"] |> put_in(get_log_entry_message(entry)) diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex @@ -88,7 +88,7 @@ defmodule Pleroma.Notification do where: q.seen == true, select: type(q.id, :string), limit: 1, - order_by: [desc: :id] + order_by: fragment("? desc nulls last", q.id) ) end @@ -137,7 +137,7 @@ defmodule Pleroma.Notification do blocked_ap_ids = opts[:blocked_users_ap_ids] || User.blocked_users_ap_ids(user) query - |> where([n, a], a.actor not in ^blocked_ap_ids) + |> where([..., user_actor: user_actor], user_actor.ap_id not in ^blocked_ap_ids) |> FollowingRelationship.keep_following_or_not_domain_blocked(user) end @@ -148,7 +148,7 @@ defmodule Pleroma.Notification do blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block]) query - |> where([n, a], a.actor not in ^blocker_ap_ids) + |> where([..., user_actor: user_actor], user_actor.ap_id not in ^blocker_ap_ids) end end @@ -161,7 +161,7 @@ defmodule Pleroma.Notification do opts[:notification_muted_users_ap_ids] || User.notification_muted_users_ap_ids(user) query - |> where([n, a], a.actor not in ^notification_muted_ap_ids) + |> where([..., user_actor: user_actor], user_actor.ap_id not in ^notification_muted_ap_ids) |> join(:left, [n, a], tm in ThreadMute, on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data), as: :thread_mute diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex @@ -177,7 +177,10 @@ defmodule Pleroma.Object do ap_id Keyword.get(options, :fetch) -> - Fetcher.fetch_object_from_id!(ap_id, options) + case Fetcher.fetch_object_from_id(ap_id, options) do + {:ok, object} -> object + _ -> nil + end true -> get_cached_by_ap_id(ap_id) @@ -239,17 +242,17 @@ defmodule Pleroma.Object do {:ok, _} <- invalid_object_cache(object) do cleanup_attachments( Config.get([:instance, :cleanup_attachments]), - %{"object" => object} + object ) {:ok, object, deleted_activity} end end - @spec cleanup_attachments(boolean(), %{required(:object) => map()}) :: + @spec cleanup_attachments(boolean(), Object.t()) :: {:ok, Oban.Job.t() | nil} - def cleanup_attachments(true, %{"object" => _} = params) do - AttachmentsCleanupWorker.enqueue("cleanup_attachments", params) + def cleanup_attachments(true, %Object{} = object) do + AttachmentsCleanupWorker.enqueue("cleanup_attachments", %{"object" => object}) end def cleanup_attachments(_, _), do: {:ok, nil} diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex @@ -72,20 +72,25 @@ defmodule Pleroma.Object.Fetcher do {:object, data, Object.normalize(activity, fetch: false)} do {:ok, object} else - {:allowed_depth, false} -> - {:error, "Max thread distance exceeded."} + {:allowed_depth, false} = e -> + log_fetch_error(id, e) + {:error, :allowed_depth} - {:containment, _} -> - {:error, "Object containment failed."} + {:containment, reason} = e -> + log_fetch_error(id, e) + {:error, reason} - {:transmogrifier, {:error, {:reject, e}}} -> - {:reject, e} + {:transmogrifier, {:error, {:reject, reason}}} = e -> + log_fetch_error(id, e) + {:reject, reason} - {:transmogrifier, {:reject, e}} -> - {:reject, e} + {:transmogrifier, {:reject, reason}} = e -> + log_fetch_error(id, e) + {:reject, reason} - {:transmogrifier, _} = e -> - {:error, e} + {:transmogrifier, reason} = e -> + log_fetch_error(id, e) + {:error, reason} {:object, data, nil} -> reinject_object(%Object{}, data) @@ -96,14 +101,21 @@ defmodule Pleroma.Object.Fetcher do {:fetch_object, %Object{} = object} -> {:ok, object} - {:fetch, {:error, error}} -> - {:error, error} + {:fetch, {:error, reason}} = e -> + log_fetch_error(id, e) + {:error, reason} e -> - e + log_fetch_error(id, e) + {:error, e} end end + defp log_fetch_error(id, error) do + Logger.metadata(object: id) + Logger.error("Object rejected while fetching #{id} #{inspect(error)}") + end + defp prepare_activity_params(data) do %{ "type" => "Create", @@ -117,26 +129,6 @@ defmodule Pleroma.Object.Fetcher do |> Maps.put_if_present("bcc", data["bcc"]) end - def fetch_object_from_id!(id, options \\ []) do - with {:ok, object} <- fetch_object_from_id(id, options) do - object - else - {:error, %Tesla.Mock.Error{}} -> - nil - - {: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 - end - end - defp make_signature(id, date) do uri = URI.parse(id) @@ -227,8 +219,11 @@ defmodule Pleroma.Object.Fetcher do {:error, {:content_type, nil}} end + {:ok, %{status: code}} when code in [401, 403] -> + {:error, :forbidden} + {:ok, %{status: code}} when code in [404, 410] -> - {:error, "Object has been deleted"} + {:error, :not_found} {:error, e} -> {:error, e} diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex @@ -61,15 +61,16 @@ defmodule Pleroma.Pagination do |> Repo.all() end - @spec paginate(Ecto.Query.t(), map(), type(), atom() | nil) :: [Ecto.Schema.t()] - def paginate(query, options, method \\ :keyset, table_binding \\ nil) - - def paginate(list, options, _method, _table_binding) when is_list(list) do + @spec paginate_list(list(), keyword()) :: list() + def paginate_list(list, options) do offset = options[:offset] || 0 limit = options[:limit] || 0 Enum.slice(list, offset, limit) end + @spec paginate(Ecto.Query.t(), map(), type(), atom() | nil) :: [Ecto.Schema.t()] + def paginate(query, options, method \\ :keyset, table_binding \\ nil) + def paginate(query, options, :keyset, table_binding) do query |> restrict(:min_id, options, table_binding) diff --git a/lib/pleroma/password/pbkdf2.ex b/lib/pleroma/password/pbkdf2.ex @@ -28,7 +28,7 @@ defmodule Pleroma.Password.Pbkdf2 do iterations = String.to_integer(iterations) - digest = String.to_atom(digest) + digest = String.to_existing_atom(digest) binary_hash = KeyGenerator.generate(password, salt, digest: digest, iterations: iterations, length: 64) diff --git a/lib/pleroma/release_tasks.ex b/lib/pleroma/release_tasks.ex @@ -55,12 +55,6 @@ defmodule Pleroma.ReleaseTasks do {:error, term} when is_binary(term) -> IO.puts(:stderr, "The database for #{inspect(@repo)} couldn't be created: #{term}") - - {:error, term} -> - IO.puts( - :stderr, - "The database for #{inspect(@repo)} couldn't be created: #{inspect(term)}" - ) end end end diff --git a/lib/pleroma/report_note.ex b/lib/pleroma/report_note.ex @@ -23,8 +23,8 @@ defmodule Pleroma.ReportNote do timestamps() end - @spec create(FlakeId.Ecto.CompatType.t(), FlakeId.Ecto.CompatType.t(), String.t()) :: - {:ok, ReportNote.t()} | {:error, Changeset.t()} + @spec create(Ecto.UUID.t(), Ecto.UUID.t(), String.t()) :: + {:ok, ReportNote.t()} | {:error, Ecto.Changeset.t()} def create(user_id, activity_id, content) do attrs = %{ user_id: user_id, @@ -38,8 +38,8 @@ defmodule Pleroma.ReportNote do |> Repo.insert() end - @spec destroy(FlakeId.Ecto.CompatType.t()) :: - {:ok, ReportNote.t()} | {:error, Changeset.t()} + @spec destroy(Ecto.UUID.t()) :: + {:ok, ReportNote.t()} | {:error, Ecto.Changeset.t()} def destroy(id) do from(r in ReportNote, where: r.id == ^id) |> Repo.one() diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex @@ -8,7 +8,7 @@ defmodule Pleroma.ReverseProxy do ~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-length content-type content-disposition content-encoding) ++ + ~w(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] @@ -81,16 +81,16 @@ defmodule Pleroma.ReverseProxy do import Plug.Conn @type option() :: - {:max_read_duration, :timer.time() | :infinity} + {:max_read_duration, non_neg_integer() | :infinity} | {:max_body_length, non_neg_integer() | :infinity} - | {:failed_request_ttl, :timer.time() | :infinity} - | {:http, []} + | {:failed_request_ttl, non_neg_integer() | :infinity} + | {:http, keyword()} | {:req_headers, [{String.t(), String.t()}]} | {:resp_headers, [{String.t(), String.t()}]} - | {:inline_content_types, boolean() | [String.t()]} + | {:inline_content_types, boolean() | list(String.t())} | {:redirect_on_failure, boolean()} - @spec call(Plug.Conn.t(), url :: String.t(), [option()]) :: Plug.Conn.t() + @spec call(Plug.Conn.t(), String.t(), list(option())) :: Plug.Conn.t() def call(_conn, _url, _opts \\ []) def call(conn = %{method: method}, url, opts) when method in @methods do @@ -388,8 +388,6 @@ defmodule Pleroma.ReverseProxy do defp body_size_constraint(_, _), do: :ok - defp check_read_duration(nil = _duration, max), do: check_read_duration(@max_read_duration, max) - defp check_read_duration(duration, max) when is_integer(duration) and is_integer(max) and max > 0 do if duration > max do @@ -407,10 +405,6 @@ defmodule Pleroma.ReverseProxy do {:ok, previous_duration + duration} end - defp increase_read_duration(_) do - {:ok, :no_duration_limit, :no_duration_limit} - end - defp client, do: Pleroma.ReverseProxy.Client.Wrapper defp track_failed_url(url, error, opts) do diff --git a/lib/pleroma/search/search_backend.ex b/lib/pleroma/search/search_backend.ex @@ -17,8 +17,8 @@ defmodule Pleroma.Search.SearchBackend do Remove the object from the index. Just the object, as opposed to the whole activity, is passed, since the object - is what contains the actual content and there is no need for fitlering when removing + is what contains the actual content and there is no need for filtering when removing from index. """ - @callback remove_from_index(object :: Pleroma.Object.t()) :: {:ok, any()} | {:error, any()} + @callback remove_from_index(object :: Pleroma.Object.t()) :: :ok | {:error, any()} end diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex @@ -27,7 +27,7 @@ defmodule Pleroma.Signature do _ -> case Pleroma.Web.WebFinger.finger(maybe_ap_id) do - %{"ap_id" => ap_id} -> {:ok, ap_id} + {:ok, %{"ap_id" => ap_id}} -> {:ok, ap_id} _ -> {:error, maybe_ap_id} end end diff --git a/lib/pleroma/telemetry/logger.ex b/lib/pleroma/telemetry/logger.ex @@ -59,7 +59,7 @@ defmodule Pleroma.Telemetry.Logger do _, _ ) do - Logger.error(fn -> + Logger.debug(fn -> "Connection pool had to refuse opening a connection to #{key} due to connection limit exhaustion" end) end @@ -81,7 +81,7 @@ defmodule Pleroma.Telemetry.Logger do %{key: key, protocol: :http}, _ ) do - Logger.info(fn -> + Logger.debug(fn -> "Pool worker for #{key}: #{length(clients)} clients are using an HTTP1 connection at the same time, head-of-line blocking might occur." end) end diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex @@ -51,6 +51,7 @@ defmodule Pleroma.Upload do | {:size_limit, nil | non_neg_integer()} | {:uploader, module()} | {:filters, [module()]} + | {:actor, String.t()} @type t :: %__MODULE__{ id: String.t(), @@ -86,7 +87,7 @@ defmodule Pleroma.Upload do end end - @spec store(source, options :: [option()]) :: {:ok, Map.t()} | {:error, any()} + @spec store(source, options :: [option()]) :: {:ok, map()} | {:error, any()} @doc "Store a file. If using a `Plug.Upload{}` as the source, be sure to use `Majic.Plug` to ensure its content_type and filename is correct." def store(upload, opts \\ []) do opts = get_opts(opts) @@ -175,7 +176,7 @@ defmodule Pleroma.Upload do defp prepare_upload(%{img: "data:image/" <> image_data}, opts) do parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data) data = Base.decode64!(parsed["data"], ignore: :whitespace) - hash = Base.encode16(:crypto.hash(:sha256, data), lower: true) + hash = Base.encode16(:crypto.hash(:sha256, data), case: :upper) with :ok <- check_binary_size(data, opts.size_limit), tmp_path <- tempfile_for_image(data), diff --git a/lib/pleroma/uploaders/uploader.ex b/lib/pleroma/uploaders/uploader.ex @@ -5,8 +5,6 @@ defmodule Pleroma.Uploaders.Uploader do import Pleroma.Web.Gettext - @mix_env Mix.env() - @moduledoc """ Defines the contract to put and get an uploaded file to any backend. """ @@ -40,7 +38,7 @@ defmodule Pleroma.Uploaders.Uploader do @callback delete_file(file :: String.t()) :: :ok | {:error, String.t()} - @callback http_callback(Plug.Conn.t(), Map.t()) :: + @callback http_callback(Plug.Conn.t(), map()) :: {:ok, Plug.Conn.t()} | {:ok, Plug.Conn.t(), file_spec()} | {:error, Plug.Conn.t(), String.t()} @@ -75,10 +73,5 @@ defmodule Pleroma.Uploaders.Uploader do end end - defp callback_timeout do - case @mix_env do - :test -> 1_000 - _ -> 30_000 - end - end + defp callback_timeout, do: Application.get_env(:pleroma, __MODULE__)[:timeout] end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex @@ -8,6 +8,7 @@ defmodule Pleroma.User do import Ecto.Changeset import Ecto.Query import Ecto, only: [assoc: 2] + import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1] alias Ecto.Multi alias Pleroma.Activity @@ -39,6 +40,7 @@ defmodule Pleroma.User do alias Pleroma.Workers.BackgroundWorker require Logger + require Pleroma.Constants @type t :: %__MODULE__{} @type account_status :: @@ -579,7 +581,7 @@ defmodule Pleroma.User do |> validate_format(:nickname, local_nickname_regex()) |> validate_length(:bio, max: bio_limit) |> validate_length(:name, min: 1, max: name_limit) - |> validate_inclusion(:actor_type, ["Person", "Service"]) + |> validate_inclusion(:actor_type, Pleroma.Constants.allowed_user_actor_types()) |> put_fields() |> put_emoji() |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)}) @@ -595,9 +597,23 @@ defmodule Pleroma.User do defp put_fields(changeset) do if raw_fields = get_change(changeset, :raw_fields) do + old_fields = changeset.data.raw_fields + raw_fields = raw_fields |> Enum.filter(fn %{"name" => n} -> n != "" end) + |> Enum.map(fn field -> + previous = + old_fields + |> Enum.find(fn %{"value" => value} -> field["value"] == value end) + + if previous && Map.has_key?(previous, "verified_at") do + field + |> Map.put("verified_at", previous["verified_at"]) + else + field + end + end) fields = raw_fields @@ -671,7 +687,7 @@ defmodule Pleroma.User do |> validate_inclusion(:actor_type, ["Person", "Service"]) end - @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()} + @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} def update_as_admin(user, params) do params = Map.put(params, "password_confirmation", params["password"]) changeset = update_as_admin_changeset(user, params) @@ -692,7 +708,7 @@ defmodule Pleroma.User do |> put_change(:password_reset_pending, false) end - @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()} + @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} def reset_password(%User{} = user, params) do reset_password(user, user, params) end @@ -1010,7 +1026,7 @@ defmodule Pleroma.User do def maybe_send_confirmation_email(_), do: {:ok, :noop} - @spec send_confirmation_email(Uset.t()) :: User.t() + @spec send_confirmation_email(User.t()) :: User.t() def send_confirmation_email(%User{} = user) do user |> Pleroma.Emails.UserEmail.account_confirmation_email() @@ -1047,7 +1063,8 @@ defmodule Pleroma.User do def needs_update?(_), do: true - @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()} + @spec maybe_direct_follow(User.t(), User.t()) :: + {:ok, User.t(), User.t()} | {:error, String.t()} # "Locked" (self-locked) users demand explicit authorization of follow requests def maybe_direct_follow(%User{} = follower, %User{local: true, is_locked: true} = followed) do @@ -1198,6 +1215,10 @@ defmodule Pleroma.User do def update_and_set_cache(changeset) do with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do + if get_change(changeset, :raw_fields) do + BackgroundWorker.enqueue("verify_fields_links", %{"user_id" => user.id}) + end + set_cache(user) end end @@ -1782,14 +1803,17 @@ defmodule Pleroma.User do BackgroundWorker.enqueue("user_activation", %{"user_id" => user.id, "status" => status}) end - @spec set_activation([User.t()], boolean()) :: {:ok, User.t()} | {:error, Changeset.t()} + @spec set_activation([User.t()], boolean()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} def set_activation(users, status) when is_list(users) do Repo.transaction(fn -> - for user <- users, do: set_activation(user, status) + for user <- users do + {:ok, user} = set_activation(user, status) + user + end end) end - @spec set_activation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()} + @spec set_activation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} def set_activation(%User{} = user, status) do with {:ok, user} <- set_activation_status(user, status) do user @@ -1867,7 +1891,7 @@ defmodule Pleroma.User do |> update_and_set_cache() end - @spec purge_user_changeset(User.t()) :: Changeset.t() + @spec purge_user_changeset(User.t()) :: Ecto.Changeset.t() def purge_user_changeset(user) do # "Right to be forgotten" # https://gdpr.eu/right-to-be-forgotten/ @@ -1970,8 +1994,45 @@ defmodule Pleroma.User do maybe_delete_from_db(user) end + def perform(:verify_fields_links, user) do + profile_urls = [user.ap_id] + + fields = + user.raw_fields + |> Enum.map(&verify_field_link(&1, profile_urls)) + + changeset = + user + |> update_changeset(%{raw_fields: fields}) + + with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do + set_cache(user) + end + end + def perform(:set_activation_async, user, status), do: set_activation(user, status) + defp verify_field_link(field, profile_urls) do + verified_at = + with %{"value" => value} <- field, + {:verified_at, nil} <- {:verified_at, Map.get(field, "verified_at")}, + %{scheme: scheme, userinfo: nil, host: host} + when not_empty_string(host) and scheme in ["http", "https"] <- + URI.parse(value), + {:not_idn, true} <- {:not_idn, to_string(:idna.encode(host)) == host}, + "me" <- Pleroma.Web.RelMe.maybe_put_rel_me(value, profile_urls) do + CommonUtils.to_masto_date(NaiveDateTime.utc_now()) + else + {:verified_at, value} when not_empty_string(value) -> + value + + _ -> + nil + end + + Map.put(field, "verified_at", verified_at) + end + @spec external_users_query() :: Ecto.Query.t() def external_users_query do User.Query.build(%{ @@ -2252,7 +2313,7 @@ defmodule Pleroma.User do if String.contains?(user.nickname, "@") do user.nickname else - %{host: host} = URI.parse(user.ap_id) + host = Pleroma.Web.WebFinger.host() user.nickname <> "@" <> host end end @@ -2358,7 +2419,7 @@ defmodule Pleroma.User do updated_user end - @spec set_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()} + @spec set_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} def set_confirmation(%User{} = user, bool) do user |> confirmation_changeset(set_confirmation: bool) @@ -2402,9 +2463,9 @@ defmodule Pleroma.User do defp put_password_hash(changeset), do: changeset - def is_internal_user?(%User{nickname: nil}), do: true - def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true - def is_internal_user?(_), do: false + def internal?(%User{nickname: nil}), do: true + def internal?(%User{local: true, nickname: "internal." <> _}), do: true + def internal?(_), do: false # A hack because user delete activities have a fake id for whatever reason # TODO: Get rid of this @@ -2536,7 +2597,7 @@ defmodule Pleroma.User do |> update_and_set_cache() end - @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t() + @spec confirmation_changeset(User.t(), keyword()) :: Ecto.Changeset.t() def confirmation_changeset(user, set_confirmation: confirmed?) do params = if confirmed? do @@ -2554,9 +2615,9 @@ defmodule Pleroma.User do cast(user, params, [:is_confirmed, :confirmation_token]) end - @spec approval_changeset(User.t(), keyword()) :: Changeset.t() - def approval_changeset(user, set_approval: approved?) do - cast(user, %{is_approved: approved?}, [:is_approved]) + @spec approval_changeset(Ecto.Changeset.t(), keyword()) :: Ecto.Changeset.t() + def approval_changeset(changeset, set_approval: approved?) do + cast(changeset, %{is_approved: approved?}, [:is_approved]) end @spec add_pinned_object_id(User.t(), String.t()) :: {:ok, User.t()} | {:error, term()} @@ -2659,10 +2720,11 @@ defmodule Pleroma.User do # - display name def sanitize_html(%User{} = user, filter) do fields = - Enum.map(user.fields, fn %{"name" => name, "value" => value} -> + Enum.map(user.fields, fn %{"name" => name, "value" => value} = fields -> %{ "name" => name, - "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly) + "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly), + "verified_at" => Map.get(fields, "verified_at") } end) diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex @@ -22,6 +22,8 @@ defmodule Pleroma.User.Backup do alias Pleroma.Web.ActivityPub.UserView alias Pleroma.Workers.BackupWorker + @type t :: %__MODULE__{} + schema "backups" do field(:content_type, :string) field(:file_name, :string) @@ -194,7 +196,15 @@ defmodule Pleroma.User.Backup do end end - @files ['actor.json', 'outbox.json', 'likes.json', 'bookmarks.json'] + @files [ + 'actor.json', + 'outbox.json', + 'likes.json', + 'bookmarks.json', + 'followers.json', + 'following.json' + ] + @spec export(Pleroma.User.Backup.t(), pid()) :: {:ok, String.t()} | :error def export(%__MODULE__{} = backup, caller_pid) do backup = Repo.preload(backup, :user) dir = backup_tempdir(backup) @@ -204,9 +214,13 @@ defmodule Pleroma.User.Backup do :ok <- statuses(dir, backup.user, caller_pid), :ok <- likes(dir, backup.user, caller_pid), :ok <- bookmarks(dir, backup.user, caller_pid), - {:ok, zip_path} <- :zip.create(String.to_charlist(dir <> ".zip"), @files, cwd: dir), + :ok <- followers(dir, backup.user, caller_pid), + :ok <- following(dir, backup.user, caller_pid), + {:ok, zip_path} <- :zip.create(backup.file_name, @files, cwd: dir), {:ok, _} <- File.rm_rf(dir) do - {:ok, to_string(zip_path)} + {:ok, zip_path} + else + _ -> :error end end @@ -352,6 +366,16 @@ defmodule Pleroma.User.Backup do caller_pid ) end + + defp followers(dir, user, caller_pid) do + User.get_followers_query(user) + |> write(dir, "followers", fn a -> {:ok, a.ap_id} end, caller_pid) + end + + defp following(dir, user, caller_pid) do + User.get_friends_query(user) + |> write(dir, "following", fn a -> {:ok, a.ap_id} end, caller_pid) + end end defmodule Pleroma.User.Backup.ProcessorAPI do @@ -382,6 +406,8 @@ defmodule Pleroma.User.Backup.Processor do [:file_size, :processed, :state] ) |> Repo.update() + else + e -> {:error, e} end end end diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex @@ -22,7 +22,7 @@ defmodule Pleroma.User.Query do - pass non empty string - e.g. Pleroma.User.Query.build(%{email: "email@example.com"}) - *contains criteria* - - add field to @containns_criteria list + - add field to @contains_criteria list - pass values list - e.g. Pleroma.User.Query.build(%{ap_id: ["http://ap_id1", "http://ap_id2"]}) """ @@ -71,7 +71,7 @@ defmodule Pleroma.User.Query do @equal_criteria [:email] @contains_criteria [:ap_id, :nickname] - @spec build(Query.t(), criteria()) :: Query.t() + @spec build(Ecto.Query.t(), criteria()) :: Ecto.Query.t() def build(query \\ base_query(), criteria) do prepare_query(query, criteria) end diff --git a/lib/pleroma/user_invite_token.ex b/lib/pleroma/user_invite_token.ex @@ -64,7 +64,7 @@ defmodule Pleroma.UserInviteToken do end @spec update_invite(UserInviteToken.t(), map()) :: - {:ok, UserInviteToken.t()} | {:error, Changeset.t()} + {:ok, UserInviteToken.t()} | {:error, Ecto.Changeset.t()} def update_invite(invite, changes) do change(invite, changes) |> Repo.update() end diff --git a/lib/pleroma/user_relationship.ex b/lib/pleroma/user_relationship.ex @@ -14,6 +14,8 @@ defmodule Pleroma.UserRelationship do alias Pleroma.User alias Pleroma.UserRelationship + @type t :: %__MODULE__{} + schema "user_relationships" do belongs_to(:source, User, type: FlakeId.Ecto.CompatType) belongs_to(:target, User, type: FlakeId.Ecto.CompatType) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -74,22 +74,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp check_remote_limit(_), do: true def increase_note_count_if_public(actor, object) do - if is_public?(object), do: User.increase_note_count(actor), else: {:ok, actor} + if public?(object), do: User.increase_note_count(actor), else: {:ok, actor} end def decrease_note_count_if_public(actor, object) do - if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor} + if public?(object), do: User.decrease_note_count(actor), else: {:ok, actor} end def update_last_status_at_if_public(actor, object) do - if is_public?(object), do: User.update_last_status_at(actor), else: {:ok, actor} + if public?(object), do: User.update_last_status_at(actor), else: {:ok, actor} end defp increase_replies_count_if_reply(%{ "object" => %{"inReplyTo" => reply_ap_id} = object, "type" => "Create" }) do - if is_public?(object) do + if public?(object) do Object.increase_replies_count(reply_ap_id) end end @@ -100,7 +100,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do "object" => %{"quoteUrl" => quote_ap_id} = object, "type" => "Create" }) do - if is_public?(object) do + if public?(object) do Object.increase_quotes_count(quote_ap_id) end end @@ -319,6 +319,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do {:ok, _actor} <- update_last_status_at_if_public(actor, activity), _ <- notify_and_stream(activity), :ok <- maybe_schedule_poll_notifications(activity), + :ok <- maybe_handle_group_posts(activity), :ok <- maybe_federate(activity) do {:ok, activity} else @@ -498,7 +499,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end @spec fetch_latest_direct_activity_id_for_context(String.t(), keyword() | map()) :: - FlakeId.Ecto.CompatType.t() | nil + Ecto.UUID.t() | nil def fetch_latest_direct_activity_id_for_context(context, opts \\ %{}) do context |> fetch_activities_for_context_query(Map.merge(%{skip_preload: true}, opts)) @@ -1707,9 +1708,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do Fetcher.fetch_and_contain_remote_object_from_id(first) do {:ok, false} else - {:error, {:ok, %{status: code}}} when code in [401, 403] -> {:ok, true} - {:error, _} = e -> e - e -> {:error, e} + {:error, _} -> {:ok, true} end end diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do This module encodes our addressing policies and general shape of our objects. """ + alias Pleroma.Activity alias Pleroma.Emoji alias Pleroma.Object alias Pleroma.User @@ -131,7 +132,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do def emoji_react(actor, object, emoji) do with {:ok, data, meta} <- object_action(actor, object) do data = - if Emoji.is_unicode_emoji?(emoji) do + if Emoji.unicode?(emoji) do unicode_emoji_react(object, data, emoji) else custom_emoji_react(object, data, emoji) @@ -347,7 +348,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do actor.ap_id == Relay.ap_id() -> [actor.follower_address] - public? and Visibility.is_local_public?(object) -> + public? and Visibility.local_public?(object) -> [actor.follower_address, object.data["actor"], Utils.as_local_public()] public? -> @@ -375,7 +376,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do # Address the actor of the object, and our actor's follower collection if the post is public. to = - if Visibility.is_public?(object) do + if Visibility.public?(object) do [actor.follower_address, object.data["actor"]] else [object.data["actor"]] diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF do @@ -139,7 +139,16 @@ defmodule Pleroma.Web.ActivityPub.MRF do @spec subdomains_regex([String.t()]) :: [Regex.t()] def subdomains_regex(domains) when is_list(domains) do - for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$)i + for domain <- domains do + try do + target = String.replace(domain, "*.", "(.*\\.)*") + ~r<^#{target}$>i + rescue + e -> + Logger.error("MRF: Invalid subdomain Regex: #{domain}") + reraise e, __STACKTRACE__ + end + end end @spec subdomain_match?([Regex.t()], String.t()) :: boolean() diff --git a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex @@ -56,8 +56,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do nick_score + name_score + actor_type_score end - defp determine_if_followbot(_), do: 0.0 - defp bot_allowed?(%{"object" => target}, bot_actor) do %User{} = user = normalize_by_ap_id(target) diff --git a/lib/pleroma/web/activity_pub/mrf/force_mention.ex b/lib/pleroma/web/activity_pub/mrf/force_mention.ex @@ -0,0 +1,59 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.ForceMention do + require Pleroma.Constants + + alias Pleroma.Config + alias Pleroma.Object + alias Pleroma.User + + @behaviour Pleroma.Web.ActivityPub.MRF.Policy + + defp get_author(url) do + with %Object{data: %{"actor" => actor}} <- Object.normalize(url, fetch: false), + %User{ap_id: ap_id, nickname: nickname} <- User.get_cached_by_ap_id(actor) do + %{"type" => "Mention", "href" => ap_id, "name" => "@#{nickname}"} + else + _ -> nil + end + end + + defp prepend_author(tags, _, false), do: tags + + defp prepend_author(tags, nil, _), do: tags + + defp prepend_author(tags, url, _) do + actor = get_author(url) + + if not is_nil(actor) do + [actor | tags] + else + tags + end + end + + @impl true + def filter(%{"type" => "Create", "object" => %{"tag" => tag} = object} = activity) do + tag = + tag + |> prepend_author( + object["inReplyTo"], + Config.get([:mrf_force_mention, :mention_parent, true]) + ) + |> prepend_author( + object["quoteUrl"], + Config.get([:mrf_force_mention, :mention_quoted, true]) + ) + |> Enum.uniq() + + {:ok, put_in(activity["object"]["tag"], tag)} + end + + @impl true + def filter(object), do: {:ok, object} + + @impl true + def describe, do: {:ok, %{}} +end diff --git a/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex b/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do alias Pleroma.Object @moduledoc """ - Reject, TWKN-remove or Set-Sensitive messsages with specific hashtags (without the leading #) + Reject, TWKN-remove or Set-Sensitive messages with specific hashtags (without the leading #) Note: This MRF Policy is always enabled, if you want to disable it you have to set empty lists. """ @@ -84,7 +84,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do if hashtags != [] do with {:ok, message} <- check_reject(message, hashtags), {:ok, message} <- - (if "type" == "Create" do + (if type == "Create" do check_ftl_removal(message, hashtags) else {:ok, message} diff --git a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex @@ -62,7 +62,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do key: :mrf_inline_quote, related_policy: "Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy", label: "MRF Inline Quote Policy", - type: :group, description: "Force quote url to appear in post content.", children: [ %{ diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex @@ -10,15 +10,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do @moduledoc "Reject or Word-Replace messages with a keyword or regex" @behaviour Pleroma.Web.ActivityPub.MRF.Policy - defp string_matches?(string, _) when not is_binary(string) do - false - end defp string_matches?(string, pattern) when is_binary(pattern) do String.contains?(string, pattern) end - defp string_matches?(string, pattern) do + defp string_matches?(string, %Regex{} = pattern) do String.match?(string, pattern) end diff --git a/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex @@ -10,9 +10,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do @impl true def filter(%{"actor" => actor} = object) do - with true <- is_local?(actor), - true <- is_eligible_type?(object), - true <- is_note?(object), + with true <- local?(actor), + true <- eligible_type?(object), + true <- note?(object), false <- has_attachment?(object), true <- only_mentions?(object) do {:reject, "[NoEmptyPolicy]"} @@ -24,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do def filter(object), do: {:ok, object} - defp is_local?(actor) do + defp local?(actor) do if actor |> String.starts_with?("#{Endpoint.url()}") do true else @@ -59,11 +59,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do defp only_mentions?(_), do: false - defp is_note?(%{"object" => %{"type" => "Note"}}), do: true - defp is_note?(_), do: false + defp note?(%{"object" => %{"type" => "Note"}}), do: true + defp note?(_), do: false - defp is_eligible_type?(%{"type" => type}) when type in ["Create", "Update"], do: true - defp is_eligible_type?(_), do: false + defp eligible_type?(%{"type" => type}) when type in ["Create", "Update"], do: true + defp eligible_type?(_), do: false @impl true def describe, do: {:ok, %{}} diff --git a/lib/pleroma/web/activity_pub/mrf/policy.ex b/lib/pleroma/web/activity_pub/mrf/policy.ex @@ -3,8 +3,8 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.Policy do - @callback filter(Map.t()) :: {:ok | :reject, Map.t()} - @callback describe() :: {:ok | :error, Map.t()} + @callback filter(map()) :: {:ok | :reject, map()} + @callback describe() :: {:ok | :error, map()} @callback config_description() :: %{ optional(:children) => [map()], key: atom(), diff --git a/lib/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy.ex @@ -28,7 +28,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy do tags = object["tag"] || [] if Enum.any?(tags, fn tag -> - CommonFixes.is_object_link_tag(tag) and tag["href"] == quote_url + CommonFixes.object_link_tag?(tag) and tag["href"] == quote_url end) do object else diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -34,7 +34,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do |> Path.basename() |> Path.extname() - file_path = Path.join(emoji_dir_path, shortcode <> (extension || ".png")) + extension = if extension == "", do: ".png", else: extension + + shortcode = Path.basename(shortcode) + file_path = Path.join(emoji_dir_path, shortcode <> extension) case File.write(file_path, response.body) do :ok -> @@ -76,6 +79,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do new_emojis = foreign_emojis |> Enum.reject(fn {shortcode, _url} -> shortcode in installed_emoji end) + |> Enum.reject(fn {shortcode, _url} -> String.contains?(shortcode, ["/", "\\"]) end) |> Enum.filter(fn {shortcode, _url} -> reject_emoji? = [:mrf_steal_emoji, :rejected_shortcodes] diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex @@ -173,6 +173,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do {:object_validation, e} -> e + + {:error, %Ecto.Changeset{} = e} -> + {:error, e} end end diff --git a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex @@ -82,7 +82,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do object when is_binary(object) <- get_field(cng, :object), %User{} = actor <- User.get_cached_by_ap_id(actor), %Object{} = object <- Object.get_cached_by_ap_id(object), - false <- Visibility.is_public?(object) do + false <- Visibility.public?(object) do same_actor = object.data["actor"] == actor.ap_id recipients = get_field(cng, :to) ++ get_field(cng, :cc) local_public = Utils.as_local_public() 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 @@ -57,6 +57,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do |> Map.put("attachment", attachment) end + def fix_attachment(%{"attachment" => attachment} = data) when attachment == [] do + data + |> Map.drop(["attachment"]) + end + def fix_attachment(data), do: data def changeset(struct, data) do diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do alias Pleroma.EctoType.ActivityPub.ObjectValidators + alias Pleroma.Maps alias Pleroma.Object alias Pleroma.Object.Containment alias Pleroma.User @@ -24,6 +25,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do end def fix_object_defaults(data) do + data = Maps.filter_empty_values(data) + context = Utils.maybe_create_context( data["context"] || data["conversation"] || data["inReplyTo"] || data["id"] @@ -99,7 +102,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do end def fix_quote_url(%{"tag" => [_ | _] = tags} = data) do - tag = Enum.find(tags, &is_object_link_tag/1) + tag = Enum.find(tags, &object_link_tag?/1) if not is_nil(tag) do data @@ -112,7 +115,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do def fix_quote_url(data), do: data # https://codeberg.org/fediverse/fep/src/branch/main/fep/e232/fep-e232.md - def is_object_link_tag(%{ + def object_link_tag?(%{ "type" => "Link", "mediaType" => media_type, "href" => href @@ -121,5 +124,5 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do true end - def is_object_link_tag(_), do: false + def object_link_tag?(_), do: false end diff --git a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex @@ -74,10 +74,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do new_emoji = Pleroma.Emoji.fully_qualify_emoji(emoji) cond do - Pleroma.Emoji.is_unicode_emoji?(emoji) -> + Pleroma.Emoji.unicode?(emoji) -> data - Pleroma.Emoji.is_unicode_emoji?(new_emoji) -> + Pleroma.Emoji.unicode?(new_emoji) -> data |> Map.put("content", new_emoji) true -> @@ -90,7 +90,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do defp validate_emoji(cng) do content = get_field(cng, :content) - if Emoji.is_unicode_emoji?(content) || Emoji.is_custom_emoji?(content) do + if Emoji.unicode?(content) || Emoji.custom?(content) do cng else cng @@ -101,7 +101,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do defp maybe_validate_tag_presence(cng) do content = get_field(cng, :content) - if Emoji.is_unicode_emoji?(content) do + if Emoji.unicode?(content) do cng else tag = get_field(cng, :tag) diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex @@ -29,6 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do field(:closed, ObjectValidators.DateTime) field(:voters, {:array, ObjectValidators.ObjectID}, default: []) + field(:nonAnonymous, :boolean) embeds_many(:anyOf, QuestionOptionsValidator) embeds_many(:oneOf, QuestionOptionsValidator) end diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex @@ -62,7 +62,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do with {:ok, local} <- Keyword.fetch(meta, :local) do do_not_federate = meta[:do_not_federate] || !config().get([:instance, :federating]) - if !do_not_federate and local and not Visibility.is_local_public?(activity) do + if !do_not_federate and local and not Visibility.local_public?(activity) do activity = if object = Keyword.get(meta, :object_data) do %{activity | data: Map.put(activity.data, "object", object)} diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex @@ -13,13 +13,12 @@ defmodule Pleroma.Web.ActivityPub.Publisher do alias Pleroma.User alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Workers.PublisherWorker require Pleroma.Constants import Pleroma.Web.ActivityPub.Visibility - @behaviour Pleroma.Web.Federator.Publisher - require Logger @moduledoc """ @@ -27,9 +26,47 @@ defmodule Pleroma.Web.ActivityPub.Publisher do """ @doc """ + Enqueue publishing a single activity. + """ + @spec enqueue_one(map(), Keyword.t()) :: {:ok, %Oban.Job{}} + def enqueue_one(%{} = params, worker_args \\ []) do + PublisherWorker.enqueue( + "publish_one", + %{"params" => params}, + worker_args + ) + end + + @doc """ + Gathers a set of remote users given an IR envelope. + """ + def remote_users(%User{id: user_id}, %{data: %{"to" => to} = data}) do + cc = Map.get(data, "cc", []) + + bcc = + data + |> Map.get("bcc", []) + |> Enum.reduce([], fn ap_id, bcc -> + case Pleroma.List.get_by_ap_id(ap_id) do + %Pleroma.List{user_id: ^user_id} = list -> + {:ok, following} = Pleroma.List.get_following(list) + bcc ++ Enum.map(following, & &1.ap_id) + + _ -> + bcc + end + end) + + [to, cc, bcc] + |> Enum.concat() + |> Enum.map(&User.get_cached_by_ap_id/1) + |> Enum.filter(fn user -> user && !user.local end) + end + + @doc """ Determine if an activity can be represented by running it through Transmogrifier. """ - def is_representable?(%Activity{} = activity) do + def representable?(%Activity{} = activity) do with {:ok, _data} <- Transmogrifier.prepare_outgoing(activity.data) do true else @@ -80,9 +117,27 @@ defmodule Pleroma.Web.ActivityPub.Publisher do result else - {_post_result, response} -> + {_post_result, %{status: code} = response} = e -> unless params[:unreachable_since], do: Instances.set_unreachable(inbox) - {:error, response} + Logger.metadata(activity: id, inbox: inbox, status: code) + Logger.error("Publisher failed to inbox #{inbox} with status #{code}") + + case response do + %{status: 403} -> {:discard, :forbidden} + %{status: 404} -> {:discard, :not_found} + %{status: 410} -> {:discard, :not_found} + _ -> {:error, e} + end + + {:error, :pool_full} -> + Logger.debug("Publisher snoozing worker job due to full connection pool") + {:snooze, 30} + + e -> + unless params[:unreachable_since], do: Instances.set_unreachable(inbox) + Logger.metadata(activity: id, inbox: inbox) + Logger.error("Publisher failed to inbox #{inbox} #{inspect(e)}") + {:error, e} end end @@ -103,19 +158,18 @@ defmodule Pleroma.Web.ActivityPub.Publisher do end end - defp should_federate?(inbox, public) do - if public do - true - else - %{host: host} = URI.parse(inbox) + def should_federate?(nil, _), do: false + def should_federate?(_, true), do: true - quarantined_instances = - Config.get([:instance, :quarantined_instances], []) - |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples() - |> Pleroma.Web.ActivityPub.MRF.subdomains_regex() + def should_federate?(inbox, _) do + %{host: host} = URI.parse(inbox) - !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host) - end + quarantined_instances = + Config.get([:instance, :quarantined_instances], []) + |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples() + |> Pleroma.Web.ActivityPub.MRF.subdomains_regex() + + !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host) end @spec recipients(User.t(), Activity.t()) :: [[User.t()]] @@ -138,7 +192,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do [] end - mentioned = Pleroma.Web.Federator.Publisher.remote_users(actor, activity) + mentioned = remote_users(actor, activity) non_mentioned = (followers ++ fetchers) -- mentioned [mentioned, non_mentioned] @@ -195,7 +249,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do def publish(%User{} = actor, %{data: %{"bcc" => bcc}} = activity) when is_list(bcc) and bcc != [] do - public = is_public?(activity) + public = public?(activity) {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) [priority_recipients, recipients] = recipients(actor, activity) @@ -204,7 +258,10 @@ defmodule Pleroma.Web.ActivityPub.Publisher do [priority_recipients, recipients] |> Enum.map(fn recipients -> recipients - |> Enum.map(fn actor -> actor.inbox end) + |> Enum.map(fn %User{} = user -> + determine_inbox(activity, user) + end) + |> Enum.uniq() |> Enum.filter(fn inbox -> should_federate?(inbox, public) end) |> Instances.filter_reachable() end) @@ -223,7 +280,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do |> Map.put("cc", cc) |> Jason.encode!() - Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{ + __MODULE__.enqueue_one(%{ inbox: inbox, json: json, actor_id: actor.id, @@ -237,7 +294,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do # Publishes an activity to all relevant peers. def publish(%User{} = actor, %Activity{} = activity) do - public = is_public?(activity) + public = public?(activity) if public && Config.get([:instance, :allow_relay]) do Logger.debug(fn -> "Relaying #{activity.data["id"]} out" end) @@ -251,7 +308,10 @@ defmodule Pleroma.Web.ActivityPub.Publisher do recipients(actor, activity) |> Enum.map(fn recipients -> recipients - |> Enum.map(fn actor -> actor.inbox end) + |> Enum.map(fn %User{} = user -> + determine_inbox(activity, user) + end) + |> Enum.uniq() |> Enum.filter(fn inbox -> should_federate?(inbox, public) end) end) @@ -262,8 +322,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do inboxes |> Instances.filter_reachable() |> Enum.each(fn {inbox, unreachable_since} -> - Pleroma.Web.Federator.Publisher.enqueue_one( - __MODULE__, + __MODULE__.enqueue_one( %{ inbox: inbox, json: json, diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex @@ -58,7 +58,7 @@ defmodule Pleroma.Web.ActivityPub.Relay do @spec publish(any()) :: {:ok, Activity.t()} | {:error, any()} def publish(%Activity{data: %{"type" => "Create"}} = activity) do with %User{} = user <- get_actor(), - true <- Visibility.is_public?(activity) do + true <- Visibility.public?(activity) do CommonAPI.repeat(activity.id, user) else error -> format_error(error) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex @@ -233,6 +233,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do Pleroma.Search.add_to_index(Map.put(activity, :object, object)) + Utils.maybe_handle_group_posts(activity) + meta = meta |> add_notifications(notifications) @@ -256,7 +258,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do Utils.add_announce_to_object(object, announced_object) - if !User.is_internal_user?(user) do + if !User.internal?(user) do Notification.create_notifications(object) ap_streamer().stream_out(object) @@ -302,9 +304,9 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do result = case deleted_object do %Object{} -> - with {:ok, deleted_object, _activity} <- Object.delete(deleted_object), + with {_, {:ok, deleted_object, _activity}} <- {:object, Object.delete(deleted_object)}, {_, actor} when is_binary(actor) <- {:actor, deleted_object.data["actor"]}, - %User{} = user <- User.get_cached_by_ap_id(actor) do + {_, %User{} = user} <- {:user, User.get_cached_by_ap_id(actor)} do User.remove_pinned_object_id(user, deleted_object.data["id"]) {:ok, user} = ActivityPub.decrease_note_count_if_public(user, deleted_object) @@ -326,6 +328,17 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do {:actor, _} -> @logger.error("The object doesn't have an actor: #{inspect(deleted_object)}") :no_object_actor + + {:user, _} -> + @logger.error( + "The object's actor could not be resolved to a user: #{inspect(deleted_object)}" + ) + + :no_object_user + + {:object, _} -> + @logger.error("The object could not be deleted: #{inspect(deleted_object)}") + {:error, object} end %User{} -> @@ -567,7 +580,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do def handle_undoing(object), do: {:error, ["don't know how to handle", object]} - @spec delete_object(Object.t()) :: :ok | {:error, Ecto.Changeset.t()} + @spec delete_object(Activity.t()) :: :ok | {:error, Ecto.Changeset.t()} defp delete_object(object) do with {:ok, _} <- Repo.delete(object), do: :ok end diff --git a/lib/pleroma/web/activity_pub/side_effects/handling.ex b/lib/pleroma/web/activity_pub/side_effects/handling.ex @@ -4,5 +4,5 @@ defmodule Pleroma.Web.ActivityPub.SideEffects.Handling do @callback handle(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()} - @callback handle_after_transaction(map()) :: map() + @callback handle_after_transaction(keyword()) :: keyword() end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -23,7 +23,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do import Ecto.Query - require Logger require Pleroma.Constants @doc """ @@ -155,8 +154,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> Map.put("context", replied_object.data["context"] || object["conversation"]) |> Map.drop(["conversation", "inReplyToAtomUri"]) else - e -> - Logger.warning("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}") + _ -> object end else @@ -181,8 +179,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do {:quoting?, _} -> object - e -> - Logger.warning("Couldn't fetch #{inspect(quote_url)}, error: #{inspect(e)}") + _ -> object end end @@ -782,7 +779,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> Object.normalize(fetch: false) data = - if Visibility.is_private?(object) && object.data["actor"] == ap_id do + if Visibility.private?(object) && object.data["actor"] == ap_id do data |> Map.put("object", object |> Map.get(:data) |> prepare_object) else data |> maybe_fix_object_url @@ -852,8 +849,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do relative_object do Map.put(data, "object", external_url) else - {:fetch, e} -> - Logger.error("Couldn't fetch #{object} #{inspect(e)}") + {:fetch, _} -> data _ -> diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex @@ -167,7 +167,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do with true <- Config.get!([:instance, :federating]), true <- type != "Block" || outgoing_blocks, - false <- Visibility.is_local_public?(activity) do + false <- Visibility.local_public?(activity) do Pleroma.Web.Federator.publish(activity) end @@ -277,7 +277,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do object_actor = User.get_cached_by_ap_id(object_actor_id) to = - if Visibility.is_public?(object) do + if Visibility.public?(object) do [actor.follower_address, object.data["actor"]] else [object.data["actor"]] @@ -780,10 +780,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do build_flag_object(object) nil -> - if %Object{} = object = Object.get_by_ap_id(id) do - build_flag_object(object) - else - %{"id" => id, "deleted" => true} + case Object.get_by_ap_id(id) do + %Object{} = object -> build_flag_object(object) + _ -> %{"id" => id, "deleted" => true} end end end @@ -939,4 +938,27 @@ defmodule Pleroma.Web.ActivityPub.Utils do |> where([a, object: o], fragment("(?)->>'type' = 'Answer'", o.data)) |> Repo.all() end + + def maybe_handle_group_posts(activity) do + poster = User.get_cached_by_ap_id(activity.actor) + + mentions = + activity.data["to"] + |> Enum.filter(&(&1 != activity.actor)) + + mentioned_local_groups = + User.get_all_by_ap_id(mentions) + |> Enum.filter(fn user -> + user.actor_type == "Group" and + user.local and + not User.blocks?(user, poster) + end) + + mentioned_local_groups + |> Enum.each(fn group -> + Pleroma.Web.CommonAPI.repeat(activity.id, group) + end) + + :ok + end end diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex @@ -11,28 +11,28 @@ defmodule Pleroma.Web.ActivityPub.Visibility do require Pleroma.Constants - @spec is_public?(Object.t() | Activity.t() | map()) :: boolean() - def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false - def is_public?(%Object{data: data}), do: is_public?(data) - def is_public?(%Activity{data: %{"type" => "Move"}}), do: true - def is_public?(%Activity{data: data}), do: is_public?(data) - def is_public?(%{"directMessage" => true}), do: false - - def is_public?(data) do + @spec public?(Object.t() | Activity.t() | map()) :: boolean() + def public?(%Object{data: %{"type" => "Tombstone"}}), do: false + def public?(%Object{data: data}), do: public?(data) + def public?(%Activity{data: %{"type" => "Move"}}), do: true + def public?(%Activity{data: data}), do: public?(data) + def public?(%{"directMessage" => true}), do: false + + def public?(data) do Utils.label_in_message?(Pleroma.Constants.as_public(), data) or Utils.label_in_message?(Utils.as_local_public(), data) end - def is_local_public?(%Object{data: data}), do: is_local_public?(data) - def is_local_public?(%Activity{data: data}), do: is_local_public?(data) + def local_public?(%Object{data: data}), do: local_public?(data) + def local_public?(%Activity{data: data}), do: local_public?(data) - def is_local_public?(data) do + def local_public?(data) do Utils.label_in_message?(Utils.as_local_public(), data) and not Utils.label_in_message?(Pleroma.Constants.as_public(), data) end - def is_private?(activity) do - with false <- is_public?(activity), + def private?(activity) do + with false <- public?(activity), %User{follower_address: follower_address} <- User.get_cached_by_ap_id(activity.data["actor"]) do follower_address in activity.data["to"] @@ -41,20 +41,20 @@ defmodule Pleroma.Web.ActivityPub.Visibility do end end - def is_announceable?(activity, user, public \\ true) do - is_public?(activity) || - (!public && is_private?(activity) && activity.data["actor"] == user.ap_id) + def announceable?(activity, user, public \\ true) do + public?(activity) || + (!public && private?(activity) && activity.data["actor"] == user.ap_id) end - def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true - def is_direct?(%Object{data: %{"directMessage" => true}}), do: true + def direct?(%Activity{data: %{"directMessage" => true}}), do: true + def direct?(%Object{data: %{"directMessage" => true}}), do: true - def is_direct?(activity) do - !is_public?(activity) && !is_private?(activity) + def direct?(activity) do + !public?(activity) && !private?(activity) end - def is_list?(%{data: %{"listMessage" => _}}), do: true - def is_list?(_), do: false + def list?(%{data: %{"listMessage" => _}}), do: true + def list?(_), do: false @spec visible_for_user?(Object.t() | Activity.t() | nil, User.t() | nil) :: boolean() def visible_for_user?(%Object{data: %{"type" => "Tombstone"}}, _), do: false @@ -77,7 +77,7 @@ defmodule Pleroma.Web.ActivityPub.Visibility do when module in [Activity, Object] do if restrict_unauthenticated_access?(message), do: false, - else: is_public?(message) and not is_local_public?(message) + else: public?(message) and not local_public?(message) end def visible_for_user?(%{__struct__: module} = message, user) @@ -86,8 +86,8 @@ defmodule Pleroma.Web.ActivityPub.Visibility do y = [message.data["actor"]] ++ message.data["to"] ++ (message.data["cc"] || []) user_is_local = user.local - federatable = not is_local_public?(message) - (is_public?(message) || Enum.any?(x, &(&1 in y))) and (user_is_local || federatable) + federatable = not local_public?(message) + (public?(message) || Enum.any?(x, &(&1 in y))) and (user_is_local || federatable) end def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do diff --git a/lib/pleroma/web/admin_api/controllers/config_controller.ex b/lib/pleroma/web/admin_api/controllers/config_controller.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do alias Pleroma.ConfigDB alias Pleroma.Web.Plugs.OAuthScopesPlug - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action == :update) plug( @@ -76,7 +76,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do json(conn, translate_descriptions(descriptions)) end - def show(conn, %{only_db: true}) do + def show(%{private: %{open_api_spex: %{params: %{only_db: true}}}} = conn, _) do with :ok <- configurable_from_database() do configs = Pleroma.Repo.all(ConfigDB) @@ -128,7 +128,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do end end - def update(%{body_params: %{configs: configs}} = conn, _) do + def update(%{private: %{open_api_spex: %{body_params: %{configs: configs}}}} = conn, _) do with :ok <- configurable_from_database() do results = configs diff --git a/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex b/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.AdminAPI.InstanceDocumentController do alias Pleroma.Web.Plugs.InstanceStatic alias Pleroma.Web.Plugs.OAuthScopesPlug - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) action_fallback(Pleroma.Web.AdminAPI.FallbackController) @@ -18,7 +18,7 @@ defmodule Pleroma.Web.AdminAPI.InstanceDocumentController do plug(OAuthScopesPlug, %{scopes: ["admin:read"]} when action == :show) plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action in [:update, :delete]) - def show(conn, %{name: document_name}) do + def show(%{private: %{open_api_spex: %{params: %{name: document_name}}}} = conn, _) do with {:ok, url} <- InstanceDocument.get(document_name), {:ok, content} <- File.read(InstanceStatic.file_path(url)) do conn @@ -27,13 +27,18 @@ defmodule Pleroma.Web.AdminAPI.InstanceDocumentController do end end - def update(%{body_params: %{file: file}} = conn, %{name: document_name}) do + def update( + %{ + private: %{open_api_spex: %{body_params: %{file: file}, params: %{name: document_name}}} + } = conn, + _ + ) do with {:ok, url} <- InstanceDocument.put(document_name, file.path) do json(conn, %{"url" => url}) end end - def delete(conn, %{name: document_name}) do + def delete(%{private: %{open_api_spex: %{params: %{name: document_name}}}} = conn, _) do with :ok <- InstanceDocument.delete(document_name) do json(conn, %{}) end diff --git a/lib/pleroma/web/admin_api/controllers/invite_controller.ex b/lib/pleroma/web/admin_api/controllers/invite_controller.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.AdminAPI.InviteController do require Logger - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(OAuthScopesPlug, %{scopes: ["admin:read:invites"]} when action == :index) plug( @@ -33,14 +33,14 @@ defmodule Pleroma.Web.AdminAPI.InviteController do end @doc "Create an account registration invite token" - def create(%{body_params: params} = conn, _) do + def create(%{private: %{open_api_spex: %{body_params: params}}} = conn, _) do {:ok, invite} = UserInviteToken.create_invite(params) render(conn, "show.json", invite: invite) end @doc "Revokes invite by token" - def revoke(%{body_params: %{token: token}} = conn, _) do + def revoke(%{private: %{open_api_spex: %{body_params: %{token: token}}}} = conn, _) do with {:ok, invite} <- UserInviteToken.find_by_token(token), {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do render(conn, "show.json", invite: updated_invite) @@ -51,7 +51,13 @@ defmodule Pleroma.Web.AdminAPI.InviteController do end @doc "Sends registration invite via email" - def email(%{assigns: %{user: user}, body_params: %{email: email} = params} = conn, _) do + def email( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{body_params: %{email: email} = params}} + } = conn, + _ + ) do with {_, false} <- {:registrations_open, Config.get([:instance, :registrations_open])}, {_, true} <- {:invites_enabled, Config.get([:instance, :invites_enabled])}, {:ok, invite_token} <- UserInviteToken.create_invite(), diff --git a/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex b/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex @@ -11,7 +11,7 @@ defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do @cachex Pleroma.Config.get([:cachex, :provider], Cachex) - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug( OAuthScopesPlug, @@ -27,7 +27,7 @@ defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do defdelegate open_api_operation(action), to: Spec.MediaProxyCacheOperation - def index(%{assigns: %{user: _}} = conn, params) do + def index(%{assigns: %{user: _}, private: %{open_api_spex: %{params: params}}} = conn, _) do entries = fetch_entries(params) urls = paginate_entries(entries, params.page, params.page_size) @@ -59,12 +59,19 @@ defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do Enum.slice(entries, offset, page_size) end - def delete(%{assigns: %{user: _}, body_params: %{urls: urls}} = conn, _) do + def delete( + %{assigns: %{user: _}, private: %{open_api_spex: %{body_params: %{urls: urls}}}} = conn, + _ + ) do MediaProxy.remove_from_banned_urls(urls) json(conn, %{}) end - def purge(%{assigns: %{user: _}, body_params: %{urls: urls, ban: ban}} = conn, _) do + def purge( + %{assigns: %{user: _}, private: %{open_api_spex: %{body_params: %{urls: urls, ban: ban}}}} = + conn, + _ + ) do MediaProxy.Invalidation.purge(urls) if ban do diff --git a/lib/pleroma/web/admin_api/controllers/relay_controller.ex b/lib/pleroma/web/admin_api/controllers/relay_controller.ex @@ -11,7 +11,7 @@ defmodule Pleroma.Web.AdminAPI.RelayController do require Logger - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug( OAuthScopesPlug, @@ -31,7 +31,13 @@ defmodule Pleroma.Web.AdminAPI.RelayController do end end - def follow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do + def follow( + %{ + assigns: %{user: admin}, + private: %{open_api_spex: %{body_params: %{relay_url: target}}} + } = conn, + _ + ) do with {:ok, _message} <- Relay.follow(target) do ModerationLog.insert_log(%{action: "relay_follow", actor: admin, target: target}) @@ -44,7 +50,13 @@ defmodule Pleroma.Web.AdminAPI.RelayController do end end - def unfollow(%{assigns: %{user: admin}, body_params: %{relay_url: target} = params} = conn, _) do + def unfollow( + %{ + assigns: %{user: admin}, + private: %{open_api_spex: %{body_params: %{relay_url: target} = params}} + } = conn, + _ + ) do with {:ok, _message} <- Relay.unfollow(target, %{force: params[:force]}) do ModerationLog.insert_log(%{action: "relay_unfollow", actor: admin, target: target}) diff --git a/lib/pleroma/web/admin_api/controllers/report_controller.ex b/lib/pleroma/web/admin_api/controllers/report_controller.ex @@ -18,7 +18,7 @@ defmodule Pleroma.Web.AdminAPI.ReportController do require Logger - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(OAuthScopesPlug, %{scopes: ["admin:read:reports"]} when action in [:index, :show]) plug( @@ -31,13 +31,13 @@ defmodule Pleroma.Web.AdminAPI.ReportController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ReportOperation - def index(conn, params) do + def index(%{private: %{open_api_spex: %{params: params}}} = conn, _) do reports = Utils.get_reports(params, params.page, params.page_size) render(conn, "index.json", reports: reports) end - def show(conn, %{id: id}) do + def show(%{private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do with %Activity{} = report <- Activity.get_report(id) do render(conn, "show.json", Report.extract_report_info(report)) else @@ -45,7 +45,13 @@ defmodule Pleroma.Web.AdminAPI.ReportController do end end - def update(%{assigns: %{user: admin}, body_params: %{reports: reports}} = conn, _) do + def update( + %{ + assigns: %{user: admin}, + private: %{open_api_spex: %{body_params: %{reports: reports}}} + } = conn, + _ + ) do result = Enum.map(reports, fn report -> case CommonAPI.update_report_state(report.id, report.state) do @@ -73,9 +79,13 @@ defmodule Pleroma.Web.AdminAPI.ReportController do end end - def notes_create(%{assigns: %{user: user}, body_params: %{content: content}} = conn, %{ - id: report_id - }) do + def notes_create( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{body_params: %{content: content}, params: %{id: report_id}}} + } = conn, + _ + ) do with {:ok, _} <- ReportNote.create(user.id, report_id, content), report <- Activity.get_by_id_with_user_actor(report_id) do ModerationLog.insert_log(%{ @@ -92,10 +102,20 @@ defmodule Pleroma.Web.AdminAPI.ReportController do end end - def notes_delete(%{assigns: %{user: user}} = conn, %{ - id: note_id, - report_id: report_id - }) do + def notes_delete( + %{ + assigns: %{user: user}, + private: %{ + open_api_spex: %{ + params: %{ + id: note_id, + report_id: report_id + } + } + } + } = conn, + _ + ) do with {:ok, note} <- ReportNote.destroy(note_id), report <- Activity.get_by_id_with_user_actor(report_id) do ModerationLog.insert_log(%{ diff --git a/lib/pleroma/web/admin_api/controllers/user_controller.ex b/lib/pleroma/web/admin_api/controllers/user_controller.ex @@ -18,7 +18,7 @@ defmodule Pleroma.Web.AdminAPI.UserController do @users_page_size 50 - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug( OAuthScopesPlug, @@ -51,13 +51,22 @@ defmodule Pleroma.Web.AdminAPI.UserController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.UserOperation - def delete(conn, %{nickname: nickname}) do + def delete(%{private: %{open_api_spex: %{params: %{nickname: nickname}}}} = conn, _) do conn - |> Map.put(:body_params, %{nicknames: [nickname]}) - |> delete(%{}) + |> do_deletes([nickname]) end - def delete(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do + def delete( + %{ + private: %{open_api_spex: %{body_params: %{nicknames: nicknames}}} + } = conn, + _ + ) do + conn + |> do_deletes(nicknames) + end + + defp do_deletes(%{assigns: %{user: admin}} = conn, nicknames) when is_list(nicknames) do users = Enum.map(nicknames, &User.get_cached_by_nickname/1) Enum.each(users, fn user -> @@ -77,9 +86,13 @@ defmodule Pleroma.Web.AdminAPI.UserController do def follow( %{ assigns: %{user: admin}, - body_params: %{ - follower: follower_nick, - followed: followed_nick + private: %{ + open_api_spex: %{ + body_params: %{ + follower: follower_nick, + followed: followed_nick + } + } } } = conn, _ @@ -102,9 +115,13 @@ defmodule Pleroma.Web.AdminAPI.UserController do def unfollow( %{ assigns: %{user: admin}, - body_params: %{ - follower: follower_nick, - followed: followed_nick + private: %{ + open_api_spex: %{ + body_params: %{ + follower: follower_nick, + followed: followed_nick + } + } } } = conn, _ @@ -124,7 +141,13 @@ defmodule Pleroma.Web.AdminAPI.UserController do json(conn, "ok") end - def create(%{assigns: %{user: admin}, body_params: %{users: users}} = conn, _) do + def create( + %{ + assigns: %{user: admin}, + private: %{open_api_spex: %{body_params: %{users: users}}} + } = conn, + _ + ) do changesets = users |> Enum.map(fn %{nickname: nickname, email: email, password: password} -> @@ -178,7 +201,13 @@ defmodule Pleroma.Web.AdminAPI.UserController do end end - def show(%{assigns: %{user: admin}} = conn, %{nickname: nickname}) do + def show( + %{ + assigns: %{user: admin}, + private: %{open_api_spex: %{params: %{nickname: nickname}}} + } = conn, + _ + ) do with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do render(conn, "show.json", %{user: user}) else @@ -186,7 +215,11 @@ defmodule Pleroma.Web.AdminAPI.UserController do end end - def toggle_activation(%{assigns: %{user: admin}} = conn, %{nickname: nickname}) do + def toggle_activation( + %{assigns: %{user: admin}, private: %{open_api_spex: %{params: %{nickname: nickname}}}} = + conn, + _ + ) do user = User.get_cached_by_nickname(nickname) {:ok, updated_user} = User.set_activation(user, !user.is_active) @@ -202,7 +235,13 @@ defmodule Pleroma.Web.AdminAPI.UserController do render(conn, "show.json", user: updated_user) end - def activate(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do + def activate( + %{ + assigns: %{user: admin}, + private: %{open_api_spex: %{body_params: %{nicknames: nicknames}}} + } = conn, + _ + ) do users = Enum.map(nicknames, &User.get_cached_by_nickname/1) {:ok, updated_users} = User.set_activation(users, true) @@ -212,10 +251,16 @@ defmodule Pleroma.Web.AdminAPI.UserController do action: "activate" }) - render(conn, "index.json", users: Keyword.values(updated_users)) + render(conn, "index.json", users: updated_users) end - def deactivate(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do + def deactivate( + %{ + assigns: %{user: admin}, + private: %{open_api_spex: %{body_params: %{nicknames: nicknames}}} + } = conn, + _ + ) do users = Enum.map(nicknames, &User.get_cached_by_nickname/1) {:ok, updated_users} = User.set_activation(users, false) @@ -225,10 +270,16 @@ defmodule Pleroma.Web.AdminAPI.UserController do action: "deactivate" }) - render(conn, "index.json", users: Keyword.values(updated_users)) + render(conn, "index.json", users: updated_users) end - def approve(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do + def approve( + %{ + assigns: %{user: admin}, + private: %{open_api_spex: %{body_params: %{nicknames: nicknames}}} + } = conn, + _ + ) do users = Enum.map(nicknames, &User.get_cached_by_nickname/1) {:ok, updated_users} = User.approve(users) @@ -241,7 +292,13 @@ defmodule Pleroma.Web.AdminAPI.UserController do render(conn, "index.json", users: updated_users) end - def suggest(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do + def suggest( + %{ + assigns: %{user: admin}, + private: %{open_api_spex: %{body_params: %{nicknames: nicknames}}} + } = conn, + _ + ) do users = Enum.map(nicknames, &User.get_cached_by_nickname/1) {:ok, updated_users} = User.set_suggestion(users, true) @@ -254,7 +311,13 @@ defmodule Pleroma.Web.AdminAPI.UserController do render(conn, "index.json", users: updated_users) end - def unsuggest(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do + def unsuggest( + %{ + assigns: %{user: admin}, + private: %{open_api_spex: %{body_params: %{nicknames: nicknames}}} + } = conn, + _ + ) do users = Enum.map(nicknames, &User.get_cached_by_nickname/1) {:ok, updated_users} = User.set_suggestion(users, false) @@ -267,7 +330,7 @@ defmodule Pleroma.Web.AdminAPI.UserController do render(conn, "index.json", users: updated_users) end - def index(conn, params) do + def index(%{private: %{open_api_spex: %{params: params}}} = conn, _) do {page, page_size} = page_params(params) filters = maybe_parse_filters(params[:filters]) diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex @@ -43,7 +43,7 @@ defmodule Pleroma.Web.ApiSpec do - [Mastodon API documentation](https://docs.joinmastodon.org/client/intro/) - [Differences in Mastodon API responses from vanilla Mastodon](https://docs-develop.pleroma.social/backend/development/API/differences_in_mastoapi_responses/) - Please report such occurences on our [issue tracker](https://git.pleroma.social/pleroma/pleroma/-/issues). Feel free to submit API questions or proposals there too! + Please report such occurrences on our [issue tracker](https://git.pleroma.social/pleroma/pleroma/-/issues). Feel free to submit API questions or proposals there too! """, # Strip environment from the version version: Application.spec(:pleroma, :vsn) |> to_string() |> String.replace(~r/\+.*$/, ""), @@ -94,15 +94,15 @@ defmodule Pleroma.Web.ApiSpec do "tags" => [ "Chat administration", "Emoji pack administration", - "Frontend managment", + "Frontend management", "Instance configuration", "Instance documents", "Instance rule managment", "Invites", "MediaProxy cache", - "OAuth application managment", + "OAuth application management", "Relays", - "Report managment", + "Report management", "Status administration", "User administration", "Announcement management" diff --git a/lib/pleroma/web/api_spec/cast_and_validate.ex b/lib/pleroma/web/api_spec/cast_and_validate.ex @@ -27,10 +27,12 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do @impl Plug - def call(conn, %{operation_id: operation_id, render_error: render_error}) do + def call(conn, %{operation_id: operation_id, render_error: render_error} = opts) do {spec, operation_lookup} = PutApiSpec.get_spec_and_operation_lookup(conn) operation = operation_lookup[operation_id] + cast_opts = opts |> Map.take([:replace_params]) |> Map.to_list() + content_type = case Conn.get_req_header(conn, "content-type") do [header_value | _] -> @@ -44,7 +46,7 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do conn = Conn.put_private(conn, :operation_id, operation_id) - case cast_and_validate(spec, operation, conn, content_type, strict?()) do + case cast_and_validate(spec, operation, conn, content_type, strict?(), cast_opts) do {:ok, conn} -> conn @@ -94,11 +96,11 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do def call(conn, opts), do: OpenApiSpex.Plug.CastAndValidate.call(conn, opts) - defp cast_and_validate(spec, operation, conn, content_type, true = _strict) do - OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) + defp cast_and_validate(spec, operation, conn, content_type, true = _strict, cast_opts) do + OpenApiSpex.cast_and_validate(spec, operation, conn, content_type, cast_opts) end - defp cast_and_validate(spec, operation, conn, content_type, false = _strict) do + defp cast_and_validate(spec, operation, conn, content_type, false = _strict, cast_opts) do case OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) do {:ok, conn} -> {:ok, conn} @@ -123,7 +125,7 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do end) conn = %Conn{conn | query_params: query_params} - OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) + OpenApiSpex.cast_and_validate(spec, operation, conn, content_type, cast_opts) end end diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex @@ -62,7 +62,7 @@ defmodule Pleroma.Web.ApiSpec.Helpers do Operation.parameter( :with_relationships, :query, - BooleanLike, + BooleanLike.schema(), "Embed relationships into accounts. **If this parameter is not set account's `pleroma.relationship` is going to be `null`.**" ) end diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -122,22 +122,27 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do parameters: [ %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, - Operation.parameter(:pinned, :query, BooleanLike, "Include only pinned statuses"), + Operation.parameter( + :pinned, + :query, + BooleanLike.schema(), + "Include only pinned statuses" + ), Operation.parameter(:tagged, :query, :string, "With tag"), Operation.parameter( :only_media, :query, - BooleanLike, + BooleanLike.schema(), "Include only statuses with media attached" ), Operation.parameter( :with_muted, :query, - BooleanLike, + BooleanLike.schema(), "Include statuses from muted accounts." ), - Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"), - Operation.parameter(:exclude_replies, :query, BooleanLike, "Exclude replies"), + Operation.parameter(:exclude_reblogs, :query, BooleanLike.schema(), "Exclude reblogs"), + Operation.parameter(:exclude_replies, :query, BooleanLike.schema(), "Exclude replies"), Operation.parameter( :exclude_visibilities, :query, @@ -147,7 +152,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do Operation.parameter( :with_muted, :query, - BooleanLike, + BooleanLike.schema(), "Include reactions from muted accounts." ) ] ++ pagination_params(), @@ -347,7 +352,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do summary: "Endorse", operationId: "AccountController.endorse", security: [%{"oAuth" => ["follow", "write:accounts"]}], - description: "Addds the given account to endorsed accounts list.", + description: "Adds the given account to endorsed accounts list.", parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}], responses: %{ 200 => Operation.response("Relationship", "application/json", AccountRelationship), diff --git a/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex b/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex @@ -16,7 +16,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.FrontendOperation do def index_operation do %Operation{ - tags: ["Frontend managment"], + tags: ["Frontend management"], summary: "Retrieve a list of available frontends", operationId: "AdminAPI.FrontendController.index", security: [%{"oAuth" => ["admin:read"]}], @@ -29,7 +29,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.FrontendOperation do def install_operation do %Operation{ - tags: ["Frontend managment"], + tags: ["Frontend management"], summary: "Install a frontend", operationId: "AdminAPI.FrontendController.install", security: [%{"oAuth" => ["admin:read"]}], diff --git a/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex @@ -17,7 +17,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do def index_operation do %Operation{ summary: "Retrieve a list of OAuth applications", - tags: ["OAuth application managment"], + tags: ["OAuth application management"], operationId: "AdminAPI.OAuthAppController.index", security: [%{"oAuth" => ["admin:write"]}], parameters: [ @@ -69,7 +69,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do def create_operation do %Operation{ - tags: ["OAuth application managment"], + tags: ["OAuth application management"], summary: "Create an OAuth application", operationId: "AdminAPI.OAuthAppController.create", requestBody: request_body("Parameters", create_request()), @@ -84,7 +84,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do def update_operation do %Operation{ - tags: ["OAuth application managment"], + tags: ["OAuth application management"], summary: "Update OAuth application", operationId: "AdminAPI.OAuthAppController.update", parameters: [id_param() | admin_api_params()], @@ -102,7 +102,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do def delete_operation do %Operation{ - tags: ["OAuth application managment"], + tags: ["OAuth application management"], summary: "Delete OAuth application", operationId: "AdminAPI.OAuthAppController.delete", parameters: [id_param() | admin_api_params()], diff --git a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex @@ -19,7 +19,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do def index_operation do %Operation{ - tags: ["Report managment"], + tags: ["Report management"], summary: "Retrieve a list of reports", operationId: "AdminAPI.ReportController.index", security: [%{"oAuth" => ["admin:read:reports"]}], @@ -75,7 +75,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do def show_operation do %Operation{ - tags: ["Report managment"], + tags: ["Report management"], summary: "Retrieve a report", operationId: "AdminAPI.ReportController.show", parameters: [id_param() | admin_api_params()], @@ -89,7 +89,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do def update_operation do %Operation{ - tags: ["Report managment"], + tags: ["Report management"], summary: "Change state of specified reports", operationId: "AdminAPI.ReportController.update", security: [%{"oAuth" => ["admin:write:reports"]}], @@ -105,7 +105,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do def notes_create_operation do %Operation{ - tags: ["Report managment"], + tags: ["Report management"], summary: "Add a note to the report", operationId: "AdminAPI.ReportController.notes_create", parameters: [id_param() | admin_api_params()], @@ -126,7 +126,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do def notes_delete_operation do %Operation{ - tags: ["Report managment"], + tags: ["Report management"], summary: "Delete note attached to the report", operationId: "AdminAPI.ReportController.notes_delete", parameters: [ @@ -147,7 +147,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do end def id_param do - Operation.parameter(:id, :path, FlakeID, "Report ID", + Operation.parameter(:id, :path, FlakeID.schema(), "Report ID", example: "9umDrYheeY451cQnEe", required: true ) diff --git a/lib/pleroma/web/api_spec/operations/chat_operation.ex b/lib/pleroma/web/api_spec/operations/chat_operation.ex @@ -137,7 +137,12 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do "Deprecated due to no support for pagination. Using [/api/v2/pleroma/chats](#operation/ChatController.index2) instead is recommended.", operationId: "ChatController.index", parameters: [ - Operation.parameter(:with_muted, :query, BooleanLike, "Include chats from muted users") + Operation.parameter( + :with_muted, + :query, + BooleanLike.schema(), + "Include chats from muted users" + ) ], responses: %{ 200 => Operation.response("The chats of the user", "application/json", chats_response()) @@ -156,7 +161,12 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do summary: "Retrieve list of chats", operationId: "ChatController.index2", parameters: [ - Operation.parameter(:with_muted, :query, BooleanLike, "Include chats from muted users") + Operation.parameter( + :with_muted, + :query, + BooleanLike.schema(), + "Include chats from muted users" + ) | pagination_params() ], responses: %{ diff --git a/lib/pleroma/web/api_spec/operations/directory_operation.ex b/lib/pleroma/web/api_spec/operations/directory_operation.ex @@ -29,7 +29,7 @@ defmodule Pleroma.Web.ApiSpec.DirectoryOperation do "Order by recent activity or account creation", required: nil ), - Operation.parameter(:local, :query, BooleanLike, "Include local users only") + Operation.parameter(:local, :query, BooleanLike.schema(), "Include local users only") ] ++ pagination_params(), responses: %{ 200 => diff --git a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex @@ -21,7 +21,7 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do summary: "Get an object of emoji to account mappings with accounts that reacted to the post", parameters: [ - Operation.parameter(:id, :path, FlakeID, "Status ID", required: true), + Operation.parameter(:id, :path, FlakeID.schema(), "Status ID", required: true), Operation.parameter(:emoji, :path, :string, "Filter by a single unicode emoji", required: nil ), @@ -45,7 +45,7 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do tags: ["Emoji reactions"], summary: "React to a post with a unicode emoji", parameters: [ - Operation.parameter(:id, :path, FlakeID, "Status ID", required: true), + Operation.parameter(:id, :path, FlakeID.schema(), "Status ID", required: true), Operation.parameter(:emoji, :path, :string, "A single character unicode emoji", required: true ) @@ -64,7 +64,7 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do tags: ["Emoji reactions"], summary: "Remove a reaction to a post with a unicode emoji", parameters: [ - Operation.parameter(:id, :path, FlakeID, "Status ID", required: true), + Operation.parameter(:id, :path, FlakeID.schema(), "Status ID", required: true), Operation.parameter(:emoji, :path, :string, "A single character unicode emoji", required: true ) diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -112,7 +112,7 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do languages: %Schema{ type: :array, items: %Schema{type: :string}, - description: "Primary langauges of the website and its staff" + description: "Primary languages of the website and its staff" }, registrations: %Schema{type: :boolean, description: "Whether registrations are enabled"}, # Extra (not present in Mastodon): @@ -264,7 +264,7 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do languages: %Schema{ type: :array, items: %Schema{type: :string}, - description: "Primary langauges of the website and its staff" + description: "Primary languages of the website and its staff" }, registrations: %Schema{ type: :object, diff --git a/lib/pleroma/web/api_spec/operations/notification_operation.ex b/lib/pleroma/web/api_spec/operations/notification_operation.ex @@ -62,7 +62,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do Operation.parameter( :with_muted, :query, - BooleanLike, + BooleanLike.schema(), "Include the notifications from muted users" ) ] ++ pagination_params(), diff --git a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex @@ -142,7 +142,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do end defp id_param do - Operation.parameter(:id, :path, FlakeID, "Account ID", + Operation.parameter(:id, :path, FlakeID.schema(), "Account ID", example: "9umDrYheeY451cQnEe", required: true ) diff --git a/lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex @@ -23,7 +23,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do security: [%{"oAuth" => ["write"]}], operationId: "PleromaAPI.ScrobbleController.create", deprecated: true, - requestBody: request_body("Parameters", create_request(), requried: true), + requestBody: request_body("Parameters", create_request(), required: true), responses: %{ 200 => Operation.response("Scrobble", "application/json", scrobble()) } diff --git a/lib/pleroma/web/api_spec/operations/pleroma_status_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_status_operation.ex @@ -37,7 +37,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaStatusOperation do end def id_param do - Operation.parameter(:id, :path, FlakeID, "Status ID", + Operation.parameter(:id, :path, FlakeID.schema(), "Status ID", example: "9umDrYheeY451cQnEe", required: true ) diff --git a/lib/pleroma/web/api_spec/operations/poll_operation.ex b/lib/pleroma/web/api_spec/operations/poll_operation.ex @@ -47,7 +47,7 @@ defmodule Pleroma.Web.ApiSpec.PollOperation do end defp id_param do - Operation.parameter(:id, :path, FlakeID, "Poll ID", + Operation.parameter(:id, :path, FlakeID.schema(), "Poll ID", example: "123", required: true ) diff --git a/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex b/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex @@ -88,7 +88,7 @@ defmodule Pleroma.Web.ApiSpec.ScheduledActivityOperation do end defp id_param do - Operation.parameter(:id, :path, FlakeID, "Poll ID", + Operation.parameter(:id, :path, FlakeID.schema(), "Poll ID", example: "123", required: true ) diff --git a/lib/pleroma/web/api_spec/operations/search_operation.ex b/lib/pleroma/web/api_spec/operations/search_operation.ex @@ -70,7 +70,7 @@ defmodule Pleroma.Web.ApiSpec.SearchOperation do Operation.parameter( :account_id, :query, - FlakeID, + FlakeID.schema(), "If provided, statuses returned will be authored only by this account" ), Operation.parameter( @@ -116,7 +116,7 @@ defmodule Pleroma.Web.ApiSpec.SearchOperation do Operation.parameter( :account_id, :query, - FlakeID, + FlakeID.schema(), "If provided, statuses returned will be authored only by this account" ), Operation.parameter( diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -39,7 +39,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do Operation.parameter( :with_muted, :query, - BooleanLike, + BooleanLike.schema(), "Include reactions from muted acccounts." ) ], @@ -82,7 +82,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do Operation.parameter( :with_muted, :query, - BooleanLike, + BooleanLike.schema(), "Include reactions from muted acccounts." ) ], @@ -534,7 +534,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do format: :"date-time", nullable: true, description: - "ISO 8601 Datetime at which to schedule a status. Providing this paramter will cause ScheduledStatus to be returned instead of Status. Must be at least 5 minutes in the future." + "ISO 8601 Datetime at which to schedule a status. Providing this parameter will cause ScheduledStatus to be returned instead of Status. Must be at least 5 minutes in the future." }, language: %Schema{ type: :string, @@ -546,7 +546,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do allOf: [BooleanLike], nullable: true, description: - "If set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example" + "If set to `true` the post won't be actually posted, but the status entity would still be rendered back. This could be useful for previewing rich text/custom emoji, for example" }, content_type: %Schema{ type: :string, @@ -685,7 +685,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do end def id_param do - Operation.parameter(:id, :path, FlakeID, "Status ID", + Operation.parameter(:id, :path, FlakeID.schema(), "Status ID", example: "9umDrYheeY451cQnEe", required: true ) diff --git a/lib/pleroma/web/api_spec/operations/timeline_operation.ex b/lib/pleroma/web/api_spec/operations/timeline_operation.ex @@ -176,7 +176,12 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation do end defp with_muted_param do - Operation.parameter(:with_muted, :query, BooleanLike, "Include activities by muted users") + Operation.parameter( + :with_muted, + :query, + BooleanLike.schema(), + "Include activities by muted users" + ) end defp exclude_visibilities_param do diff --git a/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex b/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex @@ -87,7 +87,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do defp change_password_request do %Schema{ title: "ChangePasswordRequest", - description: "POST body for changing the account's passowrd", + description: "POST body for changing the account's password", type: :object, required: [:password, :new_password, :new_password_confirmation], properties: %{ @@ -136,23 +136,23 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do } end - def update_notificaton_settings_operation do + def update_notification_settings_operation do %Operation{ tags: ["Settings"], summary: "Update Notification Settings", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.update_notificaton_settings", + operationId: "UtilController.update_notification_settings", parameters: [ Operation.parameter( :block_from_strangers, :query, - BooleanLike, + BooleanLike.schema(), "blocks notifications from accounts you do not follow" ), Operation.parameter( :hide_notification_contents, :query, - BooleanLike, + BooleanLike.schema(), "removes the contents of a message from the push notification" ) ], diff --git a/lib/pleroma/web/api_spec/schemas/attachment.ex b/lib/pleroma/web/api_spec/schemas/attachment.ex @@ -11,7 +11,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Attachment do title: "Attachment", description: "Represents a file or media attachment that can be added to a status.", type: :object, - requried: [:id, :url, :preview_url], + required: [:id, :url, :preview_url], properties: %{ id: %Schema{type: :string, description: "The ID of the attachment in the database."}, url: %Schema{ diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex @@ -56,6 +56,15 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do } }, description: "Possible answers for the poll." + }, + pleroma: %Schema{ + type: :object, + properties: %{ + non_anonymous: %Schema{ + type: :boolean, + description: "Can voters be publicly identified?" + } + } } }, example: %{ @@ -79,7 +88,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do votes_count: 4 } ], - emojis: [] + emojis: [], + pleroma: %{ + non_anonymous: false + } } }) end diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Web.Auth.Authenticator do @callback get_user(Plug.Conn.t()) :: {:ok, user :: struct()} | {:error, any()} @callback create_from_registration(Plug.Conn.t(), registration :: struct()) :: - {:ok, User.t()} | {:error, any()} + {:ok, Pleroma.User.t()} | {:error, any()} @callback get_registration(Plug.Conn.t()) :: {:ok, registration :: struct()} | {:error, any()} @callback handle_error(Plug.Conn.t(), any()) :: any() @callback auth_template() :: String.t() | nil diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex @@ -373,7 +373,7 @@ defmodule Pleroma.Web.CommonAPI do do: visibility in ~w(public unlisted) def public_announce?(object, _) do - Visibility.is_public?(object) + Visibility.public?(object) end def get_visibility(_, _, %Participation{}), do: {"direct", "direct"} @@ -501,12 +501,12 @@ defmodule Pleroma.Web.CommonAPI do end defp activity_is_public(activity) do - with false <- Visibility.is_public?(activity) do + with false <- Visibility.public?(activity) do {:error, :visibility_error} end end - @spec unpin(String.t(), User.t()) :: {:ok, User.t()} | {:error, term()} + @spec unpin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()} def unpin(id, user) do with %Activity{} = activity <- create_activity_by_id(id), {:ok, unpin_data, _} <- Builder.unpin(user, activity.object), diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex @@ -14,6 +14,8 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do import Pleroma.Web.Gettext import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1] + @type t :: %__MODULE__{} + defstruct valid?: true, errors: [], user: nil, diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex @@ -109,7 +109,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do def get_to_and_cc(%{visibility: "direct"} = draft) do # If the OP is a DM already, add the implicit actor. - if draft.in_reply_to && Visibility.is_direct?(draft.in_reply_to) do + if draft.in_reply_to && Visibility.direct?(draft.in_reply_to) do {Enum.uniq([draft.in_reply_to.data["actor"] | draft.mentions]), []} else {draft.mentions, []} diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex @@ -20,7 +20,7 @@ defmodule Pleroma.Web.ControllerHelper do |> json(json) end - @spec fetch_integer_param(map(), String.t(), integer() | nil) :: integer() | nil + @spec fetch_integer_param(map(), String.t() | atom(), integer() | nil) :: integer() | nil def fetch_integer_param(params, name, default \\ nil) do params |> Map.get(name, default) @@ -53,10 +53,15 @@ defmodule Pleroma.Web.ControllerHelper do end end + # TODO: Only fetch the params from open_api_spex when everything is converted @id_keys Pagination.page_keys() -- ["limit", "order"] defp build_pagination_fields(conn, min_id, max_id, extra_params) do params = - conn.params + if Map.has_key?(conn.private, :open_api_spex) do + get_in(conn, [Access.key(:private), Access.key(:open_api_spex), Access.key(:params)]) + else + conn.params + end |> Map.drop(Map.keys(conn.path_params) |> Enum.map(&String.to_existing_atom/1)) |> Map.merge(extra_params) |> Map.drop(@id_keys) @@ -85,18 +90,15 @@ defmodule Pleroma.Web.ControllerHelper do end end - def assign_account_by_id(conn, _) do - case Pleroma.User.get_cached_by_id(conn.params.id) do + def assign_account_by_id(%{private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do + case Pleroma.User.get_cached_by_id(id) do %Pleroma.User{} = account -> assign(conn, :account, account) nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() end end def try_render(conn, target, params) when is_binary(target) do - case render(conn, target, params) do - nil -> render_error(conn, :not_implemented, "Can't display this activity") - res -> res - end + render(conn, target, params) end def try_render(conn, _, _) do diff --git a/lib/pleroma/web/embed_controller.ex b/lib/pleroma/web/embed_controller.ex @@ -11,12 +11,10 @@ defmodule Pleroma.Web.EmbedController do alias Pleroma.Web.ActivityPub.Visibility - plug(:put_layout, :embed) - def show(conn, %{"id" => id}) do with %Activity{local: true} = activity <- Activity.get_by_id_with_object(id), - true <- Visibility.is_public?(activity.object) do + true <- Visibility.public?(activity.object) do {:ok, author} = User.get_or_fetch(activity.object.data["actor"]) conn diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex @@ -9,6 +9,16 @@ defmodule Pleroma.Web.Endpoint do alias Pleroma.Config + socket("/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, + longpoll: false, + websocket: [ + path: "/", + compress: false, + error_handler: {Pleroma.Web.MastodonAPI.WebsocketHandler, :handle_error, []}, + fullsweep_after: 20 + ] + ) + socket("/socket", Pleroma.Web.UserSocket, websocket: [ path: "/websocket", @@ -18,7 +28,8 @@ defmodule Pleroma.Web.Endpoint do ], timeout: 60_000, transport_log: false, - compress: false + compress: false, + fullsweep_after: 20 ], longpoll: false ) @@ -32,7 +43,8 @@ defmodule Pleroma.Web.Endpoint do plug(Pleroma.Web.Plugs.HTTPSecurityPlug) plug(Pleroma.Web.Plugs.UploadedMedia) - @static_cache_control "public, no-cache" + @static_cache_control "public, max-age=1209600" + @static_cache_disabled "public, no-cache" # InstanceStatic needs to be before Plug.Static to be able to override shipped-static files # If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well @@ -43,22 +55,32 @@ defmodule Pleroma.Web.Endpoint do from: :pleroma, only: ["emoji", "images"], gzip: true, - cache_control_for_etags: "public, max-age=1209600", + cache_control_for_etags: @static_cache_control, headers: %{ - "cache-control" => "public, max-age=1209600" + "cache-control" => @static_cache_control } ) plug(Pleroma.Web.Plugs.InstanceStatic, at: "/", gzip: true, - cache_control_for_etags: @static_cache_control, + cache_control_for_etags: @static_cache_disabled, headers: %{ - "cache-control" => @static_cache_control + "cache-control" => @static_cache_disabled + } + ) + + plug(Pleroma.Web.Plugs.FrontendStatic, + at: "/", + frontend_type: :primary, + only: ["index.html"], + gzip: true, + cache_control_for_etags: @static_cache_disabled, + headers: %{ + "cache-control" => @static_cache_disabled } ) - # Careful! No `only` restriction here, as we don't know what frontends contain. plug(Pleroma.Web.Plugs.FrontendStatic, at: "/", frontend_type: :primary, @@ -75,9 +97,9 @@ defmodule Pleroma.Web.Endpoint do at: "/pleroma/admin", frontend_type: :admin, gzip: true, - cache_control_for_etags: @static_cache_control, + cache_control_for_etags: @static_cache_disabled, headers: %{ - "cache-control" => @static_cache_control + "cache-control" => @static_cache_disabled } ) @@ -92,9 +114,9 @@ defmodule Pleroma.Web.Endpoint do only: Pleroma.Constants.static_only_files(), # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength gzip: true, - cache_control_for_etags: @static_cache_control, + cache_control_for_etags: @static_cache_disabled, headers: %{ - "cache-control" => @static_cache_control + "cache-control" => @static_cache_disabled } ) diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex @@ -6,9 +6,9 @@ defmodule Pleroma.Web.Federator do alias Pleroma.Activity alias Pleroma.Object.Containment alias Pleroma.User + alias Pleroma.Web.ActivityPub.Publisher alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Utils - alias Pleroma.Web.Federator.Publisher alias Pleroma.Workers.PublisherWorker alias Pleroma.Workers.ReceiverWorker @@ -68,10 +68,8 @@ defmodule Pleroma.Web.Federator do # Job Worker Callbacks - @spec perform(atom(), module(), any()) :: {:ok, any()} | {:error, any()} - def perform(:publish_one, module, params) do - apply(module, :publish_one, [params]) - end + @spec perform(atom(), any()) :: {:ok, any()} | {:error, any()} + def perform(:publish_one, params), do: Publisher.publish_one(params) def perform(:publish, activity) do Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end) diff --git a/lib/pleroma/web/federator/publisher.ex b/lib/pleroma/web/federator/publisher.ex @@ -1,110 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Federator.Publisher do - alias Pleroma.Activity - alias Pleroma.Config - alias Pleroma.User - alias Pleroma.Workers.PublisherWorker - - require Logger - - @moduledoc """ - Defines the contract used by federation implementations to publish messages to - their peers. - """ - - @doc """ - Determine whether an activity can be relayed using the federation module. - """ - @callback is_representable?(Pleroma.Activity.t()) :: boolean() - - @doc """ - Relays an activity to a specified peer, determined by the parameters. The - parameters used are controlled by the federation module. - """ - @callback publish_one(Map.t()) :: {:ok, Map.t()} | {:error, any()} - - @doc """ - Enqueue publishing a single activity. - """ - @spec enqueue_one(module(), Map.t(), Keyword.t()) :: {:ok, %Oban.Job{}} - def enqueue_one(module, %{} = params, worker_args \\ []) do - PublisherWorker.enqueue( - "publish_one", - %{"module" => to_string(module), "params" => params}, - worker_args - ) - end - - @doc """ - Relays an activity to all specified peers. - """ - @callback publish(User.t(), Activity.t()) :: :ok | {:error, any()} - - @spec publish(User.t(), Activity.t()) :: :ok - def publish(%User{} = user, %Activity{} = activity) do - Config.get([:instance, :federation_publisher_modules]) - |> Enum.each(fn module -> - if module.is_representable?(activity) do - Logger.debug("Publishing #{activity.data["id"]} using #{inspect(module)}") - module.publish(user, activity) - end - end) - - :ok - end - - @doc """ - Gathers links used by an outgoing federation module for WebFinger output. - """ - @callback gather_webfinger_links(User.t()) :: list() - - @spec gather_webfinger_links(User.t()) :: list() - def gather_webfinger_links(%User{} = user) do - Config.get([:instance, :federation_publisher_modules]) - |> Enum.reduce([], fn module, links -> - links ++ module.gather_webfinger_links(user) - end) - end - - @doc """ - Gathers nodeinfo protocol names supported by the federation module. - """ - @callback gather_nodeinfo_protocol_names() :: list() - - @spec gather_nodeinfo_protocol_names() :: list() - def gather_nodeinfo_protocol_names do - Config.get([:instance, :federation_publisher_modules]) - |> Enum.reduce([], fn module, links -> - links ++ module.gather_nodeinfo_protocol_names() - end) - end - - @doc """ - Gathers a set of remote users given an IR envelope. - """ - def remote_users(%User{id: user_id}, %{data: %{"to" => to} = data}) do - cc = Map.get(data, "cc", []) - - bcc = - data - |> Map.get("bcc", []) - |> Enum.reduce([], fn ap_id, bcc -> - case Pleroma.List.get_by_ap_id(ap_id) do - %Pleroma.List{user_id: ^user_id} = list -> - {:ok, following} = Pleroma.List.get_following(list) - bcc ++ Enum.map(following, & &1.ap_id) - - _ -> - bcc - end - end) - - [to, cc, bcc] - |> Enum.concat() - |> Enum.map(&User.get_cached_by_ap_id/1) - |> Enum.filter(fn user -> user && !user.local end) - end -end diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex @@ -132,7 +132,7 @@ defmodule Pleroma.Web.Feed.FeedView do |> safe_to_string() end - @spec to_rfc3339(String.t() | NativeDateTime.t()) :: String.t() + @spec to_rfc3339(String.t() | NaiveDateTime.t()) :: String.t() def to_rfc3339(date) when is_binary(date) do date |> Timex.parse!("{ISO:Extended}") @@ -145,7 +145,7 @@ defmodule Pleroma.Web.Feed.FeedView do |> Timex.format!("{RFC3339}") end - @spec to_rfc2822(String.t() | DateTime.t() | NativeDateTime.t()) :: String.t() + @spec to_rfc2822(String.t() | DateTime.t() | NaiveDateTime.t()) :: String.t() def to_rfc2822(datestr) when is_binary(datestr) do datestr |> Timex.parse!("{ISO:Extended}") diff --git a/lib/pleroma/web/gettext.ex b/lib/pleroma/web/gettext.ex @@ -85,12 +85,12 @@ defmodule Pleroma.Web.Gettext do Process.get({Pleroma.Web.Gettext, :locales}, []) end - def is_locale_list(locales) do + def locale_list?(locales) do Enum.all?(locales, &is_binary/1) end def put_locales(locales) do - if is_locale_list(locales) do + if locale_list?(locales) do Process.put({Pleroma.Web.Gettext, :locales}, Enum.uniq(locales)) Gettext.put_locale(Enum.at(locales, 0, Gettext.get_locale())) :ok diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -30,7 +30,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do alias Pleroma.Web.TwitterAPI.TwitterAPI alias Pleroma.Web.Utils.Params - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(:skip_auth when action in [:create, :lookup]) @@ -92,7 +92,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug( RateLimiter, - [name: :relation_id_action, params: [:id, :uri]] when action in @relationship_actions + [name: :relation_id_action, params: ["id", "uri"]] when action in @relationship_actions ) plug(RateLimiter, [name: :relations_actions] when action in @relationship_actions) @@ -104,7 +104,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AccountOperation @doc "POST /api/v1/accounts" - def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do + def create( + %{assigns: %{app: app}, private: %{open_api_spex: %{body_params: params}}} = conn, + _params + ) do with :ok <- validate_email_param(params), :ok <- TwitterAPI.validate_captcha(app, params), {:ok, user} <- TwitterAPI.register_user(params), @@ -168,7 +171,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "PATCH /api/v1/accounts/update_credentials" - def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _params) do + def update_credentials( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: params}}} = conn, + _params + ) do params = params |> Enum.filter(fn {_, value} -> not is_nil(value) end) @@ -235,7 +241,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do # So we first build the normal local changeset, then apply it to the # user data, but don't persist it. With this, we generate the object # data for our update activity. We feed this and the changeset as meta - # inforation into the pipeline, where they will be properly updated and + # information into the pipeline, where they will be properly updated and # federated. with changeset <- User.update_changeset(user, user_params), {:ok, unpersisted_user} <- Ecto.Changeset.apply_action(changeset, :update), @@ -289,7 +295,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "GET /api/v1/accounts/relationships" - def relationships(%{assigns: %{user: user}} = conn, %{id: id}) do + def relationships( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + _ + ) do targets = User.get_all_by_ids(List.wrap(id)) render(conn, "relationships.json", user: user, targets: targets) @@ -299,7 +308,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, []) @doc "GET /api/v1/accounts/:id" - def show(%{assigns: %{user: for_user}} = conn, %{id: nickname_or_id} = params) do + def show( + %{ + assigns: %{user: for_user}, + private: %{open_api_spex: %{params: %{id: nickname_or_id} = params}} + } = conn, + _params + ) do with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user), :visible <- User.visible_for(user, for_user) do render(conn, "show.json", @@ -313,7 +328,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "GET /api/v1/accounts/:id/statuses" - def statuses(%{assigns: %{user: reading_user}} = conn, params) do + def statuses( + %{assigns: %{user: reading_user}, private: %{open_api_spex: %{params: params}}} = conn, + _params + ) do with %User{} = user <- User.get_cached_by_nickname_or_id(params.id, for: reading_user), :visible <- User.visible_for(user, reading_user) do params = @@ -348,7 +366,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "GET /api/v1/accounts/:id/followers" - def followers(%{assigns: %{user: for_user, account: user}} = conn, params) do + def followers( + %{assigns: %{user: for_user, account: user}, private: %{open_api_spex: %{params: params}}} = + conn, + _params + ) do params = params |> Enum.map(fn {key, value} -> {to_string(key), value} end) @@ -373,7 +395,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "GET /api/v1/accounts/:id/following" - def following(%{assigns: %{user: for_user, account: user}} = conn, params) do + def following( + %{assigns: %{user: for_user, account: user}, private: %{open_api_spex: %{params: params}}} = + conn, + _params + ) do params = params |> Enum.map(fn {key, value} -> {to_string(key), value} end) @@ -411,7 +437,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do {:error, "Can not follow yourself"} end - def follow(%{body_params: params, assigns: %{user: follower, account: followed}} = conn, _) do + def follow( + %{ + assigns: %{user: follower, account: followed}, + private: %{open_api_spex: %{body_params: params}} + } = conn, + _ + ) do with {:ok, follower} <- MastodonAPI.follow(follower, followed, params) do render(conn, "relationship.json", user: follower, target: followed) else @@ -431,7 +463,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "POST /api/v1/accounts/:id/mute" - def mute(%{assigns: %{user: muter, account: muted}, body_params: params} = conn, _params) do + def mute( + %{ + assigns: %{user: muter, account: muted}, + private: %{open_api_spex: %{body_params: params}} + } = conn, + _params + ) do params = params |> Map.put_new(:duration, Map.get(params, :expires_in, 0)) @@ -472,7 +510,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "POST /api/v1/accounts/:id/note" def note( - %{assigns: %{user: noter, account: target}, body_params: %{comment: comment}} = conn, + %{ + assigns: %{user: noter, account: target}, + private: %{open_api_spex: %{body_params: %{comment: comment}}} + } = conn, _params ) do with {:ok, _user_note} <- UserNote.create(noter, target, comment) do @@ -513,7 +554,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "POST /api/v1/follows" - def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do + def follow_by_uri(%{private: %{open_api_spex: %{body_params: %{uri: uri}}}} = conn, _) do case User.get_cached_by_nickname(uri) do %User{} = user -> conn @@ -561,7 +602,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "GET /api/v1/accounts/lookup" - def lookup(conn, %{acct: nickname} = _params) do + def lookup(%{private: %{open_api_spex: %{params: %{acct: nickname}}}} = conn, _params) do with %User{} = user <- User.get_by_nickname(nickname) do render(conn, "show.json", user: user, diff --git a/lib/pleroma/web/mastodon_api/controllers/directory_controller.ex b/lib/pleroma/web/mastodon_api/controllers/directory_controller.ex @@ -15,7 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.DirectoryController do plug(Pleroma.Web.ApiSpec.CastAndValidate) - plug(:skip_auth when action == "index") + plug(:skip_auth when action == :index) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DirectoryOperation diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do alias Pleroma.User alias Pleroma.Web.Plugs.OAuthScopesPlug - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DomainBlockOperation plug( @@ -27,23 +27,31 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do end @doc "POST /api/v1/domain_blocks" - def create(%{assigns: %{user: blocker}, body_params: %{domain: domain}} = conn, _params) do + def create( + %{assigns: %{user: blocker}, private: %{open_api_spex: %{body_params: %{domain: domain}}}} = + conn, + _params + ) do User.block_domain(blocker, domain) json(conn, %{}) end - def create(%{assigns: %{user: blocker}} = conn, %{domain: domain}) do + 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 + def delete( + %{assigns: %{user: blocker}, private: %{open_api_spex: %{body_params: %{domain: domain}}}} = + conn, + _params + ) do User.unblock_domain(blocker, domain) json(conn, %{}) end - def delete(%{assigns: %{user: blocker}} = conn, %{domain: domain}) do + def delete(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do User.unblock_domain(blocker, domain) json(conn, %{}) end diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do alias Pleroma.Web.CommonAPI alias Pleroma.Web.Plugs.OAuthScopesPlug - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(:assign_follower when action != :index) action_fallback(:errors) @@ -44,7 +44,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do end end - defp assign_follower(%{params: %{id: id}} = conn, _) do + defp assign_follower(%{private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do case User.get_cached_by_id(id) do %User{} = follower -> assign(conn, :follower, follower) nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex @@ -11,7 +11,7 @@ defmodule Pleroma.Web.MastodonAPI.ListController do @oauth_read_actions [:index, :show, :list_accounts] - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(:list_by_id_and_user when action not in [:index, :create]) plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action in @oauth_read_actions) plug(OAuthScopesPlug, %{scopes: ["write:lists"]} when action not in @oauth_read_actions) @@ -21,25 +21,33 @@ defmodule Pleroma.Web.MastodonAPI.ListController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ListOperation # GET /api/v1/lists - def index(%{assigns: %{user: user}} = conn, opts) do - lists = Pleroma.List.for_user(user, opts) + def index(%{assigns: %{user: user}, private: %{open_api_spex: %{params: params}}} = conn, _) do + lists = Pleroma.List.for_user(user, params) render(conn, "index.json", lists: lists) end # POST /api/v1/lists - def create(%{assigns: %{user: user}, body_params: %{title: title}} = conn, _) do + def create( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: %{title: title}}}} = + conn, + _ + ) do with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(title, user) do render(conn, "show.json", list: list) end end - # GET /api/v1/lists/:id + # GET /api/v1/lists/:idOB def show(%{assigns: %{list: list}} = conn, _) do render(conn, "show.json", list: list) end # PUT /api/v1/lists/:id - def update(%{assigns: %{list: list}, body_params: %{title: title}} = conn, _) do + def update( + %{assigns: %{list: list}, private: %{open_api_spex: %{body_params: %{title: title}}}} = + conn, + _ + ) do with {:ok, list} <- Pleroma.List.rename(list, title) do render(conn, "show.json", list: list) end @@ -62,7 +70,13 @@ defmodule Pleroma.Web.MastodonAPI.ListController do end # POST /api/v1/lists/:id/accounts - def add_to_list(%{assigns: %{list: list}, body_params: %{account_ids: account_ids}} = conn, _) do + def add_to_list( + %{ + assigns: %{list: list}, + private: %{open_api_spex: %{body_params: %{account_ids: account_ids}}} + } = conn, + _ + ) do Enum.each(account_ids, fn account_id -> with %User{} = followed <- User.get_cached_by_id(account_id) do Pleroma.List.follow(list, followed) @@ -74,9 +88,22 @@ defmodule Pleroma.Web.MastodonAPI.ListController do # DELETE /api/v1/lists/:id/accounts def remove_from_list( - %{assigns: %{list: list}, params: %{account_ids: account_ids}} = conn, + %{ + private: %{open_api_spex: %{params: %{account_ids: account_ids}}} + } = conn, _ ) do + do_remove_from_list(conn, account_ids) + end + + def remove_from_list( + %{private: %{open_api_spex: %{body_params: %{account_ids: account_ids}}}} = conn, + _ + ) do + do_remove_from_list(conn, account_ids) + end + + defp do_remove_from_list(%{assigns: %{list: list}} = conn, account_ids) do Enum.each(account_ids, fn account_id -> with %User{} = followed <- User.get_cached_by_id(account_id) do Pleroma.List.unfollow(list, followed) @@ -86,11 +113,10 @@ defmodule Pleroma.Web.MastodonAPI.ListController do json(conn, %{}) end - def remove_from_list(%{body_params: params} = conn, _) do - remove_from_list(%{conn | params: params}, %{}) - end - - defp list_by_id_and_user(%{assigns: %{user: user}, params: %{id: id}} = conn, _) do + defp list_by_id_and_user( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + _ + ) do case Pleroma.List.get(id, user) do %Pleroma.List{} = list -> assign(conn, :list, list) nil -> conn |> render_error(:not_found, "List not found") |> halt() diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex @@ -12,7 +12,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:create, :create2]) - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(OAuthScopesPlug, %{scopes: ["read:media"]} when action == :show) plug(OAuthScopesPlug, %{scopes: ["write:media"]} when action != :show) @@ -20,7 +20,11 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.MediaOperation @doc "POST /api/v1/media" - def create(%{assigns: %{user: user}, body_params: %{file: file} = data} = conn, _) do + def create( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: %{file: file} = data}}} = + conn, + _ + ) do with {:ok, object} <- ActivityPub.upload( file, @@ -36,7 +40,11 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do def create(_conn, _data), do: {:error, :bad_request} @doc "POST /api/v2/media" - def create2(%{assigns: %{user: user}, body_params: %{file: file} = data} = conn, _) do + def create2( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: %{file: file} = data}}} = + conn, + _ + ) do with {:ok, object} <- ActivityPub.upload( file, @@ -54,7 +62,15 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do def create2(_conn, _data), do: {:error, :bad_request} @doc "PUT /api/v1/media/:id" - def update(%{assigns: %{user: user}, body_params: %{description: description}} = conn, %{id: id}) do + def update( + %{ + assigns: %{user: user}, + private: %{ + open_api_spex: %{body_params: %{description: description}, params: %{id: id}} + } + } = conn, + _ + ) do with %Object{} = object <- Object.get_by_id(id), :ok <- Object.authorize_access(object, user), {:ok, %Object{data: data}} <- Object.update_data(object, %{"name" => description}) do @@ -67,7 +83,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do def update(conn, data), do: show(conn, data) @doc "GET /api/v1/media/:id" - def show(%{assigns: %{user: user}} = conn, %{id: id}) do + def show(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do with %Object{data: data, id: object_id} = object <- Object.get_by_id(id), :ok <- Object.authorize_access(object, user) do attachment_data = Map.put(data, "id", object_id) diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do @oauth_read_actions [:show, :index] - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug( OAuthScopesPlug, @@ -24,8 +24,20 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.NotificationOperation + @default_notification_types ~w{ + mention + follow + follow_request + reblog + favourite + move + pleroma:emoji_reaction + poll + update + } + # GET /api/v1/notifications - def index(conn, %{account_id: account_id} = params) do + def index(%{private: %{open_api_spex: %{params: %{account_id: account_id} = params}}} = conn, _) do case Pleroma.User.get_cached_by_id(account_id) do %{ap_id: account_ap_id} -> params = @@ -33,7 +45,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do |> Map.delete(:account_id) |> Map.put(:account_ap_id, account_ap_id) - index(conn, params) + do_get_notifications(conn, params) _ -> conn @@ -42,18 +54,11 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do end end - @default_notification_types ~w{ - mention - follow - follow_request - reblog - favourite - move - pleroma:emoji_reaction - poll - update - } - def index(%{assigns: %{user: user}} = conn, params) do + def index(%{private: %{open_api_spex: %{params: params}}} = conn, _) do + do_get_notifications(conn, params) + end + + defp do_get_notifications(%{assigns: %{user: user}} = conn, params) do params = Map.new(params, fn {k, v} -> {to_string(k), v} end) |> Map.put_new("types", Map.get(params, :include_types, @default_notification_types)) @@ -69,7 +74,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do end # GET /api/v1/notifications/:id - def show(%{assigns: %{user: user}} = conn, %{id: id}) do + def show(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do with {:ok, notification} <- Notification.get(user, id) do render(conn, "show.json", notification: notification, for: user) else @@ -88,8 +93,20 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do # POST /api/v1/notifications/:id/dismiss - def dismiss(%{assigns: %{user: user}} = conn, %{id: id} = _params) do - with {:ok, _notif} <- Notification.dismiss(user, id) do + def dismiss(%{private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do + do_dismiss(conn, id) + end + + # POST /api/v1/notifications/dismiss (deprecated) + def dismiss_via_body( + %{private: %{open_api_spex: %{body_params: %{id: id}}}} = conn, + _ + ) do + do_dismiss(conn, id) + end + + defp do_dismiss(%{assigns: %{user: user}} = conn, notification_id) do + with {:ok, _notif} <- Notification.dismiss(user, notification_id) do json(conn, %{}) else {:error, reason} -> @@ -99,13 +116,11 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do end end - # POST /api/v1/notifications/dismiss (deprecated) - def dismiss_via_body(%{body_params: params} = conn, _) do - dismiss(conn, params) - end - # DELETE /api/v1/notifications/destroy_multiple - def destroy_multiple(%{assigns: %{user: user}} = conn, %{ids: ids} = _params) do + def destroy_multiple( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{ids: ids}}}} = conn, + _ + ) do Notification.destroy_multiple(user, ids) json(conn, %{}) end diff --git a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex @@ -15,7 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.PollController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug( OAuthScopesPlug, @@ -29,7 +29,7 @@ defmodule Pleroma.Web.MastodonAPI.PollController do @cachex Pleroma.Config.get([:cachex, :provider], Cachex) @doc "GET /api/v1/polls/:id" - def show(%{assigns: %{user: user}} = conn, %{id: id}) do + def show(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60), %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), true <- Visibility.visible_for_user?(activity, user) do @@ -41,7 +41,13 @@ defmodule Pleroma.Web.MastodonAPI.PollController do end @doc "POST /api/v1/polls/:id/votes" - def vote(%{assigns: %{user: user}, body_params: %{choices: choices}} = conn, %{id: id}) do + def vote( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{body_params: %{choices: choices}, params: %{id: id}}} + } = conn, + _ + ) do with %Object{data: %{"type" => "Question"}} = object <- Object.get_by_id(id), %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), true <- Visibility.visible_for_user?(activity, user), diff --git a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do @oauth_read_actions [:show, :index] - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in @oauth_read_actions) plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action not in @oauth_read_actions) plug(:assign_scheduled_activity when action != :index) @@ -23,7 +23,7 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ScheduledActivityOperation @doc "GET /api/v1/scheduled_statuses" - def index(%{assigns: %{user: user}} = conn, params) do + def index(%{assigns: %{user: user}, private: %{open_api_spex: %{params: params}}} = conn, _) do params = Map.new(params, fn {key, value} -> {to_string(key), value} end) with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do @@ -39,7 +39,13 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do end @doc "PUT /api/v1/scheduled_statuses/:id" - def update(%{assigns: %{scheduled_activity: scheduled_activity}, body_params: params} = conn, _) do + def update( + %{ + assigns: %{scheduled_activity: scheduled_activity}, + private: %{open_api_spex: %{body_params: params}} + } = conn, + _ + ) do with {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do render(conn, "show.json", scheduled_activity: scheduled_activity) end @@ -52,7 +58,10 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do end end - defp assign_scheduled_activity(%{assigns: %{user: user}, params: %{id: id}} = conn, _) do + defp assign_scheduled_activity( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + _ + ) do case ScheduledActivity.get(user, id) do %ScheduledActivity{} = activity -> assign(conn, :scheduled_activity, activity) nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -18,7 +18,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do @search_limit 40 - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) # Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search) plug(OAuthScopesPlug, %{scopes: ["read:search"], fallback: :proceed_unauthenticated}) @@ -29,7 +29,11 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.SearchOperation - def account_search(%{assigns: %{user: user}} = conn, %{q: query} = params) do + def account_search( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{q: query} = params}}} = + conn, + _ + ) do accounts = User.search(query, search_options(params, user)) conn @@ -44,7 +48,12 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do def search2(conn, params), do: do_search(:v2, conn, params) def search(conn, params), do: do_search(:v1, conn, params) - defp do_search(version, %{assigns: %{user: user}} = conn, %{q: query} = params) do + defp do_search( + version, + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{q: query} = params}}} = + conn, + _ + ) do query = String.trim(query) options = search_options(params, user) timeout = Keyword.get(Repo.config(), :timeout, 15_000) @@ -147,7 +156,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do tags end - Pleroma.Pagination.paginate(tags, options) + Pleroma.Pagination.paginate_list(tags, options) end defp add_joined_tag(tags) do diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -25,7 +25,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.RateLimiter - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(:skip_public_check when action in [:index, :show]) @@ -110,7 +110,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do `ids` query param is required """ - def index(%{assigns: %{user: user}} = conn, %{ids: ids} = params) do + def index( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{ids: ids} = params}}} = + conn, + _ + ) do limit = 100 activities = @@ -134,7 +138,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do def create( %{ assigns: %{user: user}, - body_params: %{status: _, scheduled_at: scheduled_at} = params + private: %{ + open_api_spex: %{body_params: %{status: _, scheduled_at: scheduled_at} = params} + } } = conn, _ ) @@ -156,7 +162,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do else {:far_enough, _} -> params = Map.drop(params, [:scheduled_at]) - create(%Plug.Conn{conn | body_params: params}, %{}) + + put_in( + conn, + [Access.key(:private), Access.key(:open_api_spex), Access.key(:body_params)], + params + ) + |> do_create error -> error @@ -164,7 +176,35 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end # Creates a regular status - def create(%{assigns: %{user: user}, body_params: %{status: _} = params} = conn, _) do + def create( + %{ + private: %{open_api_spex: %{body_params: %{status: _}}} + } = conn, + _ + ) do + do_create(conn) + end + + def create( + %{ + assigns: %{user: _user}, + private: %{open_api_spex: %{body_params: %{media_ids: _} = params}} + } = conn, + _ + ) do + params = Map.put(params, :status, "") + + put_in( + conn, + [Access.key(:private), Access.key(:open_api_spex), Access.key(:body_params)], + params + ) + |> do_create + end + + defp do_create( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: params}}} = conn + ) do params = Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id]) |> put_application(conn) @@ -189,13 +229,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end end - def create(%{assigns: %{user: _user}, body_params: %{media_ids: _} = params} = conn, _) do - params = Map.put(params, :status, "") - create(%Plug.Conn{conn | body_params: params}, %{}) - end - @doc "GET /api/v1/statuses/:id/history" - def show_history(%{assigns: assigns} = conn, %{id: id} = params) do + def show_history( + %{assigns: assigns, private: %{open_api_spex: %{params: %{id: id} = params}}} = conn, + _ + ) do with user = assigns[:user], %Activity{} = activity <- Activity.get_by_id_with_object(id), true <- Visibility.visible_for_user?(activity, user) do @@ -211,7 +249,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "GET /api/v1/statuses/:id/source" - def show_source(%{assigns: assigns} = conn, %{id: id} = _params) do + def show_source(%{assigns: assigns, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do with user = assigns[:user], %Activity{} = activity <- Activity.get_by_id_with_object(id), true <- Visibility.visible_for_user?(activity, user) do @@ -225,7 +263,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "PUT /api/v1/statuses/:id" - def update(%{assigns: %{user: user}, body_params: body_params} = conn, %{id: id} = params) do + def update( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{body_params: body_params, params: %{id: id} = params}} + } = conn, + _ + ) do with {_, %Activity{}} = {_, activity} <- {:activity, Activity.get_by_id_with_object(id)}, {_, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, {_, true} <- {:is_create, activity.data["type"] == "Create"}, @@ -248,7 +292,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "GET /api/v1/statuses/:id" - def show(%{assigns: %{user: user}} = conn, %{id: id} = params) do + def show( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id} = params}}} = + conn, + _ + ) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), true <- Visibility.visible_for_user?(activity, user) do try_render(conn, "show.json", @@ -263,7 +311,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "DELETE /api/v1/statuses/:id" - def delete(%{assigns: %{user: user}} = conn, %{id: id}) do + def delete(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), {:ok, %Activity{}} <- CommonAPI.delete(id, user) do try_render(conn, "show.json", @@ -278,7 +326,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/reblog" - def reblog(%{assigns: %{user: user}, body_params: params} = conn, %{id: ap_id_or_id}) do + def reblog( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{body_params: params, params: %{id: ap_id_or_id}}} + } = conn, + _ + ) do with {:ok, announce} <- CommonAPI.repeat(ap_id_or_id, user, params), %Activity{} = announce <- Activity.normalize(announce.data) do try_render(conn, "show.json", %{activity: announce, for: user, as: :activity}) @@ -286,7 +340,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/unreblog" - def unreblog(%{assigns: %{user: user}} = conn, %{id: activity_id}) do + def unreblog( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: activity_id}}}} = + conn, + _ + ) do with {:ok, _unannounce} <- CommonAPI.unrepeat(activity_id, user), %Activity{} = activity <- Activity.get_by_id(activity_id) do try_render(conn, "show.json", %{activity: activity, for: user, as: :activity}) @@ -294,7 +352,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/favourite" - def favourite(%{assigns: %{user: user}} = conn, %{id: activity_id}) do + def favourite( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: activity_id}}}} = + conn, + _ + ) do with {:ok, _fav} <- CommonAPI.favorite(user, activity_id), %Activity{} = activity <- Activity.get_by_id(activity_id) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) @@ -302,7 +364,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/unfavourite" - def unfavourite(%{assigns: %{user: user}} = conn, %{id: activity_id}) do + def unfavourite( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: activity_id}}}} = + conn, + _ + ) do with {:ok, _unfav} <- CommonAPI.unfavorite(activity_id, user), %Activity{} = activity <- Activity.get_by_id(activity_id) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) @@ -310,7 +376,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/pin" - def pin(%{assigns: %{user: user}} = conn, %{id: ap_id_or_id}) do + def pin( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: ap_id_or_id}}}} = + conn, + _ + ) do with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) else @@ -329,14 +399,21 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/unpin" - def unpin(%{assigns: %{user: user}} = conn, %{id: ap_id_or_id}) do + def unpin( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: ap_id_or_id}}}} = + conn, + _ + ) do with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) end end @doc "POST /api/v1/statuses/:id/bookmark" - def bookmark(%{assigns: %{user: user}} = conn, %{id: id}) do + def bookmark( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + _ + ) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), %User{} = user <- User.get_cached_by_nickname(user.nickname), true <- Visibility.visible_for_user?(activity, user), @@ -346,7 +423,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/unbookmark" - def unbookmark(%{assigns: %{user: user}} = conn, %{id: id}) do + def unbookmark( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + _ + ) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), %User{} = user <- User.get_cached_by_nickname(user.nickname), true <- Visibility.visible_for_user?(activity, user), @@ -356,7 +436,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/mute" - def mute_conversation(%{assigns: %{user: user}, body_params: params} = conn, %{id: id}) do + def mute_conversation( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{body_params: params, params: %{id: id}}} + } = conn, + _ + ) do with %Activity{} = activity <- Activity.get_by_id(id), {:ok, activity} <- CommonAPI.add_mute(user, activity, params) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) @@ -364,7 +450,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/unmute" - def unmute_conversation(%{assigns: %{user: user}} = conn, %{id: id}) do + def unmute_conversation( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{params: %{id: id}}} + } = conn, + _ + ) do with %Activity{} = activity <- Activity.get_by_id(id), {:ok, activity} <- CommonAPI.remove_mute(user, activity) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) @@ -373,7 +465,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do @doc "GET /api/v1/statuses/:id/card" @deprecated "https://github.com/tootsuite/mastodon/pull/11213" - def card(%{assigns: %{user: user}} = conn, %{id: status_id}) do + def card( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: status_id}}}} = conn, + _ + ) do with %Activity{} = activity <- Activity.get_by_id(status_id), true <- Visibility.visible_for_user?(activity, user) do data = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) @@ -384,7 +479,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "GET /api/v1/statuses/:id/favourited_by" - def favourited_by(%{assigns: %{user: user}} = conn, %{id: id}) do + def favourited_by( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + _ + ) do 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)}, @@ -405,7 +503,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "GET /api/v1/statuses/:id/reblogged_by" - def reblogged_by(%{assigns: %{user: user}} = conn, %{id: id}) do + def reblogged_by( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + _ + ) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, %Object{data: %{"announcements" => announces, "id" => ap_id}} <- @@ -437,7 +538,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "GET /api/v1/statuses/:id/context" - def context(%{assigns: %{user: user}} = conn, %{id: id}) do + def context( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + _ + ) do with %Activity{} = activity <- Activity.get_by_id(id) do activities = ActivityPub.fetch_activities_for_context(activity.data["context"], %{ @@ -451,7 +555,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "GET /api/v1/favourites" - def favourites(%{assigns: %{user: %User{} = user}} = conn, params) do + def favourites( + %{assigns: %{user: %User{} = user}, private: %{open_api_spex: %{params: params}}} = conn, + _ + ) do activities = ActivityPub.fetch_favourites(user, params) conn @@ -464,7 +571,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "GET /api/v1/bookmarks" - def bookmarks(%{assigns: %{user: user}} = conn, params) do + def bookmarks(%{assigns: %{user: user}, private: %{open_api_spex: %{params: params}}} = conn, _) do user = User.get_cached_by_id(user.id) bookmarks = diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -194,6 +194,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do end defp do_render("show.json", %{user: user} = opts) do + self = opts[:for] == user + user = User.sanitize_html(user, User.html_filter_policy(opts[:for])) display_name = user.name || user.nickname @@ -203,16 +205,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true) following_count = - if !user.hide_follows_count or !user.hide_follows or opts[:for] == user, + if !user.hide_follows_count or !user.hide_follows or self, do: user.following_count, else: 0 followers_count = - if !user.hide_followers_count or !user.hide_followers or opts[:for] == user, + if !user.hide_followers_count or !user.hide_followers or self, do: user.follower_count, else: 0 - bot = user.actor_type == "Service" + bot = bot?(user) emojis = Enum.map(user.emoji, fn {shortcode, raw_url} -> @@ -468,4 +470,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do defp image_url(%{"url" => [%{"href" => href} | _]}), do: href defp image_url(_), do: nil + + defp bot?(user) do + # Because older and/or Mastodon clients may not recognize a Group actor properly, + # and currently the group actor can only boost things, we should let these clients + # think groups are bots. + # See https://git.pleroma.social/pleroma/pleroma-meta/-/issues/14 + user.actor_type == "Service" || user.actor_type == "Group" + end end diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -28,6 +28,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do |> to_string, registrations: Keyword.get(instance, :registrations_open), approval_required: Keyword.get(instance, :account_approval_required), + contact_account: contact_account(Keyword.get(instance, :contact_username)), configuration: configuration(), # Extra (not present in Mastodon): max_toot_chars: Keyword.get(instance, :limit), @@ -40,6 +41,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do background_image: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image), shout_limit: Config.get([:shout, :limit]), description_limit: Keyword.get(instance, :description_limit), + chat_limit: Keyword.get(instance, :chat_limit), pleroma: pleroma_configuration(instance) }) end @@ -62,11 +64,12 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do registrations: %{ enabled: Keyword.get(instance, :registrations_open), approval_required: Keyword.get(instance, :account_approval_required), - message: nil + message: nil, + url: nil }, contact: %{ email: Keyword.get(instance, :email), - account: nil + account: contact_account(Keyword.get(instance, :contact_username)) }, # Extra (not present in Mastodon): pleroma: pleroma_configuration2(instance) @@ -139,7 +142,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do if Config.get([:instance, :profile_directory]) do "profile_directory" end, - "pleroma:get:main/ostatus" + "pleroma:get:main/ostatus", + "pleroma:group_actors" ] |> Enum.filter(& &1) end @@ -180,15 +184,35 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do } end + defp contact_account(nil), do: nil + + defp contact_account("@" <> username) do + contact_account(username) + end + + defp contact_account(username) do + user = Pleroma.User.get_cached_by_nickname(username) + + if user do + Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user, for: nil}) + else + nil + end + end + defp configuration do %{ + accounts: %{ + max_featured_tags: 0 + }, statuses: %{ max_characters: Config.get([:instance, :limit]), max_media_attachments: Config.get([:instance, :max_media_attachments]) }, media_attachments: %{ image_size_limit: Config.get([:instance, :upload_limit]), - video_size_limit: Config.get([:instance, :upload_limit]) + video_size_limit: Config.get([:instance, :upload_limit]), + supported_mime_types: ["application/octet-stream"] }, polls: %{ max_options: Config.get([:instance, :poll_limits, :max_options]), @@ -202,7 +226,13 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do defp configuration2 do configuration() |> Map.merge(%{ - urls: %{streaming: Pleroma.Web.Endpoint.websocket_url()} + urls: %{ + streaming: Pleroma.Web.Endpoint.websocket_url(), + status: Config.get([:instance, :status_page]) + }, + vapid: %{ + public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) + } }) end @@ -235,6 +265,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do banner_upload_limit: Keyword.get(instance, :banner_upload_limit), background_image: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image), + chat_limit: Keyword.get(instance, :chat_limit), description_limit: Keyword.get(instance, :description_limit), shout_limit: Config.get([:shout, :limit]) }) diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex @@ -21,7 +21,10 @@ defmodule Pleroma.Web.MastodonAPI.PollView do votes_count: votes_count, voters_count: voters_count(object), options: options, - emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"]) + emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"]), + pleroma: %{ + non_anonymous: object.data["nonAnonymous"] || false + } } if params[:for] do diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -796,8 +796,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do URI.merge(page_url_data, image_url_data) |> to_string end - defp build_image_url(_, _), do: nil - defp get_source_text(%{"content" => content} = _source) do content end diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -11,28 +11,21 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do alias Pleroma.Web.Streamer alias Pleroma.Web.StreamerView - @behaviour :cowboy_websocket + @behaviour Phoenix.Socket.Transport # Client ping period. @tick :timer.seconds(30) - # Cowboy timeout period. - @timeout :timer.seconds(60) - # Hibernate every X messages - @hibernate_every 100 - - def init(%{qs: qs} = req, state) do - with params <- Enum.into(:cow_qs.parse_qs(qs), %{}), - sec_websocket <- :cowboy_req.header("sec-websocket-protocol", req, nil), - access_token <- Map.get(params, "access_token"), - {:ok, user, oauth_token} <- authenticate_request(access_token, sec_websocket), - {:ok, topic} <- Streamer.get_topic(params["stream"], user, oauth_token, params) do - req = - if sec_websocket do - :cowboy_req.set_resp_header("sec-websocket-protocol", sec_websocket, req) - else - req - end + @impl Phoenix.Socket.Transport + def child_spec(_opts), do: :ignore + + # This only prepares the connection and is not in the process yet + @impl Phoenix.Socket.Transport + def connect(%{params: params} = transport_info) do + with access_token <- Map.get(params, "access_token"), + {:ok, user, oauth_token} <- authenticate_request(access_token), + {:ok, topic} <- + Streamer.get_topic(params["stream"], user, oauth_token, params) do topics = if topic do [topic] @@ -40,41 +33,40 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do [] end - {:cowboy_websocket, req, - %{user: user, topics: topics, oauth_token: oauth_token, count: 0, timer: nil}, - %{idle_timeout: @timeout}} + state = %{ + user: user, + topics: topics, + oauth_token: oauth_token, + count: 0, + timer: nil + } + + {:ok, state} else {:error, :bad_topic} -> - Logger.debug("#{__MODULE__} bad topic #{inspect(req)}") - req = :cowboy_req.reply(404, req) - {:ok, req, state} + Logger.debug("#{__MODULE__} bad topic #{inspect(transport_info)}") + + {:error, :bad_topic} {:error, :unauthorized} -> - Logger.debug("#{__MODULE__} authentication error: #{inspect(req)}") - req = :cowboy_req.reply(401, req) - {:ok, req, state} + Logger.debug("#{__MODULE__} authentication error: #{inspect(transport_info)}") + {:error, :unauthorized} end end - def websocket_init(state) do - Logger.debug( - "#{__MODULE__} accepted websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topics #{state.topics}" - ) - + # All subscriptions/links and messages cannot be created + # until the processed is launched with init/1 + @impl Phoenix.Socket.Transport + def init(state) do Enum.each(state.topics, fn topic -> Streamer.add_socket(topic, state.oauth_token) end) - {:ok, %{state | timer: timer()}} - end - # Client's Pong frame. - def websocket_handle(:pong, state) do - if state.timer, do: Process.cancel_timer(state.timer) - {:ok, %{state | timer: timer()}} - end + Process.send_after(self(), :ping, @tick) - # We only receive pings for now - def websocket_handle(:ping, state), do: {:ok, state} + {:ok, state} + end - def websocket_handle({:text, text}, state) do + @impl Phoenix.Socket.Transport + def handle_in({text, [opcode: :text]}, state) do with {:ok, %{} = event} <- Jason.decode(text) do handle_client_event(event, state) else @@ -84,50 +76,47 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do end end - def websocket_handle(frame, state) do + def handle_in(frame, state) do Logger.error("#{__MODULE__} received frame: #{inspect(frame)}") {:ok, state} end - def websocket_info({:render_with_user, view, template, item, topic}, state) do + @impl Phoenix.Socket.Transport + def handle_info({:render_with_user, view, template, item, topic}, state) do user = %User{} = User.get_cached_by_ap_id(state.user.ap_id) unless Streamer.filtered_by_user?(user, item) do - websocket_info({:text, view.render(template, item, user, topic)}, %{state | user: user}) + message = view.render(template, item, user, topic) + {:push, {:text, message}, %{state | user: user}} else {:ok, state} end end - def websocket_info({:text, message}, state) do - # If the websocket processed X messages, force an hibernate/GC. - # We don't hibernate at every message to balance CPU usage/latency with RAM usage. - if state.count > @hibernate_every do - {:reply, {:text, message}, %{state | count: 0}, :hibernate} - else - {:reply, {:text, message}, %{state | count: state.count + 1}} - end + def handle_info({:text, text}, state) do + {:push, {:text, text}, state} end - # Ping tick. We don't re-queue a timer there, it is instead queued when :pong is received. - # As we hibernate there, reset the count to 0. - # If the client misses :pong, Cowboy will automatically timeout the connection after - # `@idle_timeout`. - def websocket_info(:tick, state) do - {:reply, :ping, %{state | timer: nil, count: 0}, :hibernate} + def handle_info(:ping, state) do + Process.send_after(self(), :ping, @tick) + + {:push, {:ping, ""}, state} end - def websocket_info(:close, state) do - {:stop, state} + def handle_info(:close, state) do + {:stop, {:closed, 'connection closed by server'}, state} end - # State can be `[]` only in case we terminate before switching to websocket, - # we already log errors for these cases in `init/1`, so just do nothing here - def terminate(_reason, _req, []), do: :ok + def handle_info(msg, state) do + Logger.debug("#{__MODULE__} received info: #{inspect(msg)}") - def terminate(reason, _req, state) do + {:ok, state} + end + + @impl Phoenix.Socket.Transport + def terminate(reason, state) do Logger.debug( - "#{__MODULE__} terminating websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topics #{state.topics || "?"}: #{inspect(reason)}" + "#{__MODULE__} terminating websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topics #{state.topics || "?"}: #{inspect(reason)})" ) Enum.each(state.topics, fn topic -> Streamer.remove_socket(topic) end) @@ -135,16 +124,13 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do end # Public streams without authentication. - defp authenticate_request(nil, nil) do + defp authenticate_request(nil) do {:ok, nil, nil} end # Authenticated streams. - defp authenticate_request(access_token, sec_websocket) do - token = access_token || sec_websocket - - with true <- is_bitstring(token), - oauth_token = %Token{user_id: user_id} <- Repo.get_by(Token, token: token), + defp authenticate_request(access_token) do + with oauth_token = %Token{user_id: user_id} <- Repo.get_by(Token, token: access_token), user = %User{} <- User.get_cached_by_id(user_id) do {:ok, user, oauth_token} else @@ -152,36 +138,32 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do end end - defp timer do - Process.send_after(self(), :tick, @tick) - end - defp handle_client_event(%{"type" => "subscribe", "stream" => _topic} = params, state) do with {_, {:ok, topic}} <- {:topic, Streamer.get_topic(params["stream"], state.user, state.oauth_token, params)}, {_, false} <- {:subscribed, topic in state.topics} do Streamer.add_socket(topic, state.oauth_token) - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{type: "subscribe", result: "success"})} - ], %{state | topics: [topic | state.topics]}} + message = + StreamerView.render("pleroma_respond.json", %{type: "subscribe", result: "success"}) + + {:reply, :ok, {:text, message}, %{state | topics: [topic | state.topics]}} else {:subscribed, true} -> - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{type: "subscribe", result: "ignored"})} - ], state} + message = + StreamerView.render("pleroma_respond.json", %{type: "subscribe", result: "ignored"}) + + {:reply, :error, {:text, message}, state} {:topic, {:error, error}} -> - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{ - type: "subscribe", - result: "error", - error: error - })} - ], state} + message = + StreamerView.render("pleroma_respond.json", %{ + type: "subscribe", + result: "error", + error: error + }) + + {:reply, :error, {:text, message}, state} end end @@ -191,26 +173,26 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do {_, true} <- {:subscribed, topic in state.topics} do Streamer.remove_socket(topic) - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{type: "unsubscribe", result: "success"})} - ], %{state | topics: List.delete(state.topics, topic)}} + message = + StreamerView.render("pleroma_respond.json", %{type: "unsubscribe", result: "success"}) + + {:reply, :ok, {:text, message}, %{state | topics: List.delete(state.topics, topic)}} else {:subscribed, false} -> - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{type: "unsubscribe", result: "ignored"})} - ], state} + message = + StreamerView.render("pleroma_respond.json", %{type: "unsubscribe", result: "ignored"}) + + {:reply, :error, {:text, message}, state} {:topic, {:error, error}} -> - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{ - type: "unsubscribe", - result: "error", - error: error - })} - ], state} + message = + StreamerView.render("pleroma_respond.json", %{ + type: "unsubscribe", + result: "error", + error: error + }) + + {:reply, :error, {:text, message}, state} end end @@ -219,39 +201,47 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do state ) do with {:auth, nil, nil} <- {:auth, state.user, state.oauth_token}, - {:ok, user, oauth_token} <- authenticate_request(access_token, nil) do - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{ - type: "pleroma:authenticate", - result: "success" - })} - ], %{state | user: user, oauth_token: oauth_token}} + {:ok, user, oauth_token} <- authenticate_request(access_token) do + message = + StreamerView.render("pleroma_respond.json", %{ + type: "pleroma:authenticate", + result: "success" + }) + + {:reply, :ok, {:text, message}, %{state | user: user, oauth_token: oauth_token}} else {:auth, _, _} -> - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{ - type: "pleroma:authenticate", - result: "error", - error: :already_authenticated - })} - ], state} + message = + StreamerView.render("pleroma_respond.json", %{ + type: "pleroma:authenticate", + result: "error", + error: :already_authenticated + }) + + {:reply, :error, {:text, message}, state} _ -> - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{ - type: "pleroma:authenticate", - result: "error", - error: :unauthorized - })} - ], state} + message = + StreamerView.render("pleroma_respond.json", %{ + type: "pleroma:authenticate", + result: "error", + error: :unauthorized + }) + + {:reply, :error, {:text, message}, state} end end defp handle_client_event(params, state) do Logger.error("#{__MODULE__} received unknown event: #{inspect(params)}") - {[], state} + {:ok, state} + end + + def handle_error(conn, :unauthorized) do + Plug.Conn.send_resp(conn, 401, "Unauthorized") + end + + def handle_error(conn, _reason) do + Plug.Conn.send_resp(conn, 404, "Not Found") end end diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -56,7 +56,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do media_proxy_url = MediaProxy.url(url) with {:ok, %{status: status} = head_response} when status in 200..299 <- - Pleroma.HTTP.request("HEAD", media_proxy_url, [], [], pool: :media) do + Pleroma.HTTP.request(:head, media_proxy_url, "", [], pool: :media) do content_type = Tesla.get_header(head_response, "content-type") content_length = Tesla.get_header(head_response, "content-length") content_length = content_length && String.to_integer(content_length) diff --git a/lib/pleroma/web/nodeinfo/nodeinfo.ex b/lib/pleroma/web/nodeinfo/nodeinfo.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.Nodeinfo.Nodeinfo do alias Pleroma.Config alias Pleroma.Stats alias Pleroma.User - alias Pleroma.Web.Federator.Publisher + alias Pleroma.Web.ActivityPub.Publisher alias Pleroma.Web.MastodonAPI.InstanceView # returns a nodeinfo 2.0 map, since 2.1 just adds a repository field diff --git a/lib/pleroma/web/o_auth/authorization.ex b/lib/pleroma/web/o_auth/authorization.ex @@ -28,7 +28,7 @@ defmodule Pleroma.Web.OAuth.Authorization do end @spec create_authorization(App.t(), User.t() | %{}, [String.t()] | nil) :: - {:ok, Authorization.t()} | {:error, Changeset.t()} + {:ok, Authorization.t()} | {:error, Ecto.Changeset.t()} def create_authorization(%App{} = app, %User{} = user, scopes \\ nil) do %{ scopes: scopes || app.scopes, @@ -39,7 +39,7 @@ defmodule Pleroma.Web.OAuth.Authorization do |> Repo.insert() end - @spec create_changeset(map()) :: Changeset.t() + @spec create_changeset(map()) :: Ecto.Changeset.t() def create_changeset(attrs \\ %{}) do %Authorization{} |> cast(attrs, [:user_id, :app_id, :scopes, :valid_until]) @@ -58,7 +58,7 @@ defmodule Pleroma.Web.OAuth.Authorization do put_change(changeset, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), lifespan)) end - @spec use_changeset(Authtorizatiton.t(), map()) :: Changeset.t() + @spec use_changeset(Authorization.t(), map()) :: Ecto.Changeset.t() def use_changeset(%Authorization{} = auth, params) do auth |> cast(params, [:used]) @@ -66,7 +66,7 @@ defmodule Pleroma.Web.OAuth.Authorization do end @spec use_token(Authorization.t()) :: - {:ok, Authorization.t()} | {:error, Changeset.t()} | {:error, String.t()} + {:ok, Authorization.t()} | {:error, Ecto.Changeset.t()} | {:error, String.t()} def use_token(%Authorization{used: false, valid_until: valid_until} = auth) do if NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) < 0 do Repo.update(use_changeset(auth, %{used: true})) diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex @@ -310,7 +310,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do after_token_exchange(conn, %{token: token}) else _error -> - handle_token_exchange_error(conn, :invalid_credentails) + handle_token_exchange_error(conn, :invalid_credentials) end end @@ -610,13 +610,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do end end - @spec validate_scopes(App.t(), map() | list()) :: + @spec validate_scopes(App.t(), list()) :: {:ok, list()} | {:error, :missing_scopes | :unsupported_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 diff --git a/lib/pleroma/web/o_auth/token.ex b/lib/pleroma/web/o_auth/token.ex @@ -56,7 +56,8 @@ defmodule Pleroma.Web.OAuth.Token do |> Repo.find_resource() end - @spec exchange_token(App.t(), Authorization.t()) :: {:ok, Token.t()} | {:error, Changeset.t()} + @spec exchange_token(App.t(), Authorization.t()) :: + {:ok, Token.t()} | {:error, Ecto.Changeset.t()} def exchange_token(app, auth) do with {:ok, auth} <- Authorization.use_token(auth), true <- auth.app_id == app.id do @@ -95,7 +96,7 @@ defmodule Pleroma.Web.OAuth.Token do |> validate_required([:valid_until]) end - @spec create(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Changeset.t()} + @spec create(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Ecto.Changeset.t()} def create(%App{} = app, %User{} = user, attrs \\ %{}) do with {:ok, token} <- do_create(app, user, attrs) do if Pleroma.Config.get([:oauth2, :clean_expired_tokens]) do @@ -137,9 +138,9 @@ defmodule Pleroma.Web.OAuth.Token do |> Repo.all() end - def is_expired?(%__MODULE__{valid_until: valid_until}) do + def expired?(%__MODULE__{valid_until: valid_until}) do NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) > 0 end - def is_expired?(_), do: false + def expired?(_), do: false end diff --git a/lib/pleroma/web/o_auth/token/query.ex b/lib/pleroma/web/o_auth/token/query.ex @@ -9,10 +9,10 @@ defmodule Pleroma.Web.OAuth.Token.Query do import Ecto.Query, only: [from: 2] - @type query :: Ecto.Queryable.t() | Token.t() - alias Pleroma.Web.OAuth.Token + @type query :: Ecto.Queryable.t() | Token.t() + @spec get_by_refresh_token(query, String.t()) :: query def get_by_refresh_token(query \\ Token, refresh_token) do from(q in query, where: q.refresh_token == ^refresh_token) diff --git a/lib/pleroma/web/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex @@ -37,7 +37,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do with id <- Endpoint.url() <> conn.request_path, {_, %Activity{} = activity} <- {:activity, Activity.get_create_by_object_ap_id_with_object(id)}, - {_, true} <- {:public?, Visibility.is_public?(activity)} do + {_, true} <- {:public?, Visibility.public?(activity)} do redirect(conn, to: "/notice/#{activity.id}") else reason when reason in [{:public?, false}, {:activity, nil}] -> @@ -56,7 +56,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do def activity(conn, _params) do with id <- Endpoint.url() <> conn.request_path, {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)}, - {_, true} <- {:public?, Visibility.is_public?(activity)} do + {_, true} <- {:public?, Visibility.public?(activity)} do redirect(conn, to: "/notice/#{activity.id}") else reason when reason in [{:public?, false}, {:activity, nil}] -> @@ -69,7 +69,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)}, - {_, true} <- {:public?, Visibility.is_public?(activity)}, + {_, true} <- {:public?, Visibility.public?(activity)}, %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do cond do format in ["json", "activity+json"] -> @@ -106,13 +106,12 @@ defmodule Pleroma.Web.OStatus.OStatusController do # Returns an HTML embedded <audio> or <video> player suitable for embed iframes. def notice_player(conn, %{"id" => id}) do with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id), - true <- Visibility.is_public?(activity), + true <- Visibility.public?(activity), {_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)}, %Object{} = object <- Object.normalize(activity, fetch: false), %{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object, true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do conn - |> put_layout(:metadata_player) |> put_resp_header("x-frame-options", "ALLOW") |> put_resp_header( "content-security-policy", diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex @@ -38,14 +38,24 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do %{scopes: ["read:chats"]} when action in [:messages, :index, :index2, :show] ) - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ChatOperation - def delete_message(%{assigns: %{user: %{id: user_id} = user}} = conn, %{ - message_id: message_id, - id: chat_id - }) do + def delete_message( + %{ + assigns: %{user: %{id: user_id} = user}, + private: %{ + open_api_spex: %{ + params: %{ + message_id: message_id, + id: chat_id + } + } + } + } = conn, + _ + ) do with %MessageReference{} = cm_ref <- MessageReference.get_by_id(message_id), ^chat_id <- to_string(cm_ref.chat_id), @@ -72,11 +82,14 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do defp remove_or_delete(cm_ref, _), do: MessageReference.delete(cm_ref) def post_chat_message( - %{body_params: params, assigns: %{user: user}} = conn, - %{id: id} + %{ + private: %{open_api_spex: %{body_params: params, params: %{id: id}}}, + assigns: %{user: user} + } = conn, + _ ) do with {:ok, chat} <- Chat.get_by_user_and_id(user, id), - %User{} = recipient <- User.get_cached_by_ap_id(chat.recipient), + {_, %User{} = recipient} <- {:user, User.get_cached_by_ap_id(chat.recipient)}, {:ok, activity} <- CommonAPI.post_chat_message(user, recipient, params[:content], media_id: params[:media_id], @@ -97,12 +110,20 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do conn |> put_status(:bad_request) |> json(%{error: message}) + + {:user, nil} -> + conn + |> put_status(:bad_request) + |> json(%{error: "Recipient does not exist"}) end end def mark_message_as_read( - %{assigns: %{user: %{id: user_id}}} = conn, - %{id: chat_id, message_id: message_id} + %{ + assigns: %{user: %{id: user_id}}, + private: %{open_api_spex: %{params: %{id: chat_id, message_id: message_id}}} + } = conn, + _ ) do with %MessageReference{} = cm_ref <- MessageReference.get_by_id(message_id), ^chat_id <- to_string(cm_ref.chat_id), @@ -115,8 +136,16 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do end def mark_as_read( - %{body_params: %{last_read_id: last_read_id}, assigns: %{user: user}} = conn, - %{id: id} + %{ + assigns: %{user: user}, + private: %{ + open_api_spex: %{ + body_params: %{last_read_id: last_read_id}, + params: %{id: id} + } + } + } = conn, + _ ) do with {:ok, chat} <- Chat.get_by_user_and_id(user, id), {_n, _} <- MessageReference.set_all_seen_for_chat(chat, last_read_id) do @@ -124,7 +153,13 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do end end - def messages(%{assigns: %{user: user}} = conn, %{id: id} = params) do + def messages( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{params: %{id: id} = params}} + } = conn, + _ + ) do with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do chat_message_refs = chat @@ -138,7 +173,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do end end - def index(%{assigns: %{user: user}} = conn, params) do + def index(%{assigns: %{user: user}, private: %{open_api_spex: %{params: params}}} = conn, _) do chats = index_query(user, params) |> Repo.all() @@ -146,7 +181,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do render(conn, "index.json", chats: chats) end - def index2(%{assigns: %{user: user}} = conn, params) do + def index2(%{assigns: %{user: user}, private: %{open_api_spex: %{params: params}}} = conn, _) do chats = index_query(user, params) |> Pagination.fetch_paginated(params) @@ -166,14 +201,14 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do |> where([c], c.recipient not in ^exclude_users) end - def create(%{assigns: %{user: user}} = conn, %{id: id}) do + def create(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do with %User{ap_id: recipient} <- User.get_cached_by_id(id), {:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do render(conn, "show.json", chat: chat) end end - def show(%{assigns: %{user: user}} = conn, %{id: id}) do + def show(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do render(conn, "show.json", chat: chat) end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do alias Pleroma.Emoji.Pack alias Pleroma.Web.ApiSpec - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug( Pleroma.Web.Plugs.OAuthScopesPlug, @@ -22,7 +22,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do defdelegate open_api_operation(action), to: ApiSpec.PleromaEmojiFileOperation - def create(%{body_params: params} = conn, %{name: pack_name}) do + def create( + %{private: %{open_api_spex: %{body_params: params, params: %{name: pack_name}}}} = conn, + _ + ) do filename = params[:filename] || get_filename(params[:file]) shortcode = params[:shortcode] || Path.basename(filename, Path.extname(filename)) @@ -49,7 +52,17 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do end end - def update(%{body_params: %{shortcode: shortcode} = params} = conn, %{name: pack_name}) do + def update( + %{ + private: %{ + open_api_spex: %{ + body_params: %{shortcode: shortcode} = params, + params: %{name: pack_name} + } + } + } = conn, + _ + ) do new_shortcode = params[:new_shortcode] new_filename = params[:new_filename] force = params[:force] @@ -80,7 +93,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do end end - def delete(conn, %{name: pack_name, shortcode: shortcode}) do + def delete( + %{private: %{open_api_spex: %{params: %{name: pack_name, shortcode: shortcode}}}} = conn, + _ + ) do with {:ok, pack} <- Pack.load_pack(pack_name), {:ok, pack} <- Pack.delete_file(pack, shortcode) do json(conn, pack.files) diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do alias Pleroma.Emoji.Pack - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug( Pleroma.Web.Plugs.OAuthScopesPlug, @@ -26,7 +26,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation - def remote(conn, params) do + def remote(%{private: %{open_api_spex: %{params: params}}} = conn, _) do with {:ok, packs} <- Pack.list_remote(url: params.url, page_size: params.page_size, page: params.page) do json(conn, packs) @@ -38,7 +38,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do end end - def index(conn, params) do + def index(%{private: %{open_api_spex: %{params: params}}} = conn, _) do emoji_path = [:instance, :static_dir] |> Pleroma.Config.get!() @@ -61,7 +61,11 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do end end - def show(conn, %{name: name, page: page, page_size: page_size}) do + def show( + %{private: %{open_api_spex: %{params: %{name: name, page: page, page_size: page_size}}}} = + conn, + _ + ) do name = String.trim(name) with {:ok, pack} <- Pack.show(name: name, page: page, page_size: page_size) do @@ -90,7 +94,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do end end - def archive(conn, %{name: name}) do + def archive(%{private: %{open_api_spex: %{params: %{name: name}}}} = conn, _) do with {:ok, archive} <- Pack.get_archive(name) do send_download(conn, {:binary, archive}, filename: "#{name}.zip") else @@ -109,7 +113,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do end end - def download(%{body_params: %{url: url, name: name} = params} = conn, _) do + def download( + %{private: %{open_api_spex: %{body_params: %{url: url, name: name} = params}}} = conn, + _ + ) do with {:ok, _pack} <- Pack.download(name, url, params[:as]) do json(conn, "ok") else @@ -130,7 +137,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do end end - def create(conn, %{name: name}) do + def create(%{private: %{open_api_spex: %{params: %{name: name}}}} = conn, _) do name = String.trim(name) with {:ok, _pack} <- Pack.create(name) do @@ -159,7 +166,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do end end - def delete(conn, %{name: name}) do + def delete(%{private: %{open_api_spex: %{params: %{name: name}}}} = conn, _) do name = String.trim(name) with {:ok, deleted} when deleted != [] <- Pack.delete(name) do @@ -184,7 +191,11 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do end end - def update(%{body_params: %{metadata: metadata}} = conn, %{name: name}) do + def update( + %{private: %{open_api_spex: %{body_params: %{metadata: metadata}, params: %{name: name}}}} = + conn, + _ + ) do with {:ok, pack} <- Pack.update_metadata(name, metadata) do json(conn, pack.pack) else diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do alias Pleroma.Web.Plugs.OAuthScopesPlug plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:update]) - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action == :show) plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action != :show) @@ -22,9 +22,13 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do end @doc "PUT /api/v1/pleroma/mascot" - def update(%{assigns: %{user: user}, body_params: %{file: file}} = conn, _) do + def update( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: %{file: file}}}} = + conn, + _ + ) do with {:content_type, "image" <> _} <- {:content_type, file.content_type}, - {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)) do + {_, {:ok, object}} <- {:upload, ActivityPub.upload(file, actor: User.ap_id(user))} do attachment = render_attachment(object) {:ok, _user} = User.mascot_update(user, attachment) @@ -32,6 +36,9 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do else {:content_type, _} -> render_error(conn, :unsupported_media_type, "mascots can only be images") + + {:upload, {:error, _}} -> + render_error(conn, :error, "error uploading file") end end diff --git a/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex b/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.PleromaAPI.NotificationController do alias Pleroma.Notification - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug( Pleroma.Web.Plugs.OAuthScopesPlug, @@ -16,7 +16,13 @@ defmodule Pleroma.Web.PleromaAPI.NotificationController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaNotificationOperation - def mark_as_read(%{assigns: %{user: user}, body_params: %{id: notification_id}} = conn, _) do + def mark_as_read( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{body_params: %{id: notification_id}}} + } = conn, + _ + ) do with {:ok, notification} <- Notification.read_one(user, notification_id) do render(conn, "show.json", notification: notification, for: user) else @@ -27,7 +33,11 @@ defmodule Pleroma.Web.PleromaAPI.NotificationController do end end - def mark_as_read(%{assigns: %{user: user}, body_params: %{max_id: max_id}} = conn, _) do + def mark_as_read( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: %{max_id: max_id}}}} = + conn, + _ + ) do notifications = user |> Notification.set_read_up_to(max_id) diff --git a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex @@ -15,14 +15,21 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks) plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action == :mutes) - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) defdelegate open_api_operation(action), to: ApiSpec.UserImportOperation - def follow(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do - follow(%Plug.Conn{conn | body_params: %{list: File.read!(path)}}, %{}) + def follow( + %{private: %{open_api_spex: %{body_params: %{list: %Plug.Upload{path: path}}}}} = conn, + _ + ) do + list = File.read!(path) + do_follow(conn, list) end - def follow(%{assigns: %{user: follower}, body_params: %{list: list}} = conn, _) do + def follow(%{private: %{open_api_spex: %{body_params: %{list: list}}}} = conn, _), + do: do_follow(conn, list) + + def do_follow(%{assigns: %{user: follower}} = conn, list) do identifiers = list |> String.split("\n") @@ -35,20 +42,34 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do json(conn, "job started") end - def blocks(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do - blocks(%Plug.Conn{conn | body_params: %{list: File.read!(path)}}, %{}) + def blocks( + %{private: %{open_api_spex: %{body_params: %{list: %Plug.Upload{path: path}}}}} = conn, + _ + ) do + list = File.read!(path) + do_block(conn, list) end - def blocks(%{assigns: %{user: blocker}, body_params: %{list: list}} = conn, _) do + def blocks(%{private: %{open_api_spex: %{body_params: %{list: list}}}} = conn, _), + do: do_block(conn, list) + + defp do_block(%{assigns: %{user: blocker}} = conn, list) do User.Import.blocks_import(blocker, prepare_user_identifiers(list)) json(conn, "job started") end - def mutes(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do - mutes(%Plug.Conn{conn | body_params: %{list: File.read!(path)}}, %{}) + def mutes( + %{private: %{open_api_spex: %{body_params: %{list: %Plug.Upload{path: path}}}}} = conn, + _ + ) do + list = File.read!(path) + do_mute(conn, list) end - def mutes(%{assigns: %{user: user}, body_params: %{list: list}} = conn, _) do + def mutes(%{private: %{open_api_spex: %{body_params: %{list: list}}}} = conn, _), + do: do_mute(conn, list) + + defp do_mute(%{assigns: %{user: user}} = conn, list) do User.Import.mutes_import(user, prepare_user_identifiers(list)) json(conn, "job started") end diff --git a/lib/pleroma/web/plugs/cache.ex b/lib/pleroma/web/plugs/cache.ex @@ -20,7 +20,7 @@ defmodule Pleroma.Web.Plugs.Cache do - `ttl`: An expiration time (time-to-live). This value should be in milliseconds or `nil` to disable expiration. Defaults to `nil`. - `query_params`: Take URL query string into account (`true`), ignore it (`false`) or limit to specific params only (list). Defaults to `true`. - - `tracking_fun`: A function that is called on successfull responses, no matter if the request is cached or not. It should accept a conn as the first argument and the value assigned to `tracking_fun_data` as the second. + - `tracking_fun`: A function that is called on successful responses, no matter if the request is cached or not. It should accept a conn as the first argument and the value assigned to `tracking_fun_data` as the second. Additionally, you can overwrite the TTL inside a controller action by assigning `cache_ttl` to the connection struct: diff --git a/lib/pleroma/web/plugs/o_auth_plug.ex b/lib/pleroma/web/plugs/o_auth_plug.ex @@ -23,14 +23,14 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do def call(conn, _) do with {:ok, token_str} <- fetch_token_str(conn) do with {:ok, user, user_token} <- fetch_user_and_token(token_str), - false <- Token.is_expired?(user_token) do + false <- Token.expired?(user_token) do conn |> assign(:token, user_token) |> assign(:user, user) else _ -> with {:ok, app, app_token} <- fetch_app_and_token(token_str), - false <- Token.is_expired?(app_token) do + false <- Token.expired?(app_token) do conn |> assign(:token, app_token) |> assign(:app, app) diff --git a/lib/pleroma/web/plugs/rate_limiter/supervisor.ex b/lib/pleroma/web/plugs/rate_limiter/supervisor.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Web.Plugs.RateLimiter.Supervisor do Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor ] - opts = [strategy: :one_for_one, name: Pleroma.Web.Streamer.Supervisor] + opts = [strategy: :one_for_one] Supervisor.init(children, opts) end end diff --git a/lib/pleroma/web/plugs/remote_ip.ex b/lib/pleroma/web/plugs/remote_ip.ex @@ -43,6 +43,6 @@ defmodule Pleroma.Web.Plugs.RemoteIp do InetCidr.v6?(InetCidr.parse_address!(proxy)) -> proxy <> "/128" end - InetCidr.parse(proxy, true) + InetCidr.parse_cidr!(proxy, true) end end diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex @@ -105,7 +105,7 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do end defp get_media(conn, unknown, _, _) do - Logger.error("#{__MODULE__}: Unknown get startegy: #{inspect(unknown)}") + Logger.error("#{__MODULE__}: Unknown get strategy: #{inspect(unknown)}") conn |> send_resp(:internal_server_error, dgettext("errors", "Internal Error")) diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex @@ -8,6 +8,8 @@ defmodule Pleroma.Web.RichMedia.Helpers do alias Pleroma.Object alias Pleroma.Web.RichMedia.Parser + @cachex Pleroma.Config.get([:cachex, :provider], Cachex) + @config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config) @options [ @@ -16,51 +18,10 @@ defmodule Pleroma.Web.RichMedia.Helpers do recv_timeout: 2_000 ] - @spec validate_page_url(URI.t() | binary()) :: :ok | :error - defp validate_page_url(page_url) when is_binary(page_url) do - validate_tld = @config_impl.get([Pleroma.Formatter, :validate_tld]) - - page_url - |> Linkify.Parser.url?(validate_tld: validate_tld) - |> parse_uri(page_url) - end - - defp validate_page_url(%URI{host: host, scheme: "https", authority: authority}) - when is_binary(authority) do - cond do - host in @config_impl.get([:rich_media, :ignore_hosts], []) -> - :error - - get_tld(host) in @config_impl.get([:rich_media, :ignore_tld], []) -> - :error - - true -> - :ok - end - end - - defp validate_page_url(_), do: :error - - defp parse_uri(true, url) do - url - |> URI.parse() - |> validate_page_url - end - - defp parse_uri(_, _), do: :error - - defp get_tld(host) do - host - |> String.split(".") - |> Enum.reverse() - |> hd - end - def fetch_data_for_object(object) do with true <- @config_impl.get([:rich_media, :enabled]), {:ok, page_url} <- HTML.extract_first_external_url_from_object(object), - :ok <- validate_page_url(page_url), {:ok, rich_media} <- Parser.parse(page_url) do %{page_url: page_url, rich_media: rich_media} else @@ -71,7 +32,24 @@ defmodule Pleroma.Web.RichMedia.Helpers do def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do with true <- @config_impl.get([:rich_media, :enabled]), %Object{} = object <- Object.normalize(activity, fetch: false) do - fetch_data_for_object(object) + if object.data["fake"] do + fetch_data_for_object(object) + else + key = "URL|#{activity.id}" + + @cachex.fetch!(:scrubber_cache, key, fn _ -> + result = fetch_data_for_object(object) + + cond do + match?(%{page_url: _, rich_media: _}, result) -> + Activity.HTML.add_cache_key_for(activity.id, key) + {:commit, result} + + true -> + {:ignore, %{}} + end + end) + end else _ -> %{} end diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.RichMedia.Parser do require Logger @cachex Pleroma.Config.get([:cachex, :provider], Cachex) + @config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config) defp parsers do Pleroma.Config.get([:rich_media, :parsers]) @@ -13,70 +14,66 @@ defmodule Pleroma.Web.RichMedia.Parser do def parse(nil), do: {:error, "No URL provided"} - if Pleroma.Config.get(:env) == :test do - @spec parse(String.t()) :: {:ok, map()} | {:error, any()} - def parse(url), do: parse_url(url) - else - @spec parse(String.t()) :: {:ok, map()} | {:error, any()} - def parse(url) do - with {:ok, data} <- get_cached_or_parse(url), - {:ok, _} <- set_ttl_based_on_image(data, url) do - {:ok, data} - end + @spec parse(String.t()) :: {:ok, map()} | {:error, any()} + def parse(url) do + with :ok <- validate_page_url(url), + {:ok, data} <- get_cached_or_parse(url), + {:ok, _} <- set_ttl_based_on_image(data, url) do + {:ok, data} end + end - defp get_cached_or_parse(url) do - case @cachex.fetch(:rich_media_cache, url, fn -> - case parse_url(url) do - {:ok, _} = res -> - {:commit, res} - - {:error, reason} = e -> - # Unfortunately we have to log errors here, instead of doing that - # along with ttl setting at the bottom. Otherwise we can get log spam - # if more than one process was waiting for the rich media card - # while it was generated. Ideally we would set ttl here as well, - # so we don't override it number_of_waiters_on_generation - # times, but one, obviously, can't set ttl for not-yet-created entry - # and Cachex doesn't support returning ttl from the fetch callback. - log_error(url, reason) - {:commit, e} - end - end) do - {action, res} when action in [:commit, :ok] -> - case res do - {:ok, _data} = res -> - res - - {:error, reason} = e -> - if action == :commit, do: set_error_ttl(url, reason) - e - end - - {:error, e} -> - {:error, {:cachex_error, e}} - end + defp get_cached_or_parse(url) do + case @cachex.fetch(:rich_media_cache, url, fn -> + case parse_url(url) do + {:ok, _} = res -> + {:commit, res} + + {:error, reason} = e -> + # Unfortunately we have to log errors here, instead of doing that + # along with ttl setting at the bottom. Otherwise we can get log spam + # if more than one process was waiting for the rich media card + # while it was generated. Ideally we would set ttl here as well, + # so we don't override it number_of_waiters_on_generation + # times, but one, obviously, can't set ttl for not-yet-created entry + # and Cachex doesn't support returning ttl from the fetch callback. + log_error(url, reason) + {:commit, e} + end + end) do + {action, res} when action in [:commit, :ok] -> + case res do + {:ok, _data} = res -> + res + + {:error, reason} = e -> + if action == :commit, do: set_error_ttl(url, reason) + e + end + + {:error, e} -> + {:error, {:cachex_error, e}} end + end - defp set_error_ttl(_url, :body_too_large), do: :ok - defp set_error_ttl(_url, {:content_type, _}), do: :ok + defp set_error_ttl(_url, :body_too_large), do: :ok + defp set_error_ttl(_url, {:content_type, _}), do: :ok - # The TTL is not set for the errors above, since they are unlikely to change - # with time + # The TTL is not set for the errors above, since they are unlikely to change + # with time - defp set_error_ttl(url, _reason) do - ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000) - @cachex.expire(:rich_media_cache, url, ttl) - :ok - end + defp set_error_ttl(url, _reason) do + ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000) + @cachex.expire(:rich_media_cache, url, ttl) + :ok + end - defp log_error(url, {:invalid_metadata, data}) do - Logger.debug(fn -> "Incomplete or invalid metadata for #{url}: #{inspect(data)}" end) - end + defp log_error(url, {:invalid_metadata, data}) do + Logger.debug(fn -> "Incomplete or invalid metadata for #{url}: #{inspect(data)}" end) + end - defp log_error(url, reason) do - Logger.warning(fn -> "Rich media error for #{url}: #{inspect(reason)}" end) - end + defp log_error(url, reason) do + Logger.warning(fn -> "Rich media error for #{url}: #{inspect(reason)}" end) end @doc """ @@ -102,10 +99,10 @@ defmodule Pleroma.Web.RichMedia.Parser do ttl_setters: [MyModule] """ @spec set_ttl_based_on_image(map(), String.t()) :: - {:ok, Integer.t() | :noop} | {:error, :no_key} + {:ok, integer() | :noop} | {:error, :no_key} def set_ttl_based_on_image(data, url) do case get_ttl_from_image(data, url) do - {:ok, ttl} when is_number(ttl) -> + ttl when is_number(ttl) -> ttl = ttl * 1000 case @cachex.expire_at(:rich_media_cache, url, ttl) do @@ -166,4 +163,46 @@ defmodule Pleroma.Web.RichMedia.Parser do end) |> Map.new() end + + @spec validate_page_url(URI.t() | binary()) :: :ok | :error + defp validate_page_url(page_url) when is_binary(page_url) do + validate_tld = @config_impl.get([Pleroma.Formatter, :validate_tld]) + + page_url + |> Linkify.Parser.url?(validate_tld: validate_tld) + |> parse_uri(page_url) + end + + defp validate_page_url(%URI{host: host, scheme: "https"}) do + cond do + Linkify.Parser.ip?(host) -> + :error + + host in @config_impl.get([:rich_media, :ignore_hosts], []) -> + :error + + get_tld(host) in @config_impl.get([:rich_media, :ignore_tld], []) -> + :error + + true -> + :ok + end + end + + defp validate_page_url(_), do: :error + + defp parse_uri(true, url) do + url + |> URI.parse() + |> validate_page_url + end + + defp parse_uri(_, _), do: :error + + defp get_tld(host) do + host + |> String.split(".") + |> Enum.reverse() + |> hd + end end diff --git a/lib/pleroma/web/rich_media/parser/ttl.ex b/lib/pleroma/web/rich_media/parser/ttl.ex @@ -3,5 +3,5 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RichMedia.Parser.TTL do - @callback ttl(Map.t(), String.t()) :: Integer.t() | nil + @callback ttl(map(), String.t()) :: integer() | nil end diff --git a/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do def ttl(data, _url) do image = Map.get(data, :image) - if is_aws_signed_url(image) do + if aws_signed_url?(image) do image |> parse_query_params() |> format_query_params() @@ -19,13 +19,13 @@ defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do end end - defp is_aws_signed_url(image) when is_binary(image) and image != "" do + defp aws_signed_url?(image) when is_binary(image) and image != "" do %URI{host: host, query: query} = URI.parse(image) String.contains?(host, "amazonaws.com") and String.contains?(query, "X-Amz-Expires") end - defp is_aws_signed_url(_), do: nil + defp aws_signed_url?(_), do: nil defp parse_query_params(image) do %URI{query: query} = URI.parse(image) @@ -45,6 +45,6 @@ defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do |> Map.get("X-Amz-Date") |> Timex.parse("{ISO:Basic:Z}") - {:ok, Timex.to_unix(date) + String.to_integer(Map.get(params, "X-Amz-Expires"))} + Timex.to_unix(date) + String.to_integer(Map.get(params, "X-Amz-Expires")) end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex @@ -182,7 +182,7 @@ defmodule Pleroma.Web.Router do end pipeline :well_known do - plug(:accepts, ["json", "jrd+json", "xml", "xrd+xml"]) + plug(:accepts, ["json", "jrd", "jrd+json", "xml", "xrd+xml"]) end pipeline :config do @@ -486,7 +486,7 @@ defmodule Pleroma.Web.Router do post("/change_email", UtilController, :change_email) post("/change_password", UtilController, :change_password) post("/delete_account", UtilController, :delete_account) - put("/notification_settings", UtilController, :update_notificaton_settings) + put("/notification_settings", UtilController, :update_notification_settings) post("/disable_account", UtilController, :disable_account) post("/move_account", UtilController, :move_account) diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex @@ -13,7 +13,6 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do alias Pleroma.Web.Metadata alias Pleroma.Web.Router.Helpers - plug(:put_layout, :static_fe) plug(:assign_id) @page_keys ["max_id", "min_id", "limit", "since_id", "order"] @@ -22,7 +21,7 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do def show(%{assigns: %{notice_id: notice_id}} = conn, _params) do with %Activity{local: true} = activity <- Activity.get_by_id_with_object(notice_id), - true <- Visibility.is_public?(activity.object), + true <- Visibility.public?(activity.object), {_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)}, %User{} = user <- User.get_by_ap_id(activity.object.data["actor"]) do url = Helpers.url(conn) <> conn.request_path diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex @@ -20,7 +20,6 @@ defmodule Pleroma.Web.Streamer do alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.StreamerView - @mix_env Mix.env() @registry Pleroma.Web.StreamerRegistry def registry, do: @registry @@ -34,7 +33,7 @@ defmodule Pleroma.Web.Streamer do stream :: String.t(), User.t() | nil, Token.t() | nil, - Map.t() | nil + map() | nil ) :: {:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized} def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do @@ -60,7 +59,7 @@ defmodule Pleroma.Web.Streamer do end @doc "Expand and authorizes a stream" - @spec get_topic(stream :: String.t() | nil, User.t() | nil, Token.t() | nil, Map.t()) :: + @spec get_topic(stream :: String.t() | nil, User.t() | nil, Token.t() | nil, map()) :: {:ok, topic :: String.t() | nil} | {:error, :bad_topic} def get_topic(stream, user, oauth_token, params \\ %{}) @@ -246,7 +245,7 @@ defmodule Pleroma.Web.Streamer do defp do_stream("list", item) do # filter the recipient list if the activity is not public, see #270. recipient_lists = - case Visibility.is_public?(item) do + case Visibility.public?(item) do true -> Pleroma.List.get_lists_from_activity(item) @@ -396,25 +395,20 @@ defmodule Pleroma.Web.Streamer do end end - # In test environement, only return true if the registry is started. - # In benchmark environment, returns false. - # In any other environment, always returns true. - cond do - @mix_env == :test -> - def should_env_send? do - case Process.whereis(@registry) do - nil -> - false + # In dev/prod the streamer registry is expected to be started, so return true + # In test it is possible to have the registry started for a test so it will check + # In benchmark it will never find the process alive and return false + def should_env_send? do + if Application.get_env(:pleroma, Pleroma.Application)[:streamer_registry] do + true + else + case Process.whereis(@registry) do + nil -> + false - pid -> - Process.alive?(pid) - end + pid -> + Process.alive?(pid) end - - @mix_env == :benchmark -> - def should_env_send?, do: false - - true -> - def should_env_send?, do: true + end end end diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex @@ -13,7 +13,7 @@ <div class="account-header__avatar" style="background-image: url('<%= Pleroma.User.avatar_url(@user) %>')"></div> <div class="account-header__meta"> <div class="account-header__display-name"><%= @user.name %></div> - <div class="account-header__nickname">@<%= @user.nickname %>@<%= Pleroma.User.get_host(@user) %></div> + <div class="account-header__nickname">@<%= Pleroma.User.full_nickname(@user.nickname) %></div> </div> </div> <% end %> diff --git a/lib/pleroma/web/twitter_api/controllers/password_controller.ex b/lib/pleroma/web/twitter_api/controllers/password_controller.ex @@ -4,7 +4,7 @@ defmodule Pleroma.Web.TwitterAPI.PasswordController do @moduledoc """ - The module containts functions for reset password. + The module contains functions for password reset. """ use Pleroma.Web, :controller diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex @@ -29,7 +29,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do # GET /ostatus_subscribe # def follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do - case is_status?(acct) do + case status?(acct) do true -> follow_status(conn, user, acct) _ -> follow_account(conn, user, acct) end @@ -57,7 +57,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do defp follow_template(%User{} = _user), do: "follow.html" defp follow_template(_), do: "follow_login.html" - defp is_status?(acct) do + defp status?(acct) do case Fetcher.fetch_and_contain_remote_object_from_id(acct) do {:ok, %{"type" => type}} when type in @status_types -> true diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -18,7 +18,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do alias Pleroma.Web.WebFinger plug( - Pleroma.Web.ApiSpec.CastAndValidate + Pleroma.Web.ApiSpec.CastAndValidate, + [replace_params: false] when action != :remote_subscribe and action != :show_subscribe_form ) @@ -35,7 +36,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do :change_email, :change_password, :delete_account, - :update_notificaton_settings, + :update_notification_settings, :disable_account, :move_account, :add_alias, @@ -150,7 +151,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end end - def remote_interaction(%{body_params: %{ap_id: ap_id, profile: profile}} = conn, _params) do + def remote_interaction( + %{private: %{open_api_spex: %{body_params: %{ap_id: ap_id, profile: profile}}}} = conn, + _params + ) do with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile) do conn |> json(%{url: String.replace(template, "{uri}", ap_id)}) @@ -181,13 +185,16 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do json(conn, emoji) end - def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do + def update_notification_settings(%{assigns: %{user: user}} = conn, params) do with {:ok, _} <- User.update_notification_settings(user, params) do json(conn, %{status: "success"}) end end - def change_password(%{assigns: %{user: user}, body_params: body_params} = conn, %{}) do + def change_password( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: body_params}}} = conn, + _ + ) do case CommonAPI.Utils.confirm_current_password(user, body_params.password) do {:ok, user} -> with {:ok, _user} <- @@ -210,7 +217,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end end - def change_email(%{assigns: %{user: user}, body_params: body_params} = conn, %{}) do + def change_email( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: body_params}}} = conn, + _ + ) do case CommonAPI.Utils.confirm_current_password(user, body_params.password) do {:ok, user} -> with {:ok, _user} <- User.change_email(user, body_params.email) do @@ -229,7 +239,13 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end end - def delete_account(%{assigns: %{user: user}, body_params: body_params} = conn, params) do + def delete_account( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{body_params: body_params, params: params}} + } = conn, + _ + ) do # This endpoint can accept a query param or JSON body for backwards-compatibility. # Submitting a JSON body is recommended, so passwords don't end up in server logs. password = body_params[:password] || params[:password] || "" @@ -244,7 +260,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end end - def disable_account(%{assigns: %{user: user}} = conn, params) do + def disable_account( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: params}}} = conn, + _ + ) do case CommonAPI.Utils.confirm_current_password(user, params[:password]) do {:ok, user} -> User.set_activation_async(user, false) @@ -255,7 +274,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end end - def move_account(%{assigns: %{user: user}, body_params: body_params} = conn, %{}) do + def move_account( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: body_params}}} = conn, + _ + ) do case CommonAPI.Utils.confirm_current_password(user, body_params.password) do {:ok, user} -> with {:ok, target_user} <- find_or_fetch_user_by_nickname(body_params.target_account), @@ -276,7 +298,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end end - def add_alias(%{assigns: %{user: user}, body_params: body_params} = conn, _) do + def add_alias( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: body_params}}} = conn, + _ + ) do with {:ok, alias_user} <- find_user_by_nickname(body_params.alias), {:ok, _user} <- user |> User.add_alias(alias_user) do json(conn, %{status: "success"}) @@ -291,7 +316,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end end - def delete_alias(%{assigns: %{user: user}, body_params: body_params} = conn, _) do + def delete_alias( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: body_params}}} = conn, + _ + ) do with {:ok, alias_user} <- find_user_by_nickname(body_params.alias), {:ok, _user} <- user |> User.delete_alias(alias_user) do json(conn, %{status: "success"}) @@ -306,7 +334,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end end - def list_aliases(%{assigns: %{user: user}} = conn, %{}) do + def list_aliases(%{assigns: %{user: user}} = conn, _) do alias_nicks = user |> User.alias_users() @@ -319,7 +347,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do user = User.get_cached_by_nickname(nickname) if user == nil do - {:not_found, nil} + {:error, :not_found} else {:ok, user} end diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex @@ -5,8 +5,8 @@ defmodule Pleroma.Web.WebFinger do alias Pleroma.HTTP alias Pleroma.User + alias Pleroma.Web.ActivityPub.Publisher alias Pleroma.Web.Endpoint - alias Pleroma.Web.Federator.Publisher alias Pleroma.Web.XML alias Pleroma.XmlBuilder require Jason diff --git a/lib/pleroma/web/web_finger/web_finger_controller.ex b/lib/pleroma/web/web_finger/web_finger_controller.ex @@ -30,7 +30,7 @@ defmodule Pleroma.Web.WebFinger.WebFingerController do end def webfinger(%{assigns: %{format: format}} = conn, %{"resource" => resource}) - when format in ["json", "jrd+json"] do + when format in ["jrd", "json", "jrd+json"] do with {:ok, response} <- WebFinger.webfinger(resource, "JSON") do json(conn, response) else diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex @@ -28,7 +28,7 @@ defmodule Pleroma.Workers.BackgroundWorker do def perform(%Job{args: %{"op" => op, "user_id" => user_id, "identifiers" => identifiers}}) when op in ["blocks_import", "follow_import", "mutes_import"] do user = User.get_cached_by_id(user_id) - {:ok, User.Import.perform(String.to_atom(op), user, identifiers)} + {:ok, User.Import.perform(String.to_existing_atom(op), user, identifiers)} end def perform(%Job{ @@ -40,6 +40,11 @@ defmodule Pleroma.Workers.BackgroundWorker do Pleroma.FollowingRelationship.move_following(origin, target) end + def perform(%Job{args: %{"op" => "verify_fields_links", "user_id" => user_id}}) do + user = User.get_by_id(user_id) + User.perform(:verify_fields_links, user) + end + def perform(%Job{args: %{"op" => "delete_instance", "host" => host}}) do Instance.perform(:delete_instance, host) end diff --git a/lib/pleroma/workers/publisher_worker.ex b/lib/pleroma/workers/publisher_worker.ex @@ -18,9 +18,9 @@ defmodule Pleroma.Workers.PublisherWorker do Federator.perform(:publish, activity) end - def perform(%Job{args: %{"op" => "publish_one", "module" => module_name, "params" => params}}) do + def perform(%Job{args: %{"op" => "publish_one", "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) + Federator.perform(:publish_one, params) end @impl Oban.Worker diff --git a/lib/pleroma/workers/remote_fetcher_worker.ex b/lib/pleroma/workers/remote_fetcher_worker.ex @@ -9,7 +9,25 @@ defmodule Pleroma.Workers.RemoteFetcherWorker do @impl Oban.Worker def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do - {:ok, _object} = Fetcher.fetch_object_from_id(id, depth: args["depth"]) + case Fetcher.fetch_object_from_id(id, depth: args["depth"]) do + {:ok, _object} -> + :ok + + {:error, :forbidden} -> + {:discard, :forbidden} + + {:error, :not_found} -> + {:discard, :not_found} + + {:error, :allowed_depth} -> + {:discard, :allowed_depth} + + {:error, _} = e -> + e + + e -> + {:error, e} + end end @impl Oban.Worker diff --git a/mix.exs b/mix.exs @@ -4,12 +4,13 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("2.6.51"), + version: version("2.6.52"), elixir: "~> 1.11", elixirc_paths: elixirc_paths(Mix.env()), compilers: Mix.compilers(), elixirc_options: [warnings_as_errors: warnings_as_errors()], xref: [exclude: [:eldap]], + dialyzer: [plt_add_apps: [:mix, :eldap]], start_permanent: Mix.env() == :prod, aliases: aliases(), deps: deps(), @@ -136,7 +137,7 @@ defmodule Pleroma.Mixfile do {:calendar, "~> 1.0"}, {:cachex, "~> 3.2"}, {:poison, "~> 3.0", override: true}, - {:tesla, "~> 1.4.0", override: true}, + {:tesla, "~> 1.8.0"}, {:castore, "~> 0.1"}, {:cowlib, "~> 2.9", override: true}, {:gun, "~> 2.0.0-rc.1", override: true}, @@ -156,11 +157,11 @@ defmodule Pleroma.Mixfile do {:phoenix_swoosh, "~> 1.1"}, {:gen_smtp, "~> 0.13"}, {:ex_syslogger, "~> 1.4"}, - {:floki, "~> 0.27"}, + {:floki, "~> 0.35"}, {:timex, "~> 3.6"}, {:ueberauth, "~> 0.4"}, {:linkify, "~> 0.5.3"}, - {:http_signatures, "~> 0.1.1"}, + {:http_signatures, "~> 0.1.2"}, {:telemetry, "~> 1.0.0", override: true}, {:poolboy, "~> 1.5"}, {:prom_ex, "~> 1.9"}, @@ -184,6 +185,10 @@ defmodule Pleroma.Mixfile do {:vix, "~> 0.26.0"}, {:elixir_make, "~> 0.7.7", override: true}, {:blurhash, "~> 0.1.0", hex: :rinpatch_blurhash}, + {:exile, + git: "https://github.com/akash-akya/exile.git", + ref: "be87c33b02a7c3c5d22d2ece01fbd462355b28ef"}, + {:bandit, "~> 1.2"}, ## dev & test {:ex_doc, "~> 0.22", only: :dev, runtime: false}, @@ -194,7 +199,8 @@ defmodule Pleroma.Mixfile do {:hackney, "~> 1.18.0", override: true}, {:mox, "~> 1.0", only: :test}, {:websockex, "~> 0.4.3", only: :test}, - {:benchee, "~> 1.0", only: :benchmark} + {:benchee, "~> 1.0", only: :benchmark}, + {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false} ] ++ oauth_deps() end diff --git a/mix.lock b/mix.lock @@ -1,19 +1,20 @@ %{ "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"}, + "bandit": {:hex, :bandit, "1.2.1", "aa485b4ac175065b8e0fb5864ddd5dd7b50d52336b36f61c82f484c3718b3d15", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "27393e590a407f1b7d51c5fee4737f139fe224a30449ce25061eac70f763896b"}, "base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"}, "bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.1", "5114d780459a04f2b4aeef52307de23de961b69e13a5cd98a911e39fda13f420", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "42182d5f46764def15bf9af83739e3bf4ad22661b1c34fc3e88558efced07279"}, - "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, + "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "blurhash": {:hex, :rinpatch_blurhash, "0.1.0", "01a888b0f5f1f382ab52e4396f01831cbe8486ea5828604c90f4dac533d39a4b", [:mix], [{:mogrify, "~> 0.8.0", [hex: :mogrify, repo: "hexpm", optional: true]}], "hexpm", "19911a5dcbb0acb9710169a72f702bce6cb048822b12de566ccd82b2cc42b907"}, - "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [: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", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"}, "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", "90f6ce7672f70f56708792a98d98bd05176c9176", [ref: "90f6ce7672f70f56708792a98d98bd05176c9176"]}, "castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.9", "e8d3364f310da6ce6463c3dd20cf90ae7bbecbf6c5203b98bf9b48035592649b", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "9dcab3d0f3038621f1601f13539e7a9ee99843862e66ad62827b0c42b2f58a54"}, - "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, - "comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"}, + "comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"}, "concurrent_limiter": {:hex, :concurrent_limiter, "0.1.1", "43ae1dc23edda1ab03dd66febc739c4ff710d047bb4d735754909f9a474ae01c", [:mix], [{:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "53968ff238c0fbb4d7ed76ddb1af0be6f3b2f77909f6796e249e737c505a16eb"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, "cors_plug": {:hex, :cors_plug, "2.0.3", "316f806d10316e6d10f09473f19052d20ba0a0ce2a1d910ddf57d663dac402ae", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ee4ae1418e6ce117fc42c2ba3e6cbdca4e95ecd2fe59a05ec6884ca16d469aea"}, @@ -21,92 +22,95 @@ "cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, - "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, + "credo": {:hex, :credo, "1.7.3", "05bb11eaf2f2b8db370ecaa6a6bda2ec49b2acd5e0418bc106b73b07128c0436", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "35ea675a094c934c22fb1dca3696f3c31f2728ae6ef5a53b5d648c11180a4535"}, "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, + "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "earmark": {:hex, :earmark, "1.4.22", "ea3e45c6359446dc308be0a64ce82a03260d973de7d0625a762e6d352ff57958", [:mix], [{:earmark_parser, "~> 1.4.23", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "1caf5145665a42fd76d5317286b0c171861fb1c04f86ab103dde76868814fdfb"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.32", "fa739a0ecfa34493de19426681b23f6814573faee95dfd4b4aafe15a7b5b32c6", [:mix], [], "hexpm", "b8b0dd77d60373e77a3d7e8afa598f325e49e8663a51bcc2b88ef41838cca755"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "eblurhash": {:git, "https://github.com/zotonic/eblurhash.git", "bc37ceb426ef021ee9927fb249bb93f7059194ab", [ref: "bc37ceb426ef021ee9927fb249bb93f7059194ab"]}, - "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [: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 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, + "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, "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_psql_extras": {:hex, :ecto_psql_extras, "0.7.14", "7a20cfe913b0476542b43870e67386461258734896035e3f284039fd18bd4c4c", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "22f5f98592dd597db9416fcef00effae0787669fdcb6faf447e982b553798e98"}, - "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"}, + "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.15", "0fc29dbae0e444a29bd6abeee4cf3c4c037e692a272478a234a1cc765077dbb1", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "b6127f3a5c6fc3d84895e4768cc7c199f22b48b67d6c99b13fbf4a374e73f039"}, + "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, "eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"}, - "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, + "elixir_make": {:hex, :elixir_make, "0.7.8", "505026f266552ee5aabca0b9f9c229cbb496c689537c9f922f3eb5431157efc7", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "7a71945b913d37ea89b06966e1342c85cfe549b15e6d6d081e8081c493062c07"}, + "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "esbuild": {:hex, :esbuild, "0.5.0", "d5bb08ff049d7880ee3609ed5c4b864bd2f46445ea40b16b4acead724fb4c4a3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "f183a0b332d963c4cfaf585477695ea59eef9a6f2204fdd0efa00e099694ffe5"}, "eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"}, "ex_aws": {:hex, :ex_aws, "2.1.9", "dc4865ecc20a05190a34a0ac5213e3e5e2b0a75a0c2835e923ae7bfeac5e3c31", [: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, "~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "3e6c776703c9076001fbe1f7c049535f042cb2afa0d2cbd3b47cbc4e92ac0d10"}, - "ex_aws_s3": {:hex, :ex_aws_s3, "2.4.0", "ce8decb6b523381812798396bc0e3aaa62282e1b40520125d1f4eff4abdff0f4", [: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", "85dda6e27754d94582869d39cba3241d9ea60b6aa4167f9c88e309dc687e56bb"}, + "ex_aws_s3": {:hex, :ex_aws_s3, "2.5.3", "422468e5c3e1a4da5298e66c3468b465cfd354b842e512cb1f6fbbe4e2f5bdaf", [: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", "4f09dd372cc386550e484808c5ac5027766c8d0cd8271ccc578b82ee6ef4f3b8"}, "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"}, - "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, + "ex_doc": {:hex, :ex_doc, "0.31.1", "8a2355ac42b1cc7b2379da9e40243f2670143721dd50748bf6c3b1184dae2089", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "3178c3a407c557d8343479e1ff117a96fd31bafe52a039079593fb0524ef61b0"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [: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", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, "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"}, - "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, - "fast_html": {:hex, :fast_html, "2.0.5", "c61760340606c1077ff1f196f17834056cb1dd3d5cb92a9f2cabf28bc6221c3c", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "605f4f4829443c14127694ebabb681778712ceecb4470ec32aa31012330e6506"}, + "exile": {:git, "https://github.com/akash-akya/exile.git", "be87c33b02a7c3c5d22d2ece01fbd462355b28ef", [ref: "be87c33b02a7c3c5d22d2ece01fbd462355b28ef"]}, + "expo": {:hex, :expo, "0.5.1", "249e826a897cac48f591deba863b26c16682b43711dd15ee86b92f25eafd96d9", [:mix], [], "hexpm", "68a4233b0658a3d12ee00d27d37d856b1ba48607e7ce20fd376958d0ba6ce92b"}, + "fast_html": {:hex, :fast_html, "2.2.0", "6c5ef1be087a4ed613b0379c13f815c4d11742b36b67bb52cee7859847c84520", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "064c4f23b4a6168f9187dac8984b056f2c531bb0787f559fd6a8b34b38aefbae"}, "fast_sanitize": {:hex, :fast_sanitize, "0.2.3", "67b93dfb34e302bef49fec3aaab74951e0f0602fd9fa99085987af05bd91c7a5", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "finch": {:hex, :finch, "0.16.0", "40733f02c89f94a112518071c0a91fe86069560f5dbdb39f9150042f44dcfb1a", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5"}, + "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, "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.34.3", "5e2dcaec5d7c228ce5b1d3501502e308b2d79eb655e4191751a1fe491c37feac", [:mix], [], "hexpm", "9577440eea5b97924b4bf3c7ea55f7b8b6dce589f9b28b096cc294a8dc342341"}, + "floki": {:hex, :floki, "0.35.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"}, "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, - "gettext": {:hex, :gettext, "0.22.2", "6bfca374de34ecc913a28ba391ca184d88d77810a3e427afa8454a71a51341ac", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "8a2d389673aea82d7eae387e6a2ccc12660610080ae7beb19452cfdc1ec30f60"}, + "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, "gun": {:hex, :gun, "2.0.1", "160a9a5394800fcba41bc7e6d421295cf9a7894c2252c0678244948e3336ad73", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "a10bc8d6096b9502205022334f719cc9a08d9adcfbfc0dbee9ef31b56274a20b"}, - "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, + "hackney": {:hex, :hackney, "1.18.2", "d7ff544ddae5e1cb49e9cf7fa4e356d7f41b283989a1c304bfc47a8cc1cf966f", [:rebar3], [{:certifi, "~>2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "af94d5c9f97857db257090a4a10e5426ecb6f4918aa5cc666798566ae14b65fd"}, "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, - "http_signatures": {:hex, :http_signatures, "0.1.1", "ca7ebc1b61542b163644c8c3b1f0e0f41037d35f2395940d3c6c7deceab41fd8", [:mix], [], "hexpm", "cc3b8a007322cc7b624c0c15eec49ee58ac977254ff529a3c482f681465942a3"}, + "http_signatures": {:hex, :http_signatures, "0.1.2", "ed1cc7043abcf5bb4f30d68fb7bad9d618ec1a45c4ff6c023664e78b67d9c406", [:mix], [], "hexpm", "f08aa9ac121829dae109d608d83c84b940ef2f183ae50f2dd1e9a8bc619d8be7"}, "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, - "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"}, + "inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "joken": {:hex, :joken, "2.6.0", "b9dd9b6d52e3e6fcb6c65e151ad38bf4bc286382b5b6f97079c47ade6b1bcc6a", [:mix], [{:jose, "~> 1.11.5", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5a95b05a71cd0b54abd35378aeb1d487a23a52c324fa7efdffc512b655b5aaa7"}, - "jose": {:hex, :jose, "1.11.5", "3bc2d75ffa5e2c941ca93e5696b54978323191988eb8d225c2e663ddfefd515e", [:mix, :rebar3], [], "hexpm", "dcd3b215bafe02ea7c5b23dafd3eb8062a5cd8f2d904fd9caa323d37034ab384"}, - "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, + "jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"}, + "jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"}, "linkify": {:hex, :linkify, "0.5.3", "5f8143d8f61f5ff08d3aeeff47ef6509492b4948d8f08007fbf66e4d2246a7f2", [:mix], [], "hexpm", "3ef35a1377d47c25506e07c1c005ea9d38d700699d92ee92825f024434258177"}, "majic": {:hex, :majic, "1.0.0", "37e50648db5f5c2ff0c9fb46454d034d11596c03683807b9fb3850676ffdaab3", [:make, :mix], [{:elixir_make, "~> 0.6.1", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "7905858f76650d49695f14ea55cd9aaaee0c6654fa391671d4cf305c275a0a9e"}, "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"}, "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, + "mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"}, "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"}, "mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"}, "mogrify": {:hex, :mogrify, "0.8.0", "3506f3ca3f7b95a155f3b4ef803b5db176f5a0633723e3fe85e0d6399e3b11c8", [:mix], [], "hexpm", "2278d245f07056ea3b586e98801e933695147066fa4cf563f552c1b4f0ff8ad9"}, - "mox": {:hex, :mox, "1.0.2", "dc2057289ac478b35760ba74165b4b3f402f68803dd5aecd3bfd19c183815d64", [:mix], [], "hexpm", "f9864921b3aaf763c8741b5b8e6f908f44566f1e427b2630e89e9a73b981fef2"}, - "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, + "mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"}, + "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, "nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"}, "nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"}, "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, "oban": {:hex, :oban, "2.13.6", "a0cb1bce3bd393770512231fb5a3695fa19fd3af10d7575bf73f837aee7abf43", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c1c5eb16f377b3cbbf2ea14be24d20e3d91285af9d1ac86260b7c2af5464887"}, - "octo_fetch": {:hex, :octo_fetch, "0.3.0", "89ff501d2ac0448556ff1931634a538fe6d6cd358ba827ce1747e6a42a46efbf", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "c07e44f2214ab153743b7b3182f380798d0b294b1f283811c1e30cff64096d3d"}, - "open_api_spex": {:hex, :open_api_spex, "3.17.3", "ada8e352eb786050dd639db2439d3316e92f3798eb2abd051f55bb9af825b37e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "165db21a85ca83cffc8e7c8890f35b354eddda8255de7404a2848ed652b9f0fe"}, - "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, + "octo_fetch": {:hex, :octo_fetch, "0.4.0", "074b5ecbc08be10b05b27e9db08bc20a3060142769436242702931c418695b19", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "cf8be6f40cd519d7000bb4e84adcf661c32e59369ca2827c4e20042eda7a7fc6"}, + "open_api_spex": {:hex, :open_api_spex, "3.18.2", "8c855e83bfe8bf81603d919d6e892541eafece3720f34d1700b58024dadde247", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "aa3e6dcfc0ad6a02596b2172662da21c9dd848dac145ea9e603f54e3d81b8d2b"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "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.7.10", "02189140a61b2ce85bb633a9b6fd02dff705a5f1596869547aeb2b2b95edd729", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "cf784932e010fd736d656d7fead6a584a4498efefe5b8227e9f383bf15bb79d0"}, - "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.2", "b21bd01fdeffcfe2fab49e4942aa938b6d3e89e93a480d4aee58085560a0bc0d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "70242edd4601d50b69273b057ecf7b684644c19ee750989fd555625ae4ce8f5d"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.3", "86e9878f833829c3f66da03d75254c155d91d72a201eb56ae83482328dc7ca93", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d36c401206f3011fefd63d04e8ef626ec8791975d9d107f9a0817d426f61ac07"}, "phoenix_html": {:hex, :phoenix_html, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.3", "7ff51c9b6609470f681fbea20578dede0e548302b0c8bdf338b5a753a4f045bf", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "f9470a0a8bae4f56430a23d42f977b5a6205fdba6559d76f932b876bfaec652d"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"}, "phoenix_live_view": {:hex, :phoenix_live_view, "0.19.5", "6e730595e8e9b8c5da230a814e557768828fd8dfeeb90377d2d8dbb52d4ec00a", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b2eaa0dd3cfb9bd7fb949b88217df9f25aed915e986a28ad5c8a0d054e7ca9d3"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, - "phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.0", "a544d83fde4a767efb78f45404a74c9e37b2a9c5ea3339692e65a6966731f935", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "e88d117251e89a16b92222415a6d87b99a96747ddf674fc5c7631de734811dba"}, - "phoenix_template": {:hex, :phoenix_template, "1.0.3", "32de561eefcefa951aead30a1f94f1b5f0379bc9e340bb5c667f65f1edfa4326", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "16f4b6588a4152f3cc057b9d0c0ba7e82ee23afa65543da535313ad8d25d8e2c"}, + "phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "phoenix_view": {:hex, :phoenix_view, "2.0.3", "4d32c4817fce933693741deeb99ef1392619f942633dde834a5163124813aad3", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "cd34049af41be2c627df99cd4eaa71fc52a328c0c3d8e7d4aa28f880c30e7f64"}, - "plug": {:hex, :plug, "1.15.1", "b7efd81c1a1286f13efb3f769de343236bd8b7d23b4a9f40d3002fc39ad8f74c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "459497bd94d041d98d948054ec6c0b76feacd28eec38b219ca04c0de13c79d30"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, + "plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.6.2", "753611b23b29231fb916b0cdd96028084b12aff57bfd7b71781bd04b1dbeb5c9", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "951ed2433df22f4c97b85fdb145d4cee561f36b74854d64c06d896d7cd2921a7"}, "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"}, "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.17.3", "c92cda8de2033a7585dae8c61b1d420a1a1322421df84da9a82a6764580c503d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "946cf46935a4fdca7a81448be76ba3503cff082df42c6ec1ff16a4bdfbfb098d"}, + "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, "pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"}, - "prom_ex": {:hex, :prom_ex, "1.9.0", "63e6dda6c05cdeec1f26c48443dcc38ffd2118b3665ae8d2bd0e5b79f2aea03e", [:mix], [{:absinthe, ">= 1.6.0", [hex: :absinthe, repo: "hexpm", optional: true]}, {:broadway, ">= 1.0.2", [hex: :broadway, repo: "hexpm", optional: true]}, {:ecto, ">= 3.5.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:finch, "~> 0.15", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, ">= 2.4.0", [hex: :oban, repo: "hexpm", optional: true]}, {:octo_fetch, "~> 0.3", [hex: :octo_fetch, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, ">= 0.14.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, ">= 1.12.1", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.5", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry, ">= 1.0.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 1.0", [hex: :telemetry_poller, repo: "hexpm", optional: false]}], "hexpm", "01f3d4f69ec93068219e686cc65e58a29c42bea5429a8ff4e2121f19db178ee6"}, + "prom_ex": {:hex, :prom_ex, "1.9.0", "63e6dda6c05cdeec1f26c48443dcc38ffd2118b3665ae8d2bd0e5b79f2aea03e", [:mix], [{:absinthe, ">= 1.6.0", [hex: :absinthe, repo: "hexpm", optional: true]}, {:broadway, ">= 1.0.2", [hex: :broadway, repo: "hexpm", optional: true]}, {:ecto, ">= 3.5.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:finch, "~> 0.15", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, ">= 2.4.0", [hex: :oban, repo: "hexpm", optional: true]}, {:octo_fetch, "~> 0.3", [hex: :octo_fetch, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, ">= 0.14.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, ">= 1.12.1", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.5 or ~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry, ">= 1.0.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 1.0", [hex: :telemetry_poller, repo: "hexpm", optional: false]}], "hexpm", "01f3d4f69ec93068219e686cc65e58a29c42bea5429a8ff4e2121f19db178ee6"}, "prometheus": {:hex, :prometheus, "4.10.0", "792adbf0130ff61b5fa8826f013772af24b6e57b984445c8d602c8a0355704a1", [:mix, :rebar3], [{:quantile_estimator, "~> 0.2.1", [hex: :quantile_estimator, repo: "hexpm", optional: false]}], "hexpm", "2a99bb6dce85e238c7236fde6b0064f9834dc420ddbd962aac4ea2a3c3d59384"}, "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": {:git, "https://github.com/lanodan/prometheus.ex.git", "31f7fbe4b71b79ba27efc2a5085746c4011ceb8f", [branch: "fix/elixir-1.14"]}, @@ -115,29 +119,30 @@ "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"}, "quantile_estimator": {:hex, :quantile_estimator, "0.2.1", "ef50a361f11b5f26b5f16d0696e46a9e4661756492c981f7b2229ef42ff1cd15", [:rebar3], [], "hexpm", "282a8a323ca2a845c9e6f787d166348f776c1d4a41ede63046d72d422e3da946"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, - "recon": {:hex, :recon, "2.5.3", "739107b9050ea683c30e96de050bc59248fd27ec147696f79a8797ff9fa17153", [:mix, :rebar3], [], "hexpm", "6c6683f46fd4a1dfd98404b9f78dcabc7fcd8826613a89dcb984727a8c3099d7"}, + "recon": {:hex, :recon, "2.5.4", "05dd52a119ee4059fa9daa1ab7ce81bc7a8161a2f12e9d42e9d551ffd2ba901c", [:mix, :rebar3], [], "hexpm", "e9ab01ac7fc8572e41eb59385efeb3fb0ff5bf02103816535bacaedf327d0263"}, "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]}, "rustler": {:hex, :rustler, "0.30.0", "cefc49922132b072853fa9b0ca4dc2ffcb452f68fb73b779042b02d545e097fb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "9ef1abb6a7dda35c47cfc649e6a5a61663af6cf842a55814a554a84607dee389"}, "sleeplocks": {:hex, :sleeplocks, "1.1.2", "d45aa1c5513da48c888715e3381211c859af34bee9b8290490e10c90bb6ff0ca", [:rebar3], [], "hexpm", "9fe5d048c5b781d6305c1a3a0f40bb3dfc06f49bf40571f3d2d0c57eaa7f59a5"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, - "sweet_xml": {:hex, :sweet_xml, "0.7.3", "debb256781c75ff6a8c5cbf7981146312b66f044a2898f453709a53e5031b45b", [:mix], [], "hexpm", "e110c867a1b3fe74bfc7dd9893aa851f0eed5518d0d7cad76d7baafd30e4f5ba"}, + "sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"}, "swoosh": {:hex, :swoosh, "1.10.3", "32f1531ee3fe4e82da8175c597bf3692938f8152eb981e0cbf57107b6c5924c1", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [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 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8b7167d93047bac6e1a1c367bf7d899cf2e4fea0592ee04a70673548ef6091b9"}, "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, - "table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"}, + "table_rex": {:hex, :table_rex, "4.0.0", "3c613a68ebdc6d4d1e731bc973c233500974ec3993c99fcdabb210407b90959b", [:mix], [], "hexpm", "c35c4d5612ca49ebb0344ea10387da4d2afe278387d4019e4d8111e815df8f55"}, "telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"}, - "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, - "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.1.0", "4e15f6d7dbedb3a4e3aed2262b7e1407f166fcb9c30ca3f96635dfbbef99965c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0dd10e7fe8070095df063798f82709b0a1224c31b8baf6278b423898d591a069"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"}, + "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.0", "b583c3f18508f5c5561b674d16cf5d9afd2ea3c04505b7d92baaeac93c1b8260", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "9cba950e1c4733468efbe3f821841f34ac05d28e7af7798622f88ecdbbe63ea3"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, - "tesla": {:hex, :tesla, "1.4.4", "bb89aa0c9745190930366f6a2ac612cdf2d0e4d7fff449861baa7875afd797b2", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "d5503a49f9dec1b287567ea8712d085947e247cb11b06bc54adb05bfde466457"}, + "tesla": {:hex, :tesla, "1.8.0", "d511a4f5c5e42538d97eef7c40ec4f3e44effdc5068206f42ed859e09e51d1fd", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "10501f360cd926a309501287470372af1a6e1cbed0f43949203a4c13300bc79f"}, + "thousand_island": {:hex, :thousand_island, "1.3.2", "bc27f9afba6e1a676dd36507d42e429935a142cf5ee69b8e3f90bff1383943cd", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0e085b93012cd1057b378fce40cbfbf381ff6d957a382bfdd5eca1a98eec2535"}, "timex": {:hex, :timex, "3.7.7", "3ed093cae596a410759104d878ad7b38e78b7c2151c6190340835515d4a46b8a", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "0ec4b09f25fe311321f9fc04144a7e3affe48eb29481d7a5583849b6c4dfa0a7"}, "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "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.5", "69f1ee029a49afa04ad77801febaf69385f3d3e3d1e4b56b9469025677b89a28", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "55519aa2a99e5d2095c1e61cc74c9be69688f8ab75c27da724eb8279ff402a5a"}, - "ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"}, + "ueberauth": {:hex, :ueberauth, "0.10.7", "5a31cbe11e7ce5c7484d745dc9e1f11948e89662f8510d03c616de03df581ebd", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "0bccf73e2ffd6337971340832947ba232877aa8122dba4c95be9f729c8987377"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, - "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"}, - "vix": {:hex, :vix, "0.26.0", "027f10b6969b759318be84bd0bd8c88af877445e4e41cf96a0460392cea5399c", [:make, :mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:cc_precompiler, "~> 0.1.4 or ~> 0.2", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7.3 or ~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "71b0a79ae7f199cacfc8e679b0e4ba25ee47dc02e182c5b9097efb29fbe14efd"}, + "unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"}, + "vix": {:hex, :vix, "0.26.0", "027f10b6969b759318be84bd0bd8c88af877445e4e41cf96a0460392cea5399c", [:make, :mix], [{:castore, "~> 1.0 or ~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:cc_precompiler, "~> 0.2 or ~> 0.1.4", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8 or ~> 0.7.3", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "71b0a79ae7f199cacfc8e679b0e4ba25ee47dc02e182c5b9097efb29fbe14efd"}, "web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.5", "9dfeee8269b27e958a65b3e235b7e447769f66b5b5925385f5a569269164a210", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "4b977ba4a01918acbf77045ff88de7f6972c2a009213c515a445c48f224ffce9"}, diff --git a/priv/gettext/ja/LC_MESSAGES/errors.po b/priv/gettext/ja/LC_MESSAGES/errors.po @@ -3,16 +3,16 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-09-18 09:07+0000\n" -"PO-Revision-Date: 2021-09-19 09:45+0000\n" -"Last-Translator: Ryo Ueno <r.ueno.nfive@gmail.com>\n" +"PO-Revision-Date: 2024-02-16 11:49+0000\n" +"Last-Translator: SyoBoN <syobon@syobon.net>\n" "Language-Team: Japanese <https://translate.pleroma.social/projects/pleroma/" -"pleroma/ja/>\n" +"pleroma-backend-domain-errors/ja/>\n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 4.6.2\n" +"X-Generator: Weblate 4.13.1\n" ## This file is a PO Template file. ## @@ -25,11 +25,11 @@ msgstr "" ## effect: edit them in PO (`.po`) files instead. ## From Ecto.Changeset.cast/4 msgid "can't be blank" -msgstr "" +msgstr "空にすることはできません" ## From Ecto.Changeset.unique_constraint/3 msgid "has already been taken" -msgstr "" +msgstr "すでに使用されています" ## From Ecto.Changeset.put_change/3 msgid "is invalid" @@ -37,7 +37,7 @@ msgstr "" ## From Ecto.Changeset.validate_format/3 msgid "has invalid format" -msgstr "" +msgstr "書式が正しくありません" ## From Ecto.Changeset.validate_subset/3 msgid "has an invalid entry" @@ -45,7 +45,7 @@ msgstr "" ## From Ecto.Changeset.validate_exclusion/3 msgid "is reserved" -msgstr "" +msgstr "予約されています" ## From Ecto.Changeset.validate_confirmation/3 msgid "does not match confirmation" @@ -61,23 +61,23 @@ msgstr "" ## From Ecto.Changeset.validate_length/3 msgid "should be %{count} character(s)" msgid_plural "should be %{count} character(s)" -msgstr[0] "" +msgstr[0] "%{count}文字である必要があります" msgid "should have %{count} item(s)" msgid_plural "should have %{count} item(s)" -msgstr[0] "" +msgstr[0] "%{count}個の要素を含む必要があります" msgid "should be at least %{count} character(s)" msgid_plural "should be at least %{count} character(s)" -msgstr[0] "" +msgstr[0] "%{count}文字以上である必要があります" msgid "should have at least %{count} item(s)" msgid_plural "should have at least %{count} item(s)" -msgstr[0] "" +msgstr[0] "%{count}個以上の要素を含む必要があります" msgid "should be at most %{count} character(s)" msgid_plural "should be at most %{count} character(s)" -msgstr[0] "" +msgstr[0] "%{count}文字以下である必要があります" msgid "should have at most %{count} item(s)" msgid_plural "should have at most %{count} item(s)" @@ -102,7 +102,7 @@ msgstr "%{number}でなければなりません" #: lib/pleroma/web/common_api/common_api.ex:505 #, elixir-format msgid "Account not found" -msgstr "アカウントがありません" +msgstr "アカウントが見つかりません" #: lib/pleroma/web/common_api/common_api.ex:339 #, elixir-format @@ -226,7 +226,7 @@ msgstr "" #: lib/pleroma/web/common_api/utils.ex:414 #, elixir-format msgid "Invalid password." -msgstr "" +msgstr "パスワードが間違っています。" #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:220 #, elixir-format @@ -236,7 +236,7 @@ msgstr "" #: lib/pleroma/web/twitter_api/twitter_api.ex:109 #, elixir-format msgid "Kocaptcha service unavailable" -msgstr "" +msgstr "Kocaptchaが利用できません" #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:112 #, elixir-format @@ -259,12 +259,12 @@ msgstr "" #: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143 #, elixir-format msgid "Not found" -msgstr "" +msgstr "見つかりません" #: lib/pleroma/web/common_api/common_api.ex:331 #, elixir-format msgid "Poll's author can't vote" -msgstr "" +msgstr "作成者は投票できません" #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20 #: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49 @@ -284,17 +284,17 @@ msgstr "" #: lib/pleroma/web/common_api/activity_draft.ex:107 #, elixir-format msgid "The message visibility must be direct" -msgstr "" +msgstr "公開範囲は「ダイレクト」でなければいけません" #: lib/pleroma/web/common_api/utils.ex:573 #, elixir-format msgid "The status is over the character limit" -msgstr "" +msgstr "文字数制限を超過しています" #: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31 #, elixir-format msgid "This resource requires authentication." -msgstr "" +msgstr "認証が必要です。" #: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206 #, elixir-format @@ -304,7 +304,7 @@ msgstr "" #: lib/pleroma/web/common_api/common_api.ex:356 #, elixir-format msgid "Too many choices" -msgstr "" +msgstr "選択肢が多すぎます" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443 #, elixir-format @@ -320,7 +320,7 @@ msgstr "" #: lib/pleroma/web/oauth/oauth_controller.ex:308 #, elixir-format msgid "Your account is currently disabled" -msgstr "" +msgstr "あなたのアカウントは無効化されています" #: lib/pleroma/web/oauth/oauth_controller.ex:183 #: lib/pleroma/web/oauth/oauth_controller.ex:331 @@ -331,23 +331,23 @@ msgstr "" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390 #, elixir-format msgid "can't read inbox of %{nickname} as %{as_nickname}" -msgstr "" +msgstr "%{nickname}のinboxを%{as_nickname}として閲覧することはできません" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473 #, elixir-format msgid "can't update outbox of %{nickname} as %{as_nickname}" -msgstr "" +msgstr "%{nickname}のoutboxを%{as_nickname}として更新することはできません" #: lib/pleroma/web/common_api/common_api.ex:471 #, elixir-format msgid "conversation is already muted" -msgstr "" +msgstr "この会話はミュート済みです" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492 #, elixir-format msgid "error" -msgstr "" +msgstr "エラー" #: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32 #, elixir-format @@ -357,7 +357,7 @@ msgstr "" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62 #, elixir-format msgid "not found" -msgstr "" +msgstr "見つかりません" #: lib/pleroma/web/oauth/oauth_controller.ex:394 #, elixir-format @@ -382,28 +382,28 @@ msgstr "" #: lib/pleroma/web/oauth/oauth_controller.ex:410 #, elixir-format msgid "Failed to authenticate: %{message}." -msgstr "" +msgstr "認証に失敗しました: %{message}。" #: lib/pleroma/web/oauth/oauth_controller.ex:441 #, elixir-format msgid "Failed to set up user account." -msgstr "" +msgstr "ユーザーアカウントの設定に失敗しました。" #: lib/pleroma/plugs/oauth_scopes_plug.ex:38 #, elixir-format msgid "Insufficient permissions: %{permissions}." -msgstr "" +msgstr "%{permissions}の権限が必要です。" #: lib/pleroma/plugs/uploaded_media.ex:104 #, elixir-format msgid "Internal Error" -msgstr "" +msgstr "内部エラー" #: lib/pleroma/web/oauth/fallback_controller.ex:22 #: lib/pleroma/web/oauth/fallback_controller.ex:29 #, elixir-format msgid "Invalid Username/Password" -msgstr "" +msgstr "ユーザー名/パスワードが間違っています" #: lib/pleroma/web/twitter_api/twitter_api.ex:118 #, elixir-format @@ -423,7 +423,7 @@ msgstr "" #: lib/pleroma/web/oauth/fallback_controller.ex:14 #, elixir-format msgid "Unknown error, please check the details and try again." -msgstr "" +msgstr "不明なエラーです。詳細を確認し、再度試行してください。" #: lib/pleroma/web/oauth/oauth_controller.ex:119 #: lib/pleroma/web/oauth/oauth_controller.ex:158 @@ -449,17 +449,17 @@ msgstr "" #: lib/pleroma/web/twitter_api/twitter_api.ex:103 #, elixir-format msgid "CAPTCHA Error" -msgstr "" +msgstr "CAPTCHAエラー" #: lib/pleroma/web/common_api/common_api.ex:290 #, elixir-format msgid "Could not add reaction emoji" -msgstr "" +msgstr "リアクションを追加できません" #: lib/pleroma/web/common_api/common_api.ex:301 #, elixir-format msgid "Could not remove reaction emoji" -msgstr "" +msgstr "リアクションを解除できません" #: lib/pleroma/web/twitter_api/twitter_api.ex:129 #, elixir-format @@ -469,7 +469,7 @@ msgstr "" #: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92 #, elixir-format msgid "List not found" -msgstr "" +msgstr "リストが見つかりません" #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123 #, elixir-format @@ -480,7 +480,7 @@ msgstr "" #: lib/pleroma/web/oauth/oauth_controller.ex:321 #, elixir-format msgid "Password reset is required" -msgstr "" +msgstr "パスワードのリセットが必要です" #: lib/pleroma/tests/auth_test_controller.ex:9 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6 @@ -522,7 +522,7 @@ msgstr "" #: lib/pleroma/plugs/ensure_authenticated_plug.ex:28 #, elixir-format msgid "Two-factor authentication enabled, you must use a access token." -msgstr "" +msgstr "二段階認証が有効になっています。アクセストークンが必要です。" #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210 #, elixir-format @@ -552,7 +552,7 @@ msgstr "" #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 #, elixir-format msgid "Web push subscription is disabled on this Pleroma instance" -msgstr "" +msgstr "このPleromaインスタンスではプッシュ通知が利用できません" #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451 #, elixir-format @@ -562,19 +562,19 @@ msgstr "" #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126 #, elixir-format msgid "authorization required for timeline view" -msgstr "" +msgstr "タイムラインを閲覧するには認証が必要です" #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24 #, elixir-format msgid "Access denied" -msgstr "" +msgstr "アクセスが拒否されました" #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282 #, elixir-format msgid "This API requires an authenticated user" -msgstr "" +msgstr "このAPIを利用するには認証が必要です" #: lib/pleroma/plugs/user_is_admin_plug.ex:21 #, elixir-format msgid "User is not an admin." -msgstr "" +msgstr "ユーザーは管理者ではありません。" diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld @@ -40,7 +40,9 @@ "@type": "@id" }, "vcard": "http://www.w3.org/2006/vcard/ns#", - "formerRepresentations": "litepub:formerRepresentations" + "formerRepresentations": "litepub:formerRepresentations", + "sm": "http://smithereen.software/ns#", + "nonAnonymous": "sm:nonAnonymous" } ] } diff --git a/test/fixtures/ccworld-ap-bridge_note.json b/test/fixtures/ccworld-ap-bridge_note.json @@ -0,0 +1 @@ +{"@context":"https://www.w3.org/ns/activitystreams","type":"Note","id":"https://cc.mkdir.uk/ap/note/e5d1d0a1-1ab3-4498-9949-588e3fdea286","attributedTo":"https://cc.mkdir.uk/ap/acct/hiira","inReplyTo":"","quoteUrl":"","content":"おはコンー","published":"2024-01-19T22:08:05Z","to":["https://www.w3.org/ns/activitystreams#Public"],"tag":null,"attachment":[],"object":null} diff --git a/test/fixtures/minds-invalid-mention-post.json b/test/fixtures/minds-invalid-mention-post.json @@ -0,0 +1 @@ +{"@context":"https://www.w3.org/ns/activitystreams","type":"Note","id":"https://www.minds.com/api/activitypub/users/1198929502760083472/entities/urn:comment:1600926863310458883:0:0:0:1600932467852709903","attributedTo":"https://www.minds.com/api/activitypub/users/1198929502760083472","content":"\u003Ca class=\u0022u-url mention\u0022 href=\u0022https://www.minds.com/lain\u0022 target=\u0022_blank\u0022\u003E@lain\u003C/a\u003E corn syrup.","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://www.minds.com/api/activitypub/users/1198929502760083472/followers","https://lain.com/users/lain"],"tag":[{"type":"Mention","href":"https://www.minds.com/api/activitypub/users/464237775479123984","name":"@lain"}],"url":"https://www.minds.com/newsfeed/1600926863310458883?focusedCommentUrn=urn:comment:1600926863310458883:0:0:0:1600932467852709903","published":"2024-02-04T17:34:03+00:00","inReplyTo":"https://lain.com/objects/36254095-c839-4167-bcc2-b361d5de9198","source":{"content":"@lain corn syrup.","mediaType":"text/plain"}} +\ No newline at end of file diff --git a/test/fixtures/minds-pleroma-mentioned-post.json b/test/fixtures/minds-pleroma-mentioned-post.json @@ -0,0 +1 @@ +{"@context":["https://www.w3.org/ns/activitystreams","https://lain.com/schemas/litepub-0.1.jsonld",{"@language":"und"}],"actor":"https://lain.com/users/lain","attachment":[],"attributedTo":"https://lain.com/users/lain","cc":["https://lain.com/users/lain/followers"],"content":"which diet is the best for cognitive dissonance","context":"https://lain.com/contexts/98c8a130-e813-4797-8973-600e80114317","conversation":"https://lain.com/contexts/98c8a130-e813-4797-8973-600e80114317","id":"https://lain.com/objects/36254095-c839-4167-bcc2-b361d5de9198","published":"2024-02-04T17:11:23.931890Z","repliesCount":11,"sensitive":null,"source":{"content":"which diet is the best for cognitive dissonance","mediaType":"text/plain"},"summary":"","tag":[],"to":["https://www.w3.org/ns/activitystreams#Public"],"type":"Note"} +\ No newline at end of file diff --git a/test/fixtures/rich_media/google.html b/test/fixtures/rich_media/google.html @@ -0,0 +1,12 @@ +<meta property="og:url" content="https://google.com"> +<meta property="og:type" content="website"> +<meta property="og:title" content="Google"> +<meta property="og:description" content="Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for."> +<meta property="og:image" content=""> + +<meta name="twitter:card" content="summary_large_image"> +<meta property="twitter:domain" content="google.com"> +<meta property="twitter:url" content="https://google.com"> +<meta name="twitter:title" content="Google"> +<meta name="twitter:description" content="Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for."> +<meta name="twitter:image" content=""> diff --git a/test/fixtures/rich_media/oembed.html b/test/fixtures/rich_media/oembed.html @@ -1,3 +1,3 @@ <link rel="alternate" type="application/json+oembed" - href="http://example.com/oembed.json" + href="https://example.com/oembed.json" title="Bacon Lollys oEmbed Profile" /> diff --git a/test/fixtures/rich_media/yahoo.html b/test/fixtures/rich_media/yahoo.html @@ -0,0 +1,12 @@ +<meta property="og:url" content="https://yahoo.com"> +<meta property="og:type" content="website"> +<meta property="og:title" content="Yahoo | Mail, Weather, Search, Politics, News, Finance, Sports & Videos"> +<meta property="og:description" content="Latest news coverage, email, free stock quotes, live scores and video are just the beginning. Discover more every day at Yahoo!"> +<meta property="og:image" content="https://s.yimg.com/cv/apiv2/social/images/yahoo_default_logo.png"> + +<meta name="twitter:card" content="summary_large_image"> +<meta property="twitter:domain" content="yahoo.com"> +<meta property="twitter:url" content="https://yahoo.com"> +<meta name="twitter:title" content="Yahoo | Mail, Weather, Search, Politics, News, Finance, Sports & Videos"> +<meta name="twitter:description" content="Latest news coverage, email, free stock quotes, live scores and video are just the beginning. Discover more every day at Yahoo!"> +<meta name="twitter:image" content="https://s.yimg.com/cv/apiv2/social/images/yahoo_default_logo.png"> diff --git a/test/fixtures/tesla_mock/smithereen_non_anonymous_poll.json b/test/fixtures/tesla_mock/smithereen_non_anonymous_poll.json @@ -0,0 +1 @@ +{"type":"Question","id":"https://friends.grishka.me/posts/54642","attributedTo":"https://friends.grishka.me/users/1","content":"<p>здесь тоже можно что-то написать отдельно от опроса</p>","published":"2021-09-04T00:22:16Z","url":"https://friends.grishka.me/posts/54642","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://friends.grishka.me/users/1/followers"],"replies":{"type":"Collection","id":"https://friends.grishka.me/posts/54642/replies","first":{"type":"CollectionPage","items":[],"partOf":"https://friends.grishka.me/posts/54642/replies","next":"https://friends.grishka.me/posts/54642/replies?page=1"}},"sensitive":false,"likes":"https://friends.grishka.me/posts/54642/likes","name":"тестовый опрос","oneOf":[{"type":"Note","id":"https://friends.grishka.me/posts/54642#options/76","name":"тестовый ответ 1","replies":{"type":"Collection","id":"https://friends.grishka.me/activitypub/objects/polls/24/options/76/votes","totalItems":4,"items":[]}},{"type":"Note","id":"https://friends.grishka.me/posts/54642#options/77","name":"тестовый ответ 2","replies":{"type":"Collection","id":"https://friends.grishka.me/activitypub/objects/polls/24/options/77/votes","totalItems":4,"items":[]}},{"type":"Note","id":"https://friends.grishka.me/posts/54642#options/78","name":"тестовый ответ 3","replies":{"type":"Collection","id":"https://friends.grishka.me/activitypub/objects/polls/24/options/78/votes","totalItems":6,"items":[]}}],"votersCount":14,"nonAnonymous":true,"@context":["https://www.w3.org/ns/activitystreams",{"sensitive":"as:sensitive","toot":"http://joinmastodon.org/ns#","sm":"http://smithereen.software/ns#","votersCount":"toot:votersCount","nonAnonymous":"sm:nonAnonymous"}]} +\ No newline at end of file diff --git a/test/fixtures/tesla_mock/smithereen_user.json b/test/fixtures/tesla_mock/smithereen_user.json @@ -0,0 +1 @@ +{"type":"Person","id":"https://friends.grishka.me/users/1","name":"Григорий Клюшников","icon":{"type":"Image","image":{"type":"Image","url":"https://friends.grishka.me/i/6QLsOws97AWp5N_osd74C1IC1ijnFopyCBD9MSEeXNQ/q:93/bG9jYWw6Ly8vcy91cGxvYWRzL2F2YXRhcnMvNTYzODRhODEwODk5ZTRjMzI4YmY4YmQwM2Q2MWM3NmMud2VicA.jpg","mediaType":"image/jpeg","width":1280,"height":960},"width":573,"height":572,"cropRegion":[0.26422762870788574,0.3766937553882599,0.7113820910453796,0.9728997349739075],"url":"https://friends.grishka.me/i/ql_49PQcETAWgY_nC-Qj63H_Oa6FyOAEoWFkUSSkUvQ/c:573:572:nowe:338:362/q:93/bG9jYWw6Ly8vcy91cGxvYWRzL2F2YXRhcnMvNTYzODRhODEwODk5ZTRjMzI4YmY4YmQwM2Q2MWM3NmMud2VicA.jpg","mediaType":"image/jpeg"},"summary":"<p>Делаю эту хрень, пытаюсь вырвать социальные сети из жадных лап корпораций</p>\n<p></p>\n<p></p>\n<p></p>\n<p></p>\n<p></p>\n<p></p>\n<p></p>\n<p>This server does NOT support direct messages. Please write me <a href=\"https://t.me/grishka\">on Telegram</a> or <a href=\"https://matrix.to/#/@grishk:matrix.org\">Matrix</a>.</p>","url":"https://friends.grishka.me/grishka","preferredUsername":"grishka","inbox":"https://friends.grishka.me/users/1/inbox","outbox":"https://friends.grishka.me/users/1/outbox","followers":"https://friends.grishka.me/users/1/followers","following":"https://friends.grishka.me/users/1/following","endpoints":{"sharedInbox":"https://friends.grishka.me/activitypub/sharedInbox","collectionSimpleQuery":"https://friends.grishka.me/users/1/collectionQuery"},"publicKey":{"id":"https://friends.grishka.me/users/1#main-key","owner":"https://friends.grishka.me/users/1","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjlakm+i/d9ER/hIeR7KfiFW+SdLZj2SkKIeM8cmR+YFJuh9ghFqXrkFEjcaqUnAFqe5gYDNSQACnDLA8y4DnzjfGNIohKAnRoa9x6GORmfKQvcnjaTZ53S1NvUiPPyc0Pv/vfCtY/Ab0CEXe5BLqL38oZn817Jf7pBrPRTYH7m012kvwAUTT6k0Y8lPITBEG7nzYbbuGcrN9Y/RDdwE08jmBXlZ45bahRH3VNXVpQE17dCzJB+7k+iJ1R7YCoI+DuMlBYGXGE2KVk46NZTuLnOjFV9SyXfWX4/SrJM4oxev+SX2N75tQgmNZmVVHeqg2ZcbC0WCfNjJOi2HHS9MujwIDAQAB\n-----END PUBLIC KEY-----\n"},"wall":"https://friends.grishka.me/users/1/wall","firstName":"Григорий","lastName":"Клюшников","middleName":"Александрович","vcard:bday":"1993-01-22","gender":"http://schema.org#Male","supportsFriendRequests":true,"friends":"https://friends.grishka.me/users/1/friends","groups":"https://friends.grishka.me/users/1/groups","capabilities":{"supportsFriendRequests":true},"@context":["https://www.w3.org/ns/activitystreams",{"sm":"http://smithereen.software/ns#","cropRegion":{"@id":"sm:cropRegion","@container":"@list"},"wall":{"@id":"sm:wall","@type":"@id"},"collectionSimpleQuery":"sm:collectionSimpleQuery","sc":"http://schema.org#","firstName":"sc:givenName","lastName":"sc:familyName","middleName":"sc:additionalName","gender":{"@id":"sc:gender","@type":"sc:GenderType"},"maidenName":"sm:maidenName","friends":{"@id":"sm:friends","@type":"@id"},"groups":{"@id":"sm:groups","@type":"@id"},"vcard":"http://www.w3.org/2006/vcard/ns#","capabilities":"litepub:capabilities","supportsFriendRequests":"sm:supportsFriendRequests","litepub":"http://litepub.social/ns#"},"https://w3id.org/security/v1"]} +\ No newline at end of file diff --git a/test/mix/pleroma_test.exs b/test/mix/pleroma_test.exs @@ -39,7 +39,7 @@ defmodule Mix.PleromaTest do describe "get_option/3" do test "get from options" do - assert get_option([domain: "some-domain.com"], :domain, "Promt") == "some-domain.com" + assert get_option([domain: "some-domain.com"], :domain, "Prompt") == "some-domain.com" end test "get from prompt" do diff --git a/test/mix/tasks/pleroma/config_test.exs b/test/mix/tasks/pleroma/config_test.exs @@ -140,7 +140,6 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do federating: true, federation_incoming_replies_max_depth: 100, federation_reachability_timeout_days: 7, - federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher], allow_relay: true, public: true, quarantined_instances: [], @@ -183,8 +182,8 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do assert File.exists?(temp_file) {:ok, file} = File.read(temp_file) - assert file == - "import Config\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" + assert file =~ "import Config\n" + assert file =~ "A Pleroma instance, an alternative fediverse server" end end diff --git a/test/mix/tasks/pleroma/ecto/rollback_test.exs b/test/mix/tasks/pleroma/ecto/rollback_test.exs @@ -13,7 +13,7 @@ defmodule Mix.Tasks.Pleroma.Ecto.RollbackTest do assert capture_log(fn -> Mix.Tasks.Pleroma.Ecto.Rollback.run(["--env", "test"]) - end) =~ "[info] Rollback succesfully" + end) =~ "[info] Rollback successfully" Logger.configure(level: level) end diff --git a/test/mix/tasks/pleroma/robots_txt_test.exs b/test/mix/tasks/pleroma/robots_txt_test.exs @@ -26,7 +26,7 @@ defmodule Mix.Tasks.Pleroma.RobotsTxtTest do assert file == "User-Agent: *\nDisallow: /\n" end - test "to existance folder" do + test "to existing folder" do path = "test/fixtures/" file_path = path <> "robots.txt" clear_config([:instance, :static_dir], path) diff --git a/test/pleroma/activity_test.exs b/test/pleroma/activity_test.exs @@ -145,6 +145,7 @@ defmodule Pleroma.ActivityTest do setup do: clear_config([:instance, :limit_to_local_content]) + @tag :skip_darwin test "finds utf8 text in statuses", %{ japanese_activity: japanese_activity, user: user diff --git a/test/pleroma/config/deprecation_warnings_test.exs b/test/pleroma/config/deprecation_warnings_test.exs @@ -125,13 +125,12 @@ defmodule Pleroma.Config.DeprecationWarningsTest do media_removal: ["some.removal", {"some.other.instance", "Some reason"}] ) - expected_config = [ + expected_config = {:media_removal, [{"some.removal", ""}, {"some.other.instance", "Some reason"}]} - ] capture_log(fn -> DeprecationWarnings.warn() end) - assert Config.get([:mrf_simple]) == expected_config + assert expected_config in Config.get([:mrf_simple]) end test "doesn't give a warning with correct config" do @@ -215,7 +214,7 @@ defmodule Pleroma.Config.DeprecationWarningsTest do ``` config :pleroma, :mrf, - transparency_exclusions: [{"instance.tld", "Reason to exlude transparency"}] + transparency_exclusions: [{"instance.tld", "Reason to exclude transparency"}] ``` """ end @@ -327,11 +326,11 @@ defmodule Pleroma.Config.DeprecationWarningsTest do end) =~ "Your config is using old namespace for activity expiration configuration." end - test "check_uploders_s3_public_endpoint/0" do + test "check_uploaders_s3_public_endpoint/0" do clear_config([Pleroma.Uploaders.S3], public_endpoint: "https://fake.amazonaws.com/bucket/") assert capture_log(fn -> - DeprecationWarnings.check_uploders_s3_public_endpoint() + DeprecationWarnings.check_uploaders_s3_public_endpoint() end) =~ "Your config is using the old setting for controlling the URL of media uploaded to your S3 bucket." end diff --git a/test/pleroma/config_db_test.exs b/test/pleroma/config_db_test.exs @@ -321,7 +321,7 @@ defmodule Pleroma.ConfigDBTest do }) == {:proxy_url, {:socks5, {127, 0, 0, 1}, 1234}} end - test "tuple with n childs" do + test "tuple with n children" do assert ConfigDB.to_elixir_types(%{ "tuple" => [ "v1", @@ -399,7 +399,7 @@ defmodule Pleroma.ConfigDBTest do assert ConfigDB.to_elixir_types(a: 1, b: 2, c: "string") == [a: 1, b: 2, c: "string"] end - test "complex keyword with nested mixed childs" do + test "complex keyword with nested mixed children" do assert ConfigDB.to_elixir_types([ %{"tuple" => [":uploader", "Pleroma.Uploaders.Local"]}, %{"tuple" => [":filters", ["Pleroma.Upload.Filter.Dedupe"]]}, diff --git a/test/pleroma/conversation/participation_test.exs b/test/pleroma/conversation/participation_test.exs @@ -57,7 +57,7 @@ defmodule Pleroma.Conversation.ParticipationTest do assert Participation.unread_count(other_user) == 0 end - test "for a new conversation, it sets the recipents of the participation" do + test "for a new conversation, it sets the recipients of the participation" do user = insert(:user) other_user = insert(:user) third_user = insert(:user) diff --git a/test/pleroma/emoji/loader_test.exs b/test/pleroma/emoji/loader_test.exs @@ -72,7 +72,7 @@ defmodule Pleroma.Emoji.LoaderTest do assert group == "special file" end - test "no mathing returns nil", %{groups: groups} do + test "no matching returns nil", %{groups: groups} do group = groups |> Loader.match_extra("/emoji/some_undefined.png") diff --git a/test/pleroma/emoji_test.exs b/test/pleroma/emoji_test.exs @@ -6,26 +6,26 @@ defmodule Pleroma.EmojiTest do use ExUnit.Case, async: true alias Pleroma.Emoji - describe "is_unicode_emoji?/1" do + describe "unicode?/1" do test "tells if a string is an unicode emoji" do - refute Emoji.is_unicode_emoji?("X") - refute Emoji.is_unicode_emoji?("ね") + refute Emoji.unicode?("X") + refute Emoji.unicode?("ね") # Only accept fully-qualified (RGI) emoji # See http://www.unicode.org/reports/tr51/ - refute Emoji.is_unicode_emoji?("❤") - refute Emoji.is_unicode_emoji?("☂") + refute Emoji.unicode?("❤") + refute Emoji.unicode?("☂") - assert Emoji.is_unicode_emoji?("🥺") - assert Emoji.is_unicode_emoji?("🤰") - assert Emoji.is_unicode_emoji?("❤️") - assert Emoji.is_unicode_emoji?("🏳️‍⚧️") - assert Emoji.is_unicode_emoji?("🫵") + assert Emoji.unicode?("🥺") + assert Emoji.unicode?("🤰") + assert Emoji.unicode?("❤️") + assert Emoji.unicode?("🏳️‍⚧️") + assert Emoji.unicode?("🫵") # Additionally, we accept regional indicators. - assert Emoji.is_unicode_emoji?("🇵") - assert Emoji.is_unicode_emoji?("🇴") - assert Emoji.is_unicode_emoji?("🇬") + assert Emoji.unicode?("🇵") + assert Emoji.unicode?("🇴") + assert Emoji.unicode?("🇬") end end diff --git a/test/pleroma/formatter_test.exs b/test/pleroma/formatter_test.exs @@ -324,7 +324,7 @@ defmodule Pleroma.FormatterTest do assert {_text, [], ^expected_tags} = Formatter.linkify(text) end - test "parses mulitple tags in html" do + test "parses multiple tags in html" do text = "<p>#tag1 #tag2 #tag3 #tag4</p>" expected_tags = [ @@ -347,7 +347,7 @@ defmodule Pleroma.FormatterTest do assert {_text, [], ^expected_tags} = Formatter.linkify(text) end - test "parses mulitple tags on mulitple lines in html" do + test "parses multiple tags on multiple lines in html" do text = "<p>testing...</p><p>#tag1 #tag2 #tag3 #tag4</p><p>paragraph</p><p>#tag5 #tag6 #tag7 #tag8</p>" diff --git a/test/pleroma/healthcheck_test.exs b/test/pleroma/healthcheck_test.exs @@ -9,14 +9,16 @@ defmodule Pleroma.HealthcheckTest do test "system_info/0" do result = Healthcheck.system_info() |> Map.from_struct() - assert Map.keys(result) == [ + keys = Map.keys(result) + + assert Keyword.equal?(keys, [ :active, :healthy, :idle, :job_queue_stats, :memory_used, :pool_size - ] + ]) end describe "check_health/1" do @@ -25,7 +27,7 @@ defmodule Pleroma.HealthcheckTest do refute result.healthy end - test "chech_health/1" do + test "check_health/1" do result = Healthcheck.check_health(%Healthcheck{pool_size: 10, active: 9}) assert result.healthy end diff --git a/test/pleroma/http/adapter_helper/gun_test.exs b/test/pleroma/http/adapter_helper/gun_test.exs @@ -36,7 +36,7 @@ defmodule Pleroma.HTTP.AdapterHelper.GunTest do assert opts[:certificates_verification] end - test "https url with non standart port" do + test "https url with non-standard port" do uri = URI.parse("https://example.com:115") opts = Gun.options([receive_conn: false], uri) @@ -44,7 +44,7 @@ defmodule Pleroma.HTTP.AdapterHelper.GunTest do assert opts[:certificates_verification] end - test "merges with defaul http adapter config" do + test "merges with default 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) diff --git a/test/pleroma/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs @@ -268,17 +268,6 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do end) end - test "accepts valid token on Sec-WebSocket-Protocol header", %{token: token} do - assert {:ok, _} = start_socket("?stream=user", [{"Sec-WebSocket-Protocol", token.token}]) - - capture_log(fn -> - assert {:error, %WebSockex.RequestError{code: 401}} = - start_socket("?stream=user", [{"Sec-WebSocket-Protocol", "I am a friend"}]) - - Process.sleep(30) - end) - end - test "accepts valid token on client-sent event", %{token: token} do assert {:ok, pid} = start_socket() diff --git a/test/pleroma/maps_test.exs b/test/pleroma/maps_test.exs @@ -0,0 +1,22 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2024 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.MapsTest do + use Pleroma.DataCase, async: true + + alias Pleroma.Maps + + describe "filter_empty_values/1" do + assert %{"bar" => "b", "ray" => ["foo"], "objs" => %{"a" => "b"}} == + Maps.filter_empty_values(%{ + "foo" => nil, + "fooz" => "", + "bar" => "b", + "rei" => [], + "ray" => ["foo"], + "obj" => %{}, + "objs" => %{"a" => "b"} + }) + end +end diff --git a/test/pleroma/mfa/totp_test.exs b/test/pleroma/mfa/totp_test.exs @@ -7,6 +7,8 @@ defmodule Pleroma.MFA.TOTPTest do alias Pleroma.MFA.TOTP + import Pleroma.Tests.Helpers, only: [uri_equal?: 2] + test "create provisioning_uri to generate qrcode" do uri = TOTP.provisioning_uri("test-secrcet", "test@example.com", @@ -15,7 +17,9 @@ defmodule Pleroma.MFA.TOTPTest do period: 60 ) - assert uri == + assert uri_equal?( + uri, "otpauth://totp/test@example.com?digits=8&issuer=Plerome-42&period=60&secret=test-secrcet" + ) end end diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs @@ -101,8 +101,7 @@ defmodule Pleroma.Object.FetcherTest do test "it returns thread depth exceeded error if thread depth is exceeded" do clear_config([:instance, :federation_incoming_replies_max_depth], 0) - assert {:error, "Max thread distance exceeded."} = - Fetcher.fetch_object_from_id(@ap_id, depth: 1) + assert {:error, :allowed_depth} = Fetcher.fetch_object_from_id(@ap_id, depth: 1) end test "it fetches object if max thread depth is restricted to 0 and depth is not specified" do @@ -220,14 +219,14 @@ defmodule Pleroma.Object.FetcherTest do end test "handle HTTP 410 Gone response" do - assert {:error, "Object has been deleted"} == + assert {:error, :not_found} == Fetcher.fetch_and_contain_remote_object_from_id( "https://mastodon.example.org/users/userisgone" ) end test "handle HTTP 404 response" do - assert {:error, "Object has been deleted"} == + assert {:error, :not_found} == Fetcher.fetch_and_contain_remote_object_from_id( "https://mastodon.example.org/users/userisgone404" ) diff --git a/test/pleroma/otp_version_test.exs b/test/pleroma/otp_version_test.exs @@ -28,7 +28,7 @@ defmodule Pleroma.OTPVersionTest do "23.0" end - test "with non existance file" do + test "with nonexistent file" do assert OTPVersion.get_version_from_files([ "test/fixtures/warnings/otp_version/non-exising", "test/fixtures/warnings/otp_version/22.4" diff --git a/test/pleroma/repo/migrations/autolinker_to_linkify_test.exs b/test/pleroma/repo/migrations/autolinker_to_linkify_test.exs @@ -29,13 +29,13 @@ defmodule Pleroma.Repo.Migrations.AutolinkerToLinkifyTest do %{value: new_opts} = ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Formatter}) - assert new_opts == [ + assert Keyword.equal?(new_opts, class: false, extra: true, new_window: false, rel: "testing", strip_prefix: false - ] + ) clear_config(Pleroma.Formatter, new_opts) assert new_opts == Pleroma.Config.get(Pleroma.Formatter) @@ -67,6 +67,6 @@ defmodule Pleroma.Repo.Migrations.AutolinkerToLinkifyTest do strip_prefix: false ] - assert migration.transform_opts(old_opts) == expected_opts + assert Keyword.equal?(migration.transform_opts(old_opts), expected_opts) end end diff --git a/test/pleroma/repo/migrations/fix_malformed_formatter_config_test.exs b/test/pleroma/repo/migrations/fix_malformed_formatter_config_test.exs @@ -26,13 +26,13 @@ defmodule Pleroma.Repo.Migrations.FixMalformedFormatterConfigTest do %{value: new_opts} = ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Formatter}) - assert new_opts == [ + assert Keyword.equal?(new_opts, class: false, extra: true, new_window: false, rel: "F", strip_prefix: false - ] + ) clear_config(Pleroma.Formatter, new_opts) assert new_opts == Pleroma.Config.get(Pleroma.Formatter) diff --git a/test/pleroma/reverse_proxy_test.exs b/test/pleroma/reverse_proxy_test.exs @@ -306,7 +306,7 @@ defmodule Pleroma.ReverseProxyTest do end describe "response content disposition header" do - test "not atachment", %{conn: conn} do + test "not attachment", %{conn: conn} do disposition_headers_mock([ {"content-type", "image/gif"}, {"content-length", "0"} diff --git a/test/pleroma/signature_test.exs b/test/pleroma/signature_test.exs @@ -113,7 +113,7 @@ defmodule Pleroma.SignatureTest do test "it calls webfinger for 'acct:' accounts" do with_mock(Pleroma.Web.WebFinger, - finger: fn _ -> %{"ap_id" => "https://gensokyo.2hu/users/raymoo"} end + finger: fn _ -> {:ok, %{"ap_id" => "https://gensokyo.2hu/users/raymoo"}} end ) do assert Signature.key_id_to_actor_id("acct:raymoo@gensokyo.2hu") == {:ok, "https://gensokyo.2hu/users/raymoo"} diff --git a/test/pleroma/user/backup_test.exs b/test/pleroma/user/backup_test.exs @@ -166,6 +166,7 @@ defmodule Pleroma.User.BackupTest do test "it creates a zip archive with user data" do user = insert(:user, %{nickname: "cofe", name: "Cofe", ap_id: "http://cofe.io/users/cofe"}) + %{ap_id: other_ap_id} = other_user = insert(:user) {:ok, %{object: %{data: %{"id" => id1}}} = status1} = CommonAPI.post(user, %{status: "status1"}) @@ -182,6 +183,8 @@ defmodule Pleroma.User.BackupTest do Bookmark.create(user.id, status2.id) Bookmark.create(user.id, status3.id) + CommonAPI.follow(user, other_user) + assert {:ok, backup} = user |> Backup.new() |> Repo.insert() assert {:ok, path} = Backup.export(backup, self()) assert {:ok, zipfile} = :zip.zip_open(String.to_charlist(path), [:memory]) @@ -261,6 +264,16 @@ defmodule Pleroma.User.BackupTest do "type" => "OrderedCollection" } = Jason.decode!(json) + assert {:ok, {'following.json', json}} = :zip.zip_get('following.json', zipfile) + + assert %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "id" => "following.json", + "orderedItems" => [^other_ap_id], + "totalItems" => 1, + "type" => "OrderedCollection" + } = Jason.decode!(json) + :zip.zip_close(zipfile) File.rm!(path) end diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs @@ -226,7 +226,7 @@ defmodule Pleroma.UserTest do assert [] = User.get_follow_requests(followed) end - test "follow_all follows mutliple users" do + test "follow_all follows multiple users" do user = insert(:user) followed_zero = insert(:user) followed_one = insert(:user) @@ -250,7 +250,7 @@ defmodule Pleroma.UserTest do refute User.following?(user, reverse_blocked) end - test "follow_all follows mutliple users without duplicating" do + test "follow_all follows multiple users without duplicating" do user = insert(:user) followed_zero = insert(:user) followed_one = insert(:user) @@ -873,7 +873,7 @@ defmodule Pleroma.UserTest do end end - describe "get_or_fetch/1 remote users with tld, while BE is runned on subdomain" do + describe "get_or_fetch/1 remote users with tld, while BE is running on a subdomain" do setup do: clear_config([Pleroma.Web.WebFinger, :update_nickname_on_user_fetch], true) test "for mastodon" do @@ -1018,13 +1018,13 @@ defmodule Pleroma.UserTest do @tag capture_log: true test "returns nil if no user could be fetched" do - {:error, fetched_user} = User.get_or_fetch_by_nickname("nonexistant@social.heldscal.la") - assert fetched_user == "not found nonexistant@social.heldscal.la" + {:error, fetched_user} = User.get_or_fetch_by_nickname("nonexistent@social.heldscal.la") + assert fetched_user == "not found nonexistent@social.heldscal.la" end - test "returns nil for nonexistant local user" do - {:error, fetched_user} = User.get_or_fetch_by_nickname("nonexistant") - assert fetched_user == "not found nonexistant" + test "returns nil for nonexistent local user" do + {:error, fetched_user} = User.get_or_fetch_by_nickname("nonexistent") + assert fetched_user == "not found nonexistent" end test "updates an existing user, if stale" do @@ -1132,7 +1132,7 @@ defmodule Pleroma.UserTest do assert cs.valid? end - test "it sets the follower_adress" do + test "it sets the follower_address" do cs = User.remote_user_changeset(@valid_remote) # remote users get a fake local follower address assert cs.changes.follower_address == @@ -2424,20 +2424,20 @@ defmodule Pleroma.UserTest do end end - describe "is_internal_user?/1" do + describe "internal?/1" do test "non-internal user returns false" do user = insert(:user) - refute User.is_internal_user?(user) + refute User.internal?(user) end test "user with no nickname returns true" do user = insert(:user, %{nickname: nil}) - assert User.is_internal_user?(user) + assert User.internal?(user) end test "user with internal-prefixed nickname returns true" do user = insert(:user, %{nickname: "internal.test"}) - assert User.is_internal_user?(user) + assert User.internal?(user) end end @@ -2683,13 +2683,23 @@ defmodule Pleroma.UserTest do end describe "full_nickname/1" do - test "returns fully qualified nickname for local and remote users" do - local_user = - insert(:user, nickname: "local_user", ap_id: "https://somehost.com/users/local_user") + test "returns fully qualified nickname for local users" do + local_user = insert(:user, nickname: "local_user") + assert User.full_nickname(local_user) == "local_user@localhost" + end + + test "returns fully qualified nickname for local users when using different domain for webfinger" do + clear_config([Pleroma.Web.WebFinger, :domain], "plemora.dev") + + local_user = insert(:user, nickname: "local_user") + + assert User.full_nickname(local_user) == "local_user@plemora.dev" + end + + test "returns fully qualified nickname for remote users" do remote_user = insert(:user, nickname: "remote@host.com", local: false) - assert User.full_nickname(local_user) == "local_user@somehost.com" assert User.full_nickname(remote_user) == "remote@host.com" end @@ -2918,4 +2928,51 @@ defmodule Pleroma.UserTest do refute User.endorses?(user, pinned_user) end end + + test "it checks fields links for a backlink" do + user = insert(:user, ap_id: "https://social.example.org/users/lain") + + fields = [ + %{"name" => "Link", "value" => "http://example.com/rel_me/null"}, + %{"name" => "Verified link", "value" => "http://example.com/rel_me/link"}, + %{"name" => "Not a link", "value" => "i'm not a link"} + ] + + user + |> User.update_and_set_cache(%{raw_fields: fields}) + + ObanHelpers.perform_all() + + user = User.get_cached_by_id(user.id) + + assert [ + %{"verified_at" => nil}, + %{"verified_at" => verified_at}, + %{"verified_at" => nil} + ] = user.fields + + assert is_binary(verified_at) + end + + test "updating fields does not invalidate previously validated links" do + user = insert(:user, ap_id: "https://social.example.org/users/lain") + + user + |> User.update_and_set_cache(%{ + raw_fields: [%{"name" => "verified link", "value" => "http://example.com/rel_me/link"}] + }) + + ObanHelpers.perform_all() + + %User{fields: [%{"verified_at" => verified_at}]} = user = User.get_cached_by_id(user.id) + + user + |> User.update_and_set_cache(%{ + raw_fields: [%{"name" => "Verified link", "value" => "http://example.com/rel_me/link"}] + }) + + user = User.get_cached_by_id(user.id) + + assert [%{"verified_at" => ^verified_at}] = user.fields + end end diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -221,7 +221,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do user = insert(:user) {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"}) - assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post) + assert Pleroma.Web.ActivityPub.Visibility.local_public?(post) object = Object.normalize(post, fetch: false) uuid = String.split(object.data["id"], "/") |> List.last() @@ -238,7 +238,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do user = insert(:user) {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"}) - assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post) + assert Pleroma.Web.ActivityPub.Visibility.local_public?(post) object = Object.normalize(post, fetch: false) uuid = String.split(object.data["id"], "/") |> List.last() @@ -259,7 +259,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do {:ok, post} = CommonAPI.post(user, %{status: "test @#{reader.nickname}", visibility: "local"}) - assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post) + assert Pleroma.Web.ActivityPub.Visibility.local_public?(post) object = Object.normalize(post, fetch: false) uuid = String.split(object.data["id"], "/") |> List.last() @@ -436,7 +436,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do user = insert(:user) {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"}) - assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post) + assert Pleroma.Web.ActivityPub.Visibility.local_public?(post) uuid = String.split(post.data["id"], "/") |> List.last() @@ -452,7 +452,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do user = insert(:user) {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"}) - assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post) + assert Pleroma.Web.ActivityPub.Visibility.local_public?(post) uuid = String.split(post.data["id"], "/") |> List.last() diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -1028,7 +1028,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do refute repeat_activity in activities end - test "see your own posts even when they adress actors from blocked domains" do + test "see your own posts even when they address actors from blocked domains" do user = insert(:user) domain = "dogwhistle.zone" diff --git a/test/pleroma/web/activity_pub/mrf/ensure_re_prepended_test.exs b/test/pleroma/web/activity_pub/mrf/ensure_re_prepended_test.exs @@ -24,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrependedTest do assert res["object"]["summary"] == "re: object-summary" end - test "it adds `re:` to summary object when child summary containts re-subject of parent summary " do + test "it adds `re:` to summary object when child summary contains re-subject of parent summary " do message = %{ "type" => "Create", "object" => %{ diff --git a/test/pleroma/web/activity_pub/mrf/force_mention_test.exs b/test/pleroma/web/activity_pub/mrf/force_mention_test.exs @@ -0,0 +1,73 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionTest do + use Pleroma.DataCase + require Pleroma.Constants + + alias Pleroma.Web.ActivityPub.MRF.ForceMention + + import Pleroma.Factory + + test "adds mention to a reply" do + lain = + insert(:user, ap_id: "https://lain.com/users/lain", nickname: "lain@lain.com", local: false) + + niobleoum = + insert(:user, + ap_id: "https://www.minds.com/api/activitypub/users/1198929502760083472", + nickname: "niobleoum@minds.com", + local: false + ) + + status = File.read!("test/fixtures/minds-pleroma-mentioned-post.json") |> Jason.decode!() + + status_activity = %{ + "type" => "Create", + "actor" => lain.ap_id, + "object" => status + } + + Pleroma.Web.ActivityPub.Transmogrifier.handle_incoming(status_activity) + + reply = File.read!("test/fixtures/minds-invalid-mention-post.json") |> Jason.decode!() + + reply_activity = %{ + "type" => "Create", + "actor" => niobleoum.ap_id, + "object" => reply + } + + {:ok, %{"object" => %{"tag" => tag}}} = ForceMention.filter(reply_activity) + + assert Enum.find(tag, fn %{"href" => href} -> href == lain.ap_id end) + end + + test "adds mention to a quote" do + user1 = insert(:user, ap_id: "https://misskey.io/users/83ssedkv53") + user2 = insert(:user, ap_id: "https://misskey.io/users/7rkrarq81i") + + status = File.read!("test/fixtures/tesla_mock/misskey.io_8vs6wxufd0.json") |> Jason.decode!() + + status_activity = %{ + "type" => "Create", + "actor" => user1.ap_id, + "object" => status + } + + Pleroma.Web.ActivityPub.Transmogrifier.handle_incoming(status_activity) + + quote_post = File.read!("test/fixtures/quote_post/misskey_quote_post.json") |> Jason.decode!() + + quote_activity = %{ + "type" => "Create", + "actor" => user2.ap_id, + "object" => quote_post + } + + {:ok, %{"object" => %{"tag" => tag}}} = ForceMention.filter(quote_activity) + + assert Enum.find(tag, fn %{"href" => href} -> href == user1.ap_id end) + end +end diff --git a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs @@ -60,6 +60,59 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicyTest do |> File.exists?() end + test "works with unknown extension", %{path: path} do + message = %{ + "type" => "Create", + "object" => %{ + "emoji" => [{"firedfox", "https://example.org/emoji/firedfox"}], + "actor" => "https://example.org/users/admin" + } + } + + fullpath = Path.join(path, "firedfox.png") + + Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox"} -> + %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")} + end) + + clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468) + + refute "firedfox" in installed() + refute File.exists?(path) + + assert {:ok, _message} = StealEmojiPolicy.filter(message) + + assert "firedfox" in installed() + assert File.exists?(path) + assert File.exists?(fullpath) + end + + test "rejects invalid shortcodes", %{path: path} do + message = %{ + "type" => "Create", + "object" => %{ + "emoji" => [{"fired/fox", "https://example.org/emoji/firedfox"}], + "actor" => "https://example.org/users/admin" + } + } + + fullpath = Path.join(path, "fired/fox.png") + + Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox"} -> + %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")} + end) + + clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468) + + refute "firedfox" in installed() + refute File.exists?(path) + + assert {:ok, _message} = StealEmojiPolicy.filter(message) + + refute "fired/fox" in installed() + refute File.exists?(fullpath) + end + test "reject regex shortcode", %{message: message} do refute "firedfox" in installed() diff --git a/test/pleroma/web/activity_pub/mrf_test.exs b/test/pleroma/web/activity_pub/mrf_test.exs @@ -1,10 +1,13 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRFTest do use ExUnit.Case use Pleroma.Tests.Helpers + + import ExUnit.CaptureLog + alias Pleroma.Web.ActivityPub.MRF test "subdomains_regex/1" do @@ -61,6 +64,14 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do refute MRF.subdomain_match?(regexes, "EXAMPLE.COM") refute MRF.subdomain_match?(regexes, "example.com") end + + @tag capture_log: true + test "logs sensible error on accidental wildcard" do + assert_raise Regex.CompileError, fn -> + assert capture_log(MRF.subdomains_regex(["*unsafe.tld"])) =~ + "MRF: Invalid subdomain Regex: *unsafe.tld" + end + end end describe "instance_list_from_tuples/1" do diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs @@ -93,6 +93,17 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest %{valid?: true} = ArticleNotePageValidator.cast_and_validate(note) end + test "a Note from Convergence AP Bridge validates" do + insert(:user, ap_id: "https://cc.mkdir.uk/ap/acct/hiira") + + note = + "test/fixtures/ccworld-ap-bridge_note.json" + |> File.read!() + |> Jason.decode!() + + %{valid?: true} = ArticleNotePageValidator.cast_and_validate(note) + end + test "a note with an attachment should work", _ do insert(:user, %{ap_id: "https://owncast.localhost.localdomain/federation/user/streamer"}) diff --git a/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs @@ -164,7 +164,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidatorTest do assert attachment.mediaType == "image/jpeg" end - test "it transforms image dimentions to our internal format" do + test "it transforms image dimensions to our internal format" do attachment = %{ "type" => "Document", "name" => "Hello world", diff --git a/test/pleroma/web/activity_pub/object_validators/chat_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/chat_validation_test.exs @@ -147,6 +147,21 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatValidationTest do assert object["attachment"] end + test "validates for a basic object with content but attachment set to empty array", %{ + user: user, + recipient: recipient + } do + {:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "Hello!") + + valid_chat_message = + valid_chat_message + |> Map.put("attachment", []) + + assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, []) + + assert object == Map.drop(valid_chat_message, ["attachment"]) + end + test "does not validate if the message has no content", %{ valid_chat_message: valid_chat_message } do diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs @@ -25,6 +25,17 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do setup_all do: clear_config([:instance, :federating], true) + describe "should_federate?/1" do + test "it returns false when the inbox is nil" do + refute Publisher.should_federate?(nil, false) + refute Publisher.should_federate?(nil, true) + end + + test "it returns true when public is true" do + assert Publisher.should_federate?(false, true) + end + end + describe "gather_webfinger_links/1" do test "it returns links" do user = insert(:user) @@ -205,6 +216,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do refute called(Instances.set_reachable(inbox)) end + @tag capture_log: true test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code", Instances, [:passthrough], @@ -212,7 +224,8 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do actor = insert(:user) inbox = "http://404.site/users/nick1/inbox" - assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + assert {:discard, _} = + Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) assert called(Instances.set_unreachable(inbox)) end @@ -268,7 +281,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do describe "publish/2" do test_with_mock "doesn't publish a non-public activity to quarantined instances.", - Pleroma.Web.Federator.Publisher, + Pleroma.Web.ActivityPub.Publisher, [:passthrough], [] do Config.put([:instance, :quarantined_instances], [{"domain.com", "some reason"}]) @@ -295,7 +308,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do assert res == :ok assert not called( - Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{ + Publisher.enqueue_one(%{ inbox: "https://domain.com/users/nick1/inbox", actor_id: actor.id, id: note_activity.data["id"] @@ -304,7 +317,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do end test_with_mock "Publishes a non-public activity to non-quarantined instances.", - Pleroma.Web.Federator.Publisher, + Pleroma.Web.ActivityPub.Publisher, [:passthrough], [] do Config.put([:instance, :quarantined_instances], [{"somedomain.com", "some reason"}]) @@ -331,8 +344,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do assert res == :ok assert called( - Pleroma.Web.Federator.Publisher.enqueue_one( - Publisher, + Publisher.enqueue_one( %{ inbox: "https://domain.com/users/nick1/inbox", actor_id: actor.id, @@ -344,7 +356,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do end test_with_mock "Publishes to directly addressed actors with higher priority.", - Pleroma.Web.Federator.Publisher, + Pleroma.Web.ActivityPub.Publisher, [:passthrough], [] do note_activity = insert(:direct_note_activity) @@ -356,8 +368,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do assert res == :ok assert called( - Pleroma.Web.Federator.Publisher.enqueue_one( - Publisher, + Publisher.enqueue_one( %{ inbox: :_, actor_id: actor.id, @@ -369,7 +380,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do end test_with_mock "publishes an activity with BCC to all relevant peers.", - Pleroma.Web.Federator.Publisher, + Pleroma.Web.ActivityPub.Publisher, [:passthrough], [] do follower = @@ -393,7 +404,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do assert res == :ok assert called( - Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{ + Publisher.enqueue_one(%{ inbox: "https://domain.com/users/nick1/inbox", actor_id: actor.id, id: note_activity.data["id"] @@ -402,7 +413,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do end test_with_mock "publishes a delete activity to peers who signed fetch requests to the create acitvity/object.", - Pleroma.Web.Federator.Publisher, + Pleroma.Web.ActivityPub.Publisher, [:passthrough], [] do fetcher = @@ -443,8 +454,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do assert res == :ok assert called( - Pleroma.Web.Federator.Publisher.enqueue_one( - Publisher, + Publisher.enqueue_one( %{ inbox: "https://domain.com/users/nick1/inbox", actor_id: actor.id, @@ -455,8 +465,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do ) assert called( - Pleroma.Web.Federator.Publisher.enqueue_one( - Publisher, + Publisher.enqueue_one( %{ inbox: "https://domain2.com/users/nick1/inbox", actor_id: actor.id, diff --git a/test/pleroma/web/activity_pub/side_effects_test.exs b/test/pleroma/web/activity_pub/side_effects_test.exs @@ -17,11 +17,19 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.SideEffects + alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.CommonAPI + alias Pleroma.Web.CommonAPI.ActivityDraft import Mock import Pleroma.Factory + defp get_announces_of_object(%{data: %{"id" => id}} = _object) do + Pleroma.Activity.Queries.by_type("Announce") + |> Pleroma.Activity.Queries.by_object_id(id) + |> Pleroma.Repo.all() + end + describe "handle_after_transaction" do test "it streams out notifications and streams" do author = insert(:user, local: true) @@ -915,4 +923,85 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do assert User.get_follow_state(user, followed, nil) == nil end end + + describe "Group actors" do + setup do + poster = + insert(:user, + local: false, + nickname: "poster@example.com", + ap_id: "https://example.com/users/poster" + ) + + group = insert(:user, actor_type: "Group") + + make_create = fn mentioned_users -> + mentions = mentioned_users |> Enum.map(fn u -> "@#{u.nickname}" end) |> Enum.join(" ") + {:ok, draft} = ActivityDraft.create(poster, %{status: "#{mentions} hey"}) + + create_activity_data = + Utils.make_create_data(draft.changes |> Map.put(:published, nil), %{}) + |> put_in(["object", "id"], "https://example.com/object") + |> put_in(["id"], "https://example.com/activity") + + assert Enum.all?(mentioned_users, fn u -> u.ap_id in create_activity_data["to"] end) + + create_activity_data + end + + %{poster: poster, group: group, make_create: make_create} + end + + test "group should boost it", %{make_create: make_create, group: group} do + create_activity_data = make_create.([group]) + {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false) + + {:ok, _create_activity, _meta} = + SideEffects.handle(create_activity, + local: false, + object_data: create_activity_data["object"] + ) + + object = Object.normalize(create_activity, fetch: false) + assert [announce] = get_announces_of_object(object) + assert announce.actor == group.ap_id + end + + test "remote group should not boost it", %{make_create: make_create, group: group} do + remote_group = + insert(:user, actor_type: "Group", local: false, nickname: "remotegroup@example.com") + + create_activity_data = make_create.([group, remote_group]) + {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false) + + {:ok, _create_activity, _meta} = + SideEffects.handle(create_activity, + local: false, + object_data: create_activity_data["object"] + ) + + object = Object.normalize(create_activity, fetch: false) + assert [announce] = get_announces_of_object(object) + assert announce.actor == group.ap_id + end + + test "group should not boost it if group is blocking poster", %{ + make_create: make_create, + group: group, + poster: poster + } do + {:ok, _} = CommonAPI.block(group, poster) + create_activity_data = make_create.([group]) + {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false) + + {:ok, _create_activity, _meta} = + SideEffects.handle(create_activity, + local: false, + object_data: create_activity_data["object"] + ) + + object = Object.normalize(create_activity, fetch: false) + assert [] = get_announces_of_object(object) + end + end end diff --git a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs @@ -221,6 +221,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>" end + test "it works for incoming notices with a nil contentMap (firefish)" do + data = + File.read!("test/fixtures/mastodon-post-activity-contentmap.json") + |> Jason.decode!() + |> Map.put("contentMap", nil) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + object = Object.normalize(data["object"], fetch: false) + + assert object.data["content"] == + "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>" + end + test "it works for incoming notices with to/cc not being an array (kroeg)" do data = File.read!("test/fixtures/kroeg-post-activity.json") |> Jason.decode!() @@ -508,7 +521,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do [data: data] end - test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do + test "returns not modified object when has no inReplyTo field", %{data: data} do assert Transmogrifier.fix_in_reply_to(data) == data end diff --git a/test/pleroma/web/activity_pub/transmogrifier/undo_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/undo_handling_test.exs @@ -32,7 +32,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.UndoHandlingTest do assert activity.data["type"] == "Undo" end - test "it returns an error for incoming unlikes wihout a like activity" do + test "it returns an error for incoming unlikes without a like activity" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "leave a like pls"}) diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -132,7 +132,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert {:ok, activity} = Transmogrifier.handle_incoming(message) object = Object.normalize(activity) assert [%{"type" => "Mention"}, %{"type" => "Link"}] = object.data["tag"] - end) =~ "Error while fetching" + end) =~ "Object rejected while fetching" end test "it accepts quote posts" do @@ -410,7 +410,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert capture_log(fn -> {:error, _} = Transmogrifier.handle_incoming(data) - end) =~ "Object containment failed" + end) =~ "Object rejected while fetching" end test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do @@ -425,7 +425,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert capture_log(fn -> {:error, _} = Transmogrifier.handle_incoming(data) - end) =~ "Object containment failed" + end) =~ "Object rejected while fetching" end test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do @@ -440,7 +440,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert capture_log(fn -> {:error, _} = Transmogrifier.handle_incoming(data) - end) =~ "Object containment failed" + end) =~ "Object rejected while fetching" end end diff --git a/test/pleroma/web/activity_pub/utils_test.exs b/test/pleroma/web/activity_pub/utils_test.exs @@ -17,7 +17,7 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do require Pleroma.Constants describe "strip_report_status_data/1" do - test "does not break on issues with the reported activites" do + test "does not break on issues with the reported activities" do reporter = insert(:user) target_account = insert(:user) {:ok, activity} = CommonAPI.post(target_account, %{status: "foobar"}) @@ -153,7 +153,7 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do assert Enum.sort(cc) == expected_cc end - test "does not adress actor's follower address if the activity is not public", %{ + test "does not address actor's follower address if the activity is not public", %{ user: user, other_user: other_user, third_user: third_user @@ -622,7 +622,7 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do end describe "get_cached_emoji_reactions/1" do - test "returns the normalized data or an emtpy list" do + test "returns the normalized data or an empty list" do object = insert(:note) assert Utils.get_cached_emoji_reactions(object) == [] diff --git a/test/pleroma/web/activity_pub/visibility_test.exs b/test/pleroma/web/activity_pub/visibility_test.exs @@ -52,60 +52,60 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do } end - test "is_direct?", %{ + test "direct?", %{ public: public, private: private, direct: direct, unlisted: unlisted, list: list } do - assert Visibility.is_direct?(direct) - refute Visibility.is_direct?(public) - refute Visibility.is_direct?(private) - refute Visibility.is_direct?(unlisted) - assert Visibility.is_direct?(list) + assert Visibility.direct?(direct) + refute Visibility.direct?(public) + refute Visibility.direct?(private) + refute Visibility.direct?(unlisted) + assert Visibility.direct?(list) end - test "is_public?", %{ + test "public?", %{ public: public, private: private, direct: direct, unlisted: unlisted, list: list } do - refute Visibility.is_public?(direct) - assert Visibility.is_public?(public) - refute Visibility.is_public?(private) - assert Visibility.is_public?(unlisted) - refute Visibility.is_public?(list) + refute Visibility.public?(direct) + assert Visibility.public?(public) + refute Visibility.public?(private) + assert Visibility.public?(unlisted) + refute Visibility.public?(list) end - test "is_private?", %{ + test "private?", %{ public: public, private: private, direct: direct, unlisted: unlisted, list: list } do - refute Visibility.is_private?(direct) - refute Visibility.is_private?(public) - assert Visibility.is_private?(private) - refute Visibility.is_private?(unlisted) - refute Visibility.is_private?(list) + refute Visibility.private?(direct) + refute Visibility.private?(public) + assert Visibility.private?(private) + refute Visibility.private?(unlisted) + refute Visibility.private?(list) end - test "is_list?", %{ + test "list?", %{ public: public, private: private, direct: direct, unlisted: unlisted, list: list } do - refute Visibility.is_list?(direct) - refute Visibility.is_list?(public) - refute Visibility.is_list?(private) - refute Visibility.is_list?(unlisted) - assert Visibility.is_list?(list) + refute Visibility.list?(direct) + refute Visibility.list?(public) + refute Visibility.list?(private) + refute Visibility.list?(unlisted) + assert Visibility.list?(list) end test "visible_for_user? Activity", %{ @@ -227,7 +227,7 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do } do Repo.delete(user) Pleroma.User.invalidate_cache(user) - refute Visibility.is_private?(direct) + refute Visibility.private?(direct) end test "get_visibility", %{ diff --git a/test/pleroma/web/admin_api/controllers/config_controller_test.exs b/test/pleroma/web/admin_api/controllers/config_controller_test.exs @@ -873,7 +873,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do %{ "tuple" => [ ":_", - "Phoenix.Endpoint.Cowboy2Handler", + "Plug.Cowboy.Handler", %{"tuple" => ["Pleroma.Web.Endpoint", []]} ] } @@ -937,7 +937,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do %{ "tuple" => [ ":_", - "Phoenix.Endpoint.Cowboy2Handler", + "Plug.Cowboy.Handler", %{"tuple" => ["Pleroma.Web.Endpoint", []]} ] } diff --git a/test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs b/test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs @@ -163,7 +163,7 @@ defmodule Pleroma.Web.AdminAPI.OAuthAppControllerTest do assert response == "" end - test "with non existance id", %{conn: conn} do + test "with nonexistent id", %{conn: conn} do response = conn |> delete("/api/pleroma/admin/oauth_app/0") diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs @@ -27,8 +27,15 @@ defmodule Pleroma.Web.CommonAPITest do import Mox import Pleroma.Factory + require Pleroma.Activity.Queries require Pleroma.Constants + defp get_announces_of_object(%{data: %{"id" => id}} = _object) do + Pleroma.Activity.Queries.by_type("Announce") + |> Pleroma.Activity.Queries.by_object_id(id) + |> Pleroma.Repo.all() + end + setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) :ok @@ -500,7 +507,7 @@ defmodule Pleroma.Web.CommonAPITest do {:ok, convo_reply} = CommonAPI.post(user, %{status: ".", in_reply_to_conversation_id: participation.id}) - assert Visibility.is_direct?(convo_reply) + assert Visibility.direct?(convo_reply) assert activity.data["context"] == convo_reply.data["context"] end @@ -918,7 +925,7 @@ defmodule Pleroma.Web.CommonAPITest do {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"}) {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user) - assert Visibility.is_public?(announce_activity) + assert Visibility.public?(announce_activity) end test "can't repeat a repeat" do @@ -940,7 +947,7 @@ defmodule Pleroma.Web.CommonAPITest do {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user, %{visibility: "private"}) - assert Visibility.is_private?(announce_activity) + assert Visibility.private?(announce_activity) refute Visibility.visible_for_user?(announce_activity, nil) end @@ -953,7 +960,7 @@ defmodule Pleroma.Web.CommonAPITest do {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, author) - assert Visibility.is_private?(announce_activity) + assert Visibility.private?(announce_activity) refute Visibility.visible_for_user?(announce_activity, nil) assert Visibility.visible_for_user?(activity, follower) @@ -1625,7 +1632,7 @@ defmodule Pleroma.Web.CommonAPITest do with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"}) - assert Visibility.is_local_public?(activity) + assert Visibility.local_public?(activity) assert_not_called(Pleroma.Web.Federator.publish(activity)) end end @@ -1640,7 +1647,7 @@ defmodule Pleroma.Web.CommonAPITest do assert {:ok, %Activity{data: %{"deleted_activity_id" => ^activity_id}} = activity} = CommonAPI.delete(activity_id, user) - assert Visibility.is_local_public?(activity) + assert Visibility.local_public?(activity) assert_not_called(Pleroma.Web.Federator.publish(activity)) end end @@ -1656,7 +1663,7 @@ defmodule Pleroma.Web.CommonAPITest do assert {:ok, %Activity{data: %{"type" => "Announce"}} = activity} = CommonAPI.repeat(activity_id, user) - assert Visibility.is_local_public?(activity) + assert Visibility.local_public?(activity) refute called(Pleroma.Web.Federator.publish(activity)) end end @@ -1674,7 +1681,7 @@ defmodule Pleroma.Web.CommonAPITest do assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} = CommonAPI.unrepeat(activity_id, user) - assert Visibility.is_local_public?(activity) + assert Visibility.local_public?(activity) refute called(Pleroma.Web.Federator.publish(activity)) end end @@ -1689,7 +1696,7 @@ defmodule Pleroma.Web.CommonAPITest do assert {:ok, %Activity{data: %{"type" => "Like"}} = activity} = CommonAPI.favorite(user, activity.id) - assert Visibility.is_local_public?(activity) + assert Visibility.local_public?(activity) refute called(Pleroma.Web.Federator.publish(activity)) end end @@ -1704,7 +1711,7 @@ defmodule Pleroma.Web.CommonAPITest do with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do assert {:ok, activity} = CommonAPI.unfavorite(activity.id, user) - assert Visibility.is_local_public?(activity) + assert Visibility.local_public?(activity) refute called(Pleroma.Web.Federator.publish(activity)) end end @@ -1718,7 +1725,7 @@ defmodule Pleroma.Web.CommonAPITest do assert {:ok, %Activity{data: %{"type" => "EmojiReact"}} = activity} = CommonAPI.react_with_emoji(activity.id, user, "👍") - assert Visibility.is_local_public?(activity) + assert Visibility.local_public?(activity) refute called(Pleroma.Web.Federator.publish(activity)) end end @@ -1734,7 +1741,7 @@ defmodule Pleroma.Web.CommonAPITest do assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} = CommonAPI.unreact_with_emoji(activity.id, user, "👍") - assert Visibility.is_local_public?(activity) + assert Visibility.local_public?(activity) refute called(Pleroma.Web.Federator.publish(activity)) end end @@ -1863,4 +1870,54 @@ defmodule Pleroma.Web.CommonAPITest do assert Map.has_key?(updated_object.data, "updated") end end + + describe "Group actors" do + setup do + poster = insert(:user) + group = insert(:user, actor_type: "Group") + other_group = insert(:user, actor_type: "Group") + %{poster: poster, group: group, other_group: other_group} + end + + test "it boosts public posts", %{poster: poster, group: group} do + {:ok, post} = CommonAPI.post(poster, %{status: "hey @#{group.nickname}"}) + + announces = get_announces_of_object(post.object) + assert [_] = announces + end + + test "it does not boost private posts", %{poster: poster, group: group} do + {:ok, private_post} = + CommonAPI.post(poster, %{status: "hey @#{group.nickname}", visibility: "private"}) + + assert [] = get_announces_of_object(private_post.object) + end + + test "remote groups do not boost any posts", %{poster: poster} do + remote_group = + insert(:user, actor_type: "Group", local: false, nickname: "remote@example.com") + + {:ok, post} = CommonAPI.post(poster, %{status: "hey @#{User.full_nickname(remote_group)}"}) + assert remote_group.ap_id in post.data["to"] + + announces = get_announces_of_object(post.object) + assert [] = announces + end + + test "multiple groups mentioned", %{poster: poster, group: group, other_group: other_group} do + {:ok, post} = + CommonAPI.post(poster, %{status: "hey @#{group.nickname} @#{other_group.nickname}"}) + + announces = get_announces_of_object(post.object) + assert [_, _] = announces + end + + test "it does not boost if group is blocking poster", %{poster: poster, group: group} do + {:ok, _} = CommonAPI.block(group, poster) + {:ok, post} = CommonAPI.post(poster, %{status: "hey @#{group.nickname}"}) + + announces = get_announces_of_object(post.object) + assert [] = announces + end + end end diff --git a/test/pleroma/web/federator_test.exs b/test/pleroma/web/federator_test.exs @@ -40,6 +40,44 @@ defmodule Pleroma.Web.FederatorTest do %{activity: activity, relay_mock: relay_mock} end + test "to shared inbox when multiple actors from same instance are recipients" do + user = insert(:user) + + shared_inbox = "https://domain.com/inbox" + + follower_one = + insert(:user, %{ + local: false, + nickname: "nick1@domain.com", + ap_id: "https://domain.com/users/nick1", + inbox: "https://domain.com/users/nick1/inbox", + shared_inbox: shared_inbox + }) + + follower_two = + insert(:user, %{ + local: false, + nickname: "nick2@domain.com", + ap_id: "https://domain.com/users/nick2", + inbox: "https://domain.com/users/nick2/inbox", + shared_inbox: shared_inbox + }) + + {:ok, _, _} = Pleroma.User.follow(follower_one, user) + {:ok, _, _} = Pleroma.User.follow(follower_two, user) + + {:ok, _activity} = CommonAPI.post(user, %{status: "Happy Friday everyone!"}) + + ObanHelpers.perform(all_enqueued(worker: PublisherWorker)) + + inboxes = + all_enqueued(worker: PublisherWorker) + |> Enum.filter(&(get_in(&1, [Access.key(:args), Access.key("op")]) == "publish_one")) + |> Enum.map(&get_in(&1, [Access.key(:args), Access.key("params"), Access.key("inbox")])) + + assert [shared_inbox] == inboxes + end + test "with relays active, it publishes to the relay", %{ activity: activity, relay_mock: relay_mock diff --git a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs @@ -1360,7 +1360,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do 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 + test "returns error when user already registered", %{conn: conn, valid_params: valid_params} do _user = insert(:user, email: "lain@example.org") app_token = insert(:oauth_token, user: nil) @@ -1495,7 +1495,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do |> Plug.Conn.put_req_header("authorization", "Bearer " <> token) |> put_req_header("content-type", "multipart/form-data") |> post("/api/v1/accounts", %{ - nickname: "nickanme", + nickname: "nickname", agreement: true, email: "email@example.com", fullname: "Lain", @@ -1781,7 +1781,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do assert %{language: "ru_RU"} = Pleroma.User.get_by_nickname("foo") end - test "createing an account without language parameter should fallback to cookie/header language", + test "creating an account without language parameter should fallback to cookie/header language", %{conn: conn} do params = %{ username: "foo2", diff --git a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs @@ -109,6 +109,18 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do |> json_response_and_validate_schema(200) end + test "get instance contact information", %{conn: conn} do + user = insert(:user, %{local: true}) + + clear_config([:instance, :contact_username], user.nickname) + + conn = get(conn, "/api/v1/instance") + + assert result = json_response_and_validate_schema(conn, 200) + + assert result["contact_account"]["id"] == user.id + end + test "get instance information v2", %{conn: conn} do clear_config([:auth, :oauth_consumer_strategies], []) diff --git a/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs @@ -42,6 +42,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do end end + @tag :skip_darwin test "search", %{conn: conn} do user = insert(:user) user_two = insert(:user, %{nickname: "shp@shitposter.club"}) diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -12,6 +12,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do alias Pleroma.Object alias Pleroma.Repo alias Pleroma.ScheduledActivity + alias Pleroma.Tests.Helpers alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub @@ -335,13 +336,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do path -> Pleroma.Test.StaticConfig.get(path) end) - Tesla.Mock.mock(fn - %{ - method: :get, - url: "https://example.com/twitter-card" - } -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")} - + Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -2191,7 +2186,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do # Using the header for pagination works correctly [next, _] = get_resp_header(result, "link") |> hd() |> String.split(", ") - [_, max_id] = Regex.run(~r/max_id=([^&]+)/, next) + [next_url, _next_rel] = String.split(next, ";") + next_url = String.trim_trailing(next_url, ">") |> String.trim_leading("<") + + max_id = Helpers.get_query_parameter(next_url, "max_id") assert max_id == third_favorite.id diff --git a/test/pleroma/web/mastodon_api/controllers/subscription_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/subscription_controller_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do - use Pleroma.Web.ConnCase, async: true + use Pleroma.Web.ConnCase, async: false import Pleroma.Factory @@ -35,17 +35,20 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do defmacro assert_error_when_disable_push(do: yield) do quote do - vapid_details = Application.get_env(:web_push_encryption, :vapid_details, []) - Application.put_env(:web_push_encryption, :vapid_details, []) - assert %{"error" => "Web push subscription is disabled on this Pleroma instance"} == unquote(yield) - - Application.put_env(:web_push_encryption, :vapid_details, vapid_details) end end describe "when disabled" do + setup do + vapid_config = Application.get_env(:web_push_encryption, :vapid_details) + + Application.put_env(:web_push_encryption, :vapid_details, []) + + on_exit(fn -> Application.put_env(:web_push_encryption, :vapid_details, vapid_config) end) + end + test "POST returns error", %{conn: conn} do assert_error_when_disable_push do conn diff --git a/test/pleroma/web/mastodon_api/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs @@ -511,10 +511,15 @@ defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do |> json_response_and_validate_schema(200) assert account_data["fields"] == [ - %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"}, + %{ + "name" => "<a href=\"http://google.com\">foo</a>", + "value" => "bar", + "verified_at" => nil + }, %{ "name" => "link.io", - "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>) + "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>), + "verified_at" => nil } ] @@ -573,8 +578,8 @@ defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do |> json_response_and_validate_schema(200) assert account_data["fields"] == [ - %{"name" => ":firefox:", "value" => "is best 2hu"}, - %{"name" => "they wins", "value" => ":blank:"} + %{"name" => ":firefox:", "value" => "is best 2hu", "verified_at" => nil}, + %{"name" => "they wins", "value" => ":blank:", "verified_at" => nil} ] assert account_data["source"]["fields"] == [ @@ -602,10 +607,11 @@ defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do |> json_response_and_validate_schema(200) assert account["fields"] == [ - %{"name" => "foo", "value" => "bar"}, + %{"name" => "foo", "value" => "bar", "verified_at" => nil}, %{ "name" => "link", - "value" => ~S(<a href="http://cofe.io" rel="ugc">http://cofe.io</a>) + "value" => ~S(<a href="http://cofe.io" rel="ugc">http://cofe.io</a>), + "verified_at" => nil } ] @@ -627,7 +633,7 @@ defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do |> json_response_and_validate_schema(200) assert account["fields"] == [ - %{"name" => "foo", "value" => ""} + %{"name" => "foo", "value" => "", "verified_at" => nil} ] end @@ -732,4 +738,20 @@ defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do assert account["source"]["pleroma"]["actor_type"] == "Person" end end + + describe "Mark account as group" do + setup do: oauth_access(["write:accounts"]) + setup :request_content_type + + test "changing actor_type to Group makes account a Group and enables bot indicator for backward compatibility", + %{conn: conn} do + account = + conn + |> patch("/api/v1/accounts/update_credentials", %{actor_type: "Group"}) + |> json_response_and_validate_schema(200) + + assert account["bot"] + assert account["source"]["pleroma"]["actor_type"] == "Group" + end + end end diff --git a/test/pleroma/web/mastodon_api/views/poll_view_test.exs b/test/pleroma/web/mastodon_api/views/poll_view_test.exs @@ -43,7 +43,8 @@ defmodule Pleroma.Web.MastodonAPI.PollViewTest do %{title: "why are you even asking?", votes_count: 0} ], votes_count: 0, - voters_count: 0 + voters_count: 0, + pleroma: %{non_anonymous: false} } result = PollView.render("show.json", %{object: object}) @@ -165,4 +166,11 @@ defmodule Pleroma.Web.MastodonAPI.PollViewTest do ] } = PollView.render("show.json", %{object: object}) end + + test "that poll is non anonymous" do + object = Object.normalize("https://friends.grishka.me/posts/54642", fetch: true) + result = PollView.render("show.json", %{object: object}) + + assert result[:pleroma][:non_anonymous] == true + end end diff --git a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs @@ -182,7 +182,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do media_proxy_url: media_proxy_url } do Tesla.Mock.mock(fn - %{method: "HEAD", url: ^media_proxy_url} -> + %{method: :head, url: ^media_proxy_url} -> %Tesla.Env{status: 500, body: ""} end) @@ -197,7 +197,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do media_proxy_url: media_proxy_url } do Tesla.Mock.mock(fn - %{method: "HEAD", url: ^media_proxy_url} -> + %{method: :head, url: ^media_proxy_url} -> %Tesla.Env{status: 200, body: "", headers: [{"content-type", "application/pdf"}]} end) @@ -217,7 +217,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do clear_config([:media_preview_proxy, :min_content_length], 1_000_000_000) Tesla.Mock.mock(fn - %{method: "HEAD", url: ^media_proxy_url} -> + %{method: :head, url: ^media_proxy_url} -> %Tesla.Env{ status: 200, body: "", @@ -242,7 +242,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do media_proxy_url: media_proxy_url } do Tesla.Mock.mock(fn - %{method: "HEAD", url: ^media_proxy_url} -> + %{method: :head, url: ^media_proxy_url} -> %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/gif"}]} end) @@ -260,7 +260,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do media_proxy_url: media_proxy_url } do Tesla.Mock.mock(fn - %{method: "HEAD", url: ^media_proxy_url} -> + %{method: :head, url: ^media_proxy_url} -> %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]} end) @@ -280,7 +280,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do clear_config([:media_preview_proxy, :min_content_length], 100_000) Tesla.Mock.mock(fn - %{method: "HEAD", url: ^media_proxy_url} -> + %{method: :head, url: ^media_proxy_url} -> %Tesla.Env{ status: 200, body: "", @@ -302,7 +302,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do assert_dependencies_installed() Tesla.Mock.mock(fn - %{method: "HEAD", url: ^media_proxy_url} -> + %{method: :head, url: ^media_proxy_url} -> %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/png"}]} %{method: :get, url: ^media_proxy_url} -> @@ -324,7 +324,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do assert_dependencies_installed() Tesla.Mock.mock(fn - %{method: "HEAD", url: ^media_proxy_url} -> + %{method: :head, url: ^media_proxy_url} -> %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]} %{method: :get, url: ^media_proxy_url} -> @@ -344,7 +344,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do media_proxy_url: media_proxy_url } do Tesla.Mock.mock(fn - %{method: "HEAD", url: ^media_proxy_url} -> + %{method: :head, url: ^media_proxy_url} -> %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]} %{method: :get, url: ^media_proxy_url} -> diff --git a/test/pleroma/web/o_auth/mfa_controller_test.exs b/test/pleroma/web/o_auth/mfa_controller_test.exs @@ -214,7 +214,7 @@ defmodule Pleroma.Web.OAuth.MFAControllerTest do assert response == %{"error" => "Invalid code"} end - test "returns error when client credentails is wrong ", %{conn: conn, user: user} do + test "returns error when client credentials is wrong ", %{conn: conn, user: user} do otp_token = TOTP.generate_token(user.multi_factor_authentication_settings.totp.secret) mfa_token = insert(:mfa_token, user: user) diff --git a/test/pleroma/web/o_auth/token/utils_test.exs b/test/pleroma/web/o_auth/token/utils_test.exs @@ -13,7 +13,7 @@ defmodule Pleroma.Web.OAuth.Token.UtilsTest do Utils.fetch_app(%Plug.Conn{params: %{"client_id" => 1, "client_secret" => "x"}}) end - test "returns App by params credentails" do + test "returns App by params credentials" do app = insert(:oauth_app) assert {:ok, load_app} = @@ -24,7 +24,7 @@ defmodule Pleroma.Web.OAuth.Token.UtilsTest do assert load_app == app end - test "returns App by header credentails" do + test "returns App by header credentials" do app = insert(:oauth_app) header = "Basic " <> Base.encode64("#{app.client_id}:#{app.client_secret}") diff --git a/test/pleroma/web/pleroma_api/controllers/chat_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/chat_controller_test.exs @@ -7,6 +7,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do alias Pleroma.Chat alias Pleroma.Chat.MessageReference alias Pleroma.Object + alias Pleroma.Tests.Helpers alias Pleroma.UnstubbedConfigMock, as: ConfigMock alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub @@ -212,36 +213,63 @@ defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do result = json_response_and_validate_schema(response, 200) [next, prev] = get_resp_header(response, "link") |> hd() |> String.split(", ") - api_endpoint = "/api/v1/pleroma/chats/" + api_endpoint = Pleroma.Web.Endpoint.url() <> "/api/v1/pleroma/chats/" + + [next_url, next_rel] = String.split(next, ";") + next_url = String.trim_trailing(next_url, ">") |> String.trim_leading("<") + + next_url_sorted = Helpers.uri_query_sort(next_url) assert String.match?( - next, - ~r(#{api_endpoint}.*/messages\?limit=\d+&max_id=.*; rel=\"next\"$) + next_url_sorted, + ~r(#{api_endpoint}.*/messages\?limit=\d+&max_id=.*&offset=\d+$) ) + assert next_rel =~ "next" + + [prev_url, prev_rel] = String.split(prev, ";") + prev_url = String.trim_trailing(prev_url, ">") |> String.trim_leading("<") + + prev_url_sorted = Helpers.uri_query_sort(prev_url) + assert String.match?( - prev, - ~r(#{api_endpoint}.*/messages\?limit=\d+&min_id=.*; rel=\"prev\"$) + prev_url_sorted, + ~r(#{api_endpoint}.*/messages\?limit=\d+&min_id=.*&offset=\d+$) ) + assert prev_rel =~ "prev" + assert length(result) == 20 - response = - get(conn, "/api/v1/pleroma/chats/#{chat.id}/messages?max_id=#{List.last(result)["id"]}") + response = get(conn, "#{api_endpoint}#{chat.id}/messages?max_id=#{List.last(result)["id"]}") result = json_response_and_validate_schema(response, 200) [next, prev] = get_resp_header(response, "link") |> hd() |> String.split(", ") + [next_url, next_rel] = String.split(next, ";") + next_url = String.trim_trailing(next_url, ">") |> String.trim_leading("<") + + next_url_sorted = Helpers.uri_query_sort(next_url) + assert String.match?( - next, - ~r(#{api_endpoint}.*/messages\?limit=\d+&max_id=.*; rel=\"next\"$) + next_url_sorted, + ~r(#{api_endpoint}.*/messages\?limit=\d+&max_id=.*&offset=\d+$) ) + assert next_rel =~ "next" + + [prev_url, prev_rel] = String.split(prev, ";") + prev_url = String.trim_trailing(prev_url, ">") |> String.trim_leading("<") + + prev_url_sorted = Helpers.uri_query_sort(prev_url) + assert String.match?( - prev, - ~r(#{api_endpoint}.*/messages\?limit=\d+&max_id=.*&min_id=.*; rel=\"prev\"$) + prev_url_sorted, + ~r(#{api_endpoint}.*/messages\?limit=\d+&max_id=.*&min_id=.*&offset=\d+$) ) + assert prev_rel =~ "prev" + assert length(result) == 10 end diff --git a/test/pleroma/web/pleroma_api/controllers/emoji_pack_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_pack_controller_test.exs @@ -116,7 +116,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} -> json(%{metadata: %{features: ["shareable_emoji_packs"]}}) - %{method: :get, url: "https://example.com/api/pleroma/emoji/packs?page=2&page_size=1"} -> + %{method: :get, url: "https://example.com/api/v1/pleroma/emoji/packs?page=2&page_size=1"} -> json(resp) end) @@ -199,7 +199,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do %{ method: :get, - url: "https://example.com/api/pleroma/emoji/pack?name=test_pack" + url: "https://example.com/api/v1/pleroma/emoji/pack?name=test_pack&page_size=" <> _n } -> conn |> get("/api/pleroma/emoji/pack?name=test_pack") @@ -208,7 +208,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do %{ method: :get, - url: "https://example.com/api/pleroma/emoji/packs/archive?name=test_pack" + url: "https://example.com/api/v1/pleroma/emoji/packs/archive?name=test_pack" } -> conn |> get("/api/pleroma/emoji/packs/archive?name=test_pack") @@ -217,7 +217,9 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do %{ method: :get, - url: "https://example.com/api/pleroma/emoji/pack?name=test_pack_nonshared" + url: + "https://example.com/api/v1/pleroma/emoji/pack?name=test_pack_nonshared&page_size=" <> + _n } -> conn |> get("/api/pleroma/emoji/pack?name=test_pack_nonshared") @@ -305,14 +307,14 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do %{ method: :get, - url: "https://example.com/api/pleroma/emoji/pack?name=pack_bad_sha" + url: "https://example.com/api/v1/pleroma/emoji/pack?name=pack_bad_sha&page_size=" <> _n } -> {:ok, pack} = Pleroma.Emoji.Pack.load_pack("pack_bad_sha") %Tesla.Env{status: 200, body: Jason.encode!(pack)} %{ method: :get, - url: "https://example.com/api/pleroma/emoji/packs/archive?name=pack_bad_sha" + url: "https://example.com/api/v1/pleroma/emoji/packs/archive?name=pack_bad_sha" } -> %Tesla.Env{ status: 200, @@ -342,7 +344,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do %{ method: :get, - url: "https://example.com/api/pleroma/emoji/pack?name=test_pack" + url: "https://example.com/api/v1/pleroma/emoji/pack?name=test_pack&page_size=" <> _n } -> {:ok, pack} = Pleroma.Emoji.Pack.load_pack("test_pack") %Tesla.Env{status: 200, body: Jason.encode!(pack)} diff --git a/test/pleroma/web/pleroma_api/controllers/instances_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/instances_controller_test.exs @@ -16,7 +16,7 @@ defmodule Pleroma.Web.PleromaApi.InstancesControllerTest do {:ok, %Pleroma.Instances.Instance{unreachable_since: constant_unreachable}} = Instances.set_consistently_unreachable(constant) - _eventual_unrechable = Instances.set_unreachable(eventual) + _eventual_unreachable = Instances.set_unreachable(eventual) %{constant_unreachable: constant_unreachable, constant: constant} end diff --git a/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs b/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs @@ -49,6 +49,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatMessageReferenceViewTest do :chat_message_id_idempotency_key_cache, ^id -> {:ok, "123"} cache, key -> NullCache.get(cache, key) end) + |> stub(:fetch, fn :rich_media_cache, _, _ -> {:ok, {:ok, %{}}} end) chat_message = MessageReferenceView.render("show.json", chat_message_reference: cm_ref) diff --git a/test/pleroma/web/rich_media/helpers_test.exs b/test/pleroma/web/rich_media/helpers_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RichMedia.HelpersTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false alias Pleroma.StaticStubbedConfigMock, as: ConfigMock alias Pleroma.Web.CommonAPI @@ -14,7 +14,7 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do import Tesla.Mock setup do - mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) ConfigMock |> stub(:get, fn @@ -83,8 +83,34 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end - # This does not seem to work. The urls are being fetched. - @tag skip: true + test "recrawls URLs on updates" do + original_url = "https://google.com/" + updated_url = "https://yahoo.com/" + + Pleroma.StaticStubbedConfigMock + |> stub(:get, fn + [:rich_media, :enabled] -> true + path -> Pleroma.Test.StaticConfig.get(path) + end) + + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{status: "I like this site #{original_url}"}) + + assert match?( + %{page_url: ^original_url, rich_media: _}, + Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) + ) + + {:ok, _} = CommonAPI.update(user, activity, %{status: "I like this site #{updated_url}"}) + + activity = Pleroma.Activity.get_by_id(activity.id) + + assert match?( + %{page_url: ^updated_url, rich_media: _}, + Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) + ) + end + test "refuses to crawl URLs of private network from posts" do user = insert(:user) @@ -102,10 +128,10 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do path -> Pleroma.Test.StaticConfig.get(path) end) - assert %{} = Helpers.fetch_data_for_activity(activity) - assert %{} = Helpers.fetch_data_for_activity(activity2) - assert %{} = Helpers.fetch_data_for_activity(activity3) - assert %{} = Helpers.fetch_data_for_activity(activity4) - assert %{} = Helpers.fetch_data_for_activity(activity5) + assert %{} == Helpers.fetch_data_for_activity(activity) + assert %{} == Helpers.fetch_data_for_activity(activity2) + assert %{} == Helpers.fetch_data_for_activity(activity3) + assert %{} == Helpers.fetch_data_for_activity(activity4) + assert %{} == Helpers.fetch_data_for_activity(activity5) end end diff --git a/test/pleroma/web/rich_media/parser/ttl/aws_signed_url_test.exs b/test/pleroma/web/rich_media/parser/ttl/aws_signed_url_test.exs @@ -22,7 +22,7 @@ defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrlTest do expire_time = Timex.parse!(timestamp, "{ISO:Basic:Z}") |> Timex.to_unix() |> Kernel.+(valid_till) - assert {:ok, expire_time} == Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl.ttl(metadata, url) + assert expire_time == Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl.ttl(metadata, url) end test "s3 signed url is parsed and correct ttl is set for rich media" do diff --git a/test/pleroma/web/rich_media/parser_test.exs b/test/pleroma/web/rich_media/parser_test.exs @@ -3,95 +3,26 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RichMedia.ParserTest do - use ExUnit.Case, async: true + use Pleroma.DataCase, async: false alias Pleroma.Web.RichMedia.Parser - setup do - Tesla.Mock.mock(fn - %{ - method: :get, - url: "http://example.com/ogp" - } -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")} - - %{ - method: :get, - url: "http://example.com/non-ogp" - } -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/non_ogp_embed.html")} - - %{ - method: :get, - url: "http://example.com/ogp-missing-title" - } -> - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/rich_media/ogp-missing-title.html") - } - - %{ - method: :get, - url: "http://example.com/twitter-card" - } -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")} - - %{ - method: :get, - url: "http://example.com/oembed" - } -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/oembed.html")} - - %{ - method: :get, - url: "http://example.com/oembed.json" - } -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/oembed.json")} - - %{method: :get, url: "http://example.com/empty"} -> - %Tesla.Env{status: 200, body: "hello"} + import Tesla.Mock - %{method: :get, url: "http://example.com/malformed"} -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/malformed-data.html")} - - %{method: :get, url: "http://example.com/error"} -> - {:error, :overload} - - %{ - method: :head, - url: "http://example.com/huge-page" - } -> - %Tesla.Env{ - status: 200, - headers: [{"content-length", "2000001"}, {"content-type", "text/html"}] - } - - %{ - method: :head, - url: "http://example.com/pdf-file" - } -> - %Tesla.Env{ - status: 200, - headers: [{"content-length", "1000000"}, {"content-type", "application/pdf"}] - } - - %{method: :head} -> - %Tesla.Env{status: 404, body: "", headers: []} - end) - - :ok + setup do + mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) end test "returns error when no metadata present" do - assert {:error, _} = Parser.parse("http://example.com/empty") + assert {:error, _} = Parser.parse("https://example.com/empty") end test "doesn't just add a title" do - assert {:error, {:invalid_metadata, _}} = Parser.parse("http://example.com/non-ogp") + assert {:error, {:invalid_metadata, _}} = Parser.parse("https://example.com/non-ogp") end test "parses ogp" do - assert Parser.parse("http://example.com/ogp") == + assert Parser.parse("https://example.com/ogp") == {:ok, %{ "image" => "http://ia.media-imdb.com/images/rock.jpg", @@ -99,12 +30,12 @@ defmodule Pleroma.Web.RichMedia.ParserTest do "description" => "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.", "type" => "video.movie", - "url" => "http://example.com/ogp" + "url" => "https://example.com/ogp" }} end test "falls back to <title> when ogp:title is missing" do - assert Parser.parse("http://example.com/ogp-missing-title") == + assert Parser.parse("https://example.com/ogp-missing-title") == {:ok, %{ "image" => "http://ia.media-imdb.com/images/rock.jpg", @@ -112,12 +43,12 @@ defmodule Pleroma.Web.RichMedia.ParserTest do "description" => "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.", "type" => "video.movie", - "url" => "http://example.com/ogp-missing-title" + "url" => "https://example.com/ogp-missing-title" }} end test "parses twitter card" do - assert Parser.parse("http://example.com/twitter-card") == + assert Parser.parse("https://example.com/twitter-card") == {:ok, %{ "card" => "summary", @@ -125,12 +56,12 @@ defmodule Pleroma.Web.RichMedia.ParserTest do "image" => "https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg", "title" => "Small Island Developing States Photo Submission", "description" => "View the album on Flickr.", - "url" => "http://example.com/twitter-card" + "url" => "https://example.com/twitter-card" }} end test "parses OEmbed and filters HTML tags" do - assert Parser.parse("http://example.com/oembed") == + assert Parser.parse("https://example.com/oembed") == {:ok, %{ "author_name" => "\u202E\u202D\u202Cbees\u202C", @@ -150,7 +81,7 @@ defmodule Pleroma.Web.RichMedia.ParserTest do "thumbnail_width" => 150, "title" => "Bacon Lollys", "type" => "photo", - "url" => "http://example.com/oembed", + "url" => "https://example.com/oembed", "version" => "1.0", "web_page" => "https://www.flickr.com/photos/bees/2362225867/", "web_page_short_url" => "https://flic.kr/p/4AK2sc", @@ -159,18 +90,18 @@ defmodule Pleroma.Web.RichMedia.ParserTest do end test "rejects invalid OGP data" do - assert {:error, _} = Parser.parse("http://example.com/malformed") + assert {:error, _} = Parser.parse("https://example.com/malformed") end test "returns error if getting page was not successful" do - assert {:error, :overload} = Parser.parse("http://example.com/error") + assert {:error, :overload} = Parser.parse("https://example.com/error") end test "does a HEAD request to check if the body is too large" do - assert {:error, :body_too_large} = Parser.parse("http://example.com/huge-page") + assert {:error, :body_too_large} = Parser.parse("https://example.com/huge-page") end test "does a HEAD request to check if the body is html" do - assert {:error, {:content_type, _}} = Parser.parse("http://example.com/pdf-file") + assert {:error, {:content_type, _}} = Parser.parse("https://example.com/pdf-file") end end diff --git a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs @@ -137,7 +137,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do |> html_response(200) assert response =~ "Error fetching user" - end) =~ "Object has been deleted" + end) =~ ":not_found" end end diff --git a/test/pleroma/web/web_finger/web_finger_controller_test.exs b/test/pleroma/web/web_finger/web_finger_controller_test.exs @@ -23,8 +23,15 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do assert response.status == 200 - assert response.resp_body == - ~s(<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" template="#{Pleroma.Web.Endpoint.url()}/.well-known/webfinger?resource={uri}" type="application/xrd+xml" /></XRD>) + response_xml = + response.resp_body + |> Floki.parse_document!(html_parser: Floki.HTMLParser.Mochiweb, attributes_as_maps: true) + + expected_xml = + ~s(<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" template="#{Pleroma.Web.Endpoint.url()}/.well-known/webfinger?resource={uri}" type="application/xrd+xml" /></XRD>) + |> Floki.parse_document!(html_parser: Floki.HTMLParser.Mochiweb, attributes_as_maps: true) + + assert match?(^response_xml, expected_xml) end test "Webfinger JRD" do @@ -48,12 +55,7 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do ] end - test "reach user on tld, while pleroma is runned on subdomain" do - Pleroma.Web.Endpoint.config_change( - [{Pleroma.Web.Endpoint, url: [host: "sub.example.com"]}], - [] - ) - + test "reach user on tld, while pleroma is running on subdomain" do clear_config([Pleroma.Web.Endpoint, :url, :host], "sub.example.com") clear_config([Pleroma.Web.WebFinger, :domain], "example.com") @@ -68,13 +70,6 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do assert response["subject"] == "acct:#{user.nickname}@example.com" assert response["aliases"] == ["https://sub.example.com/users/#{user.nickname}"] - - on_exit(fn -> - Pleroma.Web.Endpoint.config_change( - [{Pleroma.Web.Endpoint, url: [host: "localhost"]}], - [] - ) - end) end test "it returns 404 when user isn't found (JSON)" do diff --git a/test/pleroma/workers/remote_fetcher_worker_test.exs b/test/pleroma/workers/remote_fetcher_worker_test.exs @@ -0,0 +1,69 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.RemoteFetcherWorkerTest do + use Pleroma.DataCase + use Oban.Testing, repo: Pleroma.Repo + + alias Pleroma.Workers.RemoteFetcherWorker + + @deleted_object_one "https://deleted-404.example.com/" + @deleted_object_two "https://deleted-410.example.com/" + @unauthorized_object "https://unauthorized.example.com/" + @depth_object "https://depth.example.com/" + + describe "RemoteFetcherWorker" do + setup do + Tesla.Mock.mock(fn + %{method: :get, url: @deleted_object_one} -> + %Tesla.Env{ + status: 404 + } + + %{method: :get, url: @deleted_object_two} -> + %Tesla.Env{ + status: 410 + } + + %{method: :get, url: @unauthorized_object} -> + %Tesla.Env{ + status: 403 + } + + %{method: :get, url: @depth_object} -> + %Tesla.Env{ + status: 200 + } + end) + end + + test "does not requeue a deleted object" do + assert {:discard, _} = + RemoteFetcherWorker.perform(%Oban.Job{ + args: %{"op" => "fetch_remote", "id" => @deleted_object_one} + }) + + assert {:discard, _} = + RemoteFetcherWorker.perform(%Oban.Job{ + args: %{"op" => "fetch_remote", "id" => @deleted_object_two} + }) + end + + test "does not requeue an unauthorized object" do + assert {:discard, _} = + RemoteFetcherWorker.perform(%Oban.Job{ + args: %{"op" => "fetch_remote", "id" => @unauthorized_object} + }) + end + + test "does not requeue an object that exceeded depth" do + clear_config([:instance, :federation_incoming_replies_max_depth], 0) + + assert {:discard, _} = + RemoteFetcherWorker.perform(%Oban.Job{ + args: %{"op" => "fetch_remote", "id" => @depth_object, "depth" => 1} + }) + end + end +end diff --git a/test/support/cachex_proxy.ex b/test/support/cachex_proxy.ex @@ -27,9 +27,15 @@ defmodule Pleroma.CachexProxy do defdelegate fetch!(cache, key, func), to: Cachex @impl true + defdelegate fetch(cache, key, func), to: Cachex + + @impl true defdelegate expire_at(cache, str, num), to: Cachex @impl true + defdelegate expire(cache, str, num), to: Cachex + + @impl true defdelegate exists?(cache, key), to: Cachex @impl true diff --git a/test/support/helpers.ex b/test/support/helpers.ex @@ -10,6 +10,39 @@ defmodule Pleroma.Tests.Helpers do require Logger + @doc "Accepts two URLs/URIs and sorts the query parameters before comparing" + def uri_equal?(a, b) do + a_sorted = uri_query_sort(a) + b_sorted = uri_query_sort(b) + + match?(^a_sorted, b_sorted) + end + + @doc "Accepts a URL/URI and sorts the query parameters" + def uri_query_sort(uri) do + parsed = URI.parse(uri) + + sorted_query = + String.split(parsed.query, "&") + |> Enum.sort() + |> Enum.join("&") + + parsed + |> Map.put(:query, sorted_query) + |> URI.to_string() + end + + @doc "Returns the value of the specified query parameter for the provided URL" + def get_query_parameter(url, param) do + url + |> URI.parse() + |> Map.get(:query) + |> URI.query_decoder() + |> Enum.to_list() + |> Enum.into(%{}, fn {x, y} -> {x, y} end) + |> Map.get(param) + end + defmacro clear_config(config_path) do quote do clear_config(unquote(config_path)) do diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex @@ -178,7 +178,7 @@ defmodule HttpRequestMock do end def get( - "https://social.heldscal.la/.well-known/webfinger?resource=nonexistant@social.heldscal.la", + "https://social.heldscal.la/.well-known/webfinger?resource=nonexistent@social.heldscal.la", _, _, [{"accept", "application/xrd+xml,application/jrd+json"}] @@ -186,7 +186,7 @@ defmodule HttpRequestMock do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/nonexistant@social.heldscal.la.xml") + body: File.read!("test/fixtures/tesla_mock/nonexistent@social.heldscal.la.xml") }} end @@ -1059,7 +1059,7 @@ defmodule HttpRequestMock do }} end - def get("http://example.com/malformed", _, _, _) do + def get("https://example.com/malformed", _, _, _) do {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/malformed-data.html")}} end @@ -1464,6 +1464,63 @@ defmodule HttpRequestMock do }} end + def get("https://google.com/", _, _, _) do + {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/google.html")}} + end + + def get("https://yahoo.com/", _, _, _) do + {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/yahoo.html")}} + end + + def get("https://example.com/error", _, _, _), do: {:error, :overload} + + def get("https://example.com/ogp-missing-title", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/rich_media/ogp-missing-title.html") + }} + end + + def get("https://example.com/oembed", _, _, _) do + {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/oembed.html")}} + end + + def get("https://example.com/oembed.json", _, _, _) do + {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/oembed.json")}} + end + + def get("https://example.com/twitter-card", _, _, _) do + {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")}} + end + + def get("https://example.com/non-ogp", _, _, _) do + {:ok, + %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/non_ogp_embed.html")}} + end + + def get("https://example.com/empty", _, _, _) do + {:ok, %Tesla.Env{status: 200, body: "hello"}} + end + + def get("https://friends.grishka.me/posts/54642", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/smithereen_non_anonymous_poll.json"), + headers: activitypub_object_headers() + }} + end + + def get("https://friends.grishka.me/users/1", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/smithereen_user.json"), + headers: activitypub_object_headers() + }} + end + def get(url, query, body, headers) do {:error, "Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"} @@ -1537,14 +1594,41 @@ defmodule HttpRequestMock do # Most of the rich media mocks are missing HEAD requests, so we just return 404. @rich_media_mocks [ + "https://example.com/empty", + "https://example.com/error", + "https://example.com/malformed", + "https://example.com/non-ogp", + "https://example.com/oembed", + "https://example.com/oembed.json", "https://example.com/ogp", "https://example.com/ogp-missing-data", - "https://example.com/twitter-card" + "https://example.com/ogp-missing-title", + "https://example.com/twitter-card", + "https://google.com/", + "https://pleroma.local/notice/9kCP7V", + "https://yahoo.com/" ] + def head(url, _query, _body, _headers) when url in @rich_media_mocks do {:ok, %Tesla.Env{status: 404, body: ""}} end + def head("https://example.com/pdf-file", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + headers: [{"content-length", "1000000"}, {"content-type", "application/pdf"}] + }} + end + + def head("https://example.com/huge-page", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + headers: [{"content-length", "2000001"}, {"content-type", "text/html"}] + }} + end + def head(url, query, body, headers) do {:error, "Mock response not implemented for HEAD #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"} diff --git a/test/support/null_cache.ex b/test/support/null_cache.ex @@ -29,6 +29,9 @@ defmodule Pleroma.NullCache do end @impl true + def fetch(_, key, func), do: func.(key) + + @impl true def get_and_update(_, _, func) do func.(nil) end @@ -37,6 +40,9 @@ defmodule Pleroma.NullCache do def expire_at(_, _, _), do: {:ok, true} @impl true + def expire(_, _, _), do: {:ok, true} + + @impl true def exists?(_, _), do: {:ok, false} @impl true diff --git a/test/test_helper.exs b/test/test_helper.exs @@ -6,6 +6,12 @@ Code.put_compiler_option(:warnings_as_errors, true) ExUnit.start(exclude: [:federated, :erratic]) +if match?({:unix, :darwin}, :os.type()) do + excluded = ExUnit.configuration() |> Keyword.get(:exclude, []) + excluded = excluded ++ [:skip_darwin] + ExUnit.configure(exclude: excluded) +end + Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual) Mox.defmock(Pleroma.ReverseProxy.ClientMock, for: Pleroma.ReverseProxy.Client)