From 8f8ee6130ad9143efb6d653dfd0d7e0cd9d05400 Mon Sep 17 00:00:00 2001 From: Volker Schlecht <47375452+VlkrS@users.noreply.github.com> Date: Thu, 12 Dec 2024 19:30:04 +0100 Subject: [PATCH] Allow building CLI with elixir 1.18.x Tested with elixir 1.18.0-rc0 (cherry picked from commit 5086e283bda7f69ce05219a72787c4e2a5ec6f2e) # Conflicts: # .github/workflows/check-build-system-equivalence.yaml # .github/workflows/gazelle-scheduled.yaml # .github/workflows/oci-arm64-make.yaml # .github/workflows/oci-make.yaml # .github/workflows/templates/test.template.yaml # .github/workflows/test-authnz.yaml # .github/workflows/test-make-target.yaml # .github/workflows/test-make.yaml # .github/workflows/test-management-ui-for-pr.yaml # .github/workflows/test-management-ui.yaml # .github/workflows/test-mixed-versions.yaml # .github/workflows/test-plugin-mixed.yaml # .github/workflows/test-plugin.yaml # .github/workflows/test.yaml # COMMUNITY_SUPPORT.md # MODULE.bazel # Makefile # bazel/BUILD.horus # bazel/BUILD.redbug # bazel/bzlmod/secondary_umbrella.bzl # deps/amqp10_client/BUILD.bazel # deps/amqp10_client/app.bzl # deps/amqp10_client/src/amqp10_client.erl # deps/amqp10_client/src/amqp10_client_connection.erl # deps/amqp10_client/src/amqp10_client_frame_reader.erl # deps/amqp10_client/src/amqp10_client_session.erl # deps/amqp10_client/src/amqp10_client_types.erl # deps/amqp10_client/src/amqp10_msg.erl # deps/amqp10_client/test/system_SUITE.erl # deps/amqp10_common/app.bzl # deps/amqp_client/src/amqp_network_connection.erl # deps/oauth2_client/app.bzl # deps/oauth2_client/include/oauth2_client.hrl # deps/oauth2_client/src/oauth2_client.erl # deps/oauth2_client/test/system_SUITE.erl # deps/oauth2_client/test/unit_SUITE.erl # deps/rabbit/BUILD.bazel # deps/rabbit/Makefile # deps/rabbit/app.bzl # deps/rabbit/ct.test.spec # deps/rabbit/include/rabbit_amqp.hrl # deps/rabbit/src/mc.erl # deps/rabbit/src/mc_amqp.erl # deps/rabbit/src/mc_amqpl.erl # deps/rabbit/src/mc_compat.erl # deps/rabbit/src/mc_util.erl # deps/rabbit/src/rabbit_access_control.erl # deps/rabbit/src/rabbit_amqp_management.erl # deps/rabbit/src/rabbit_amqp_reader.erl # deps/rabbit/src/rabbit_amqp_session.erl # deps/rabbit/src/rabbit_amqp_util.erl # deps/rabbit/src/rabbit_amqp_writer.erl # deps/rabbit/src/rabbit_amqqueue.erl # deps/rabbit/src/rabbit_amqqueue_process.erl # deps/rabbit/src/rabbit_backing_queue.erl # deps/rabbit/src/rabbit_binding.erl # deps/rabbit/src/rabbit_channel.erl # deps/rabbit/src/rabbit_core_ff.erl # deps/rabbit/src/rabbit_db_binding.erl # deps/rabbit/src/rabbit_db_cluster.erl # deps/rabbit/src/rabbit_db_exchange.erl # deps/rabbit/src/rabbit_depr_ff_extra.erl # deps/rabbit/src/rabbit_deprecated_features.erl # deps/rabbit/src/rabbit_exchange.erl # deps/rabbit/src/rabbit_feature_flags.erl # deps/rabbit/src/rabbit_ff_controller.erl # deps/rabbit/src/rabbit_ff_extra.erl # deps/rabbit/src/rabbit_ff_registry.erl # deps/rabbit/src/rabbit_ff_registry_factory.erl # deps/rabbit/src/rabbit_ff_registry_wrapper.erl # deps/rabbit/src/rabbit_global_counters.erl # deps/rabbit/src/rabbit_khepri.erl # deps/rabbit/src/rabbit_mnesia.erl # deps/rabbit/src/rabbit_networking.erl # deps/rabbit/src/rabbit_prelaunch_feature_flags.erl # deps/rabbit/src/rabbit_queue_type.erl # deps/rabbit/src/rabbit_quorum_queue.erl # deps/rabbit/src/rabbit_reader.erl # deps/rabbit/src/rabbit_stream_queue.erl # deps/rabbit/test/amqp_address_SUITE.erl # deps/rabbit/test/amqp_auth_SUITE.erl # deps/rabbit/test/amqp_client_SUITE.erl # deps/rabbit/test/amqp_system_SUITE.erl # deps/rabbit/test/dead_lettering_SUITE.erl # deps/rabbit/test/disconnect_detected_during_alarm_SUITE.erl # deps/rabbit/test/feature_flags_v2_SUITE.erl # deps/rabbit/test/mc_unit_SUITE.erl # deps/rabbit/test/quorum_queue_SUITE.erl # deps/rabbit/test/rabbit_db_binding_SUITE.erl # deps/rabbit/test/rabbit_db_queue_SUITE.erl # deps/rabbit/test/topic_permission_SUITE.erl # deps/rabbit_common/mk/rabbitmq-early-plugin.mk # deps/rabbit_common/src/rabbit_core_metrics.erl # deps/rabbit_common/src/rabbit_env.erl # deps/rabbit_common/src/rabbit_event.erl # deps/rabbit_common/src/rabbit_ssl_options.erl # deps/rabbit_common/test/rabbit_env_SUITE.erl # deps/rabbitmq_amqp_client/src/rabbitmq_amqp_client.erl # deps/rabbitmq_auth_backend_http/examples/rabbitmq_auth_backend_spring_boot/pom.xml # deps/rabbitmq_auth_backend_http/examples/rabbitmq_auth_backend_spring_boot_kotlin/pom.xml # deps/rabbitmq_auth_backend_http/src/rabbit_auth_backend_http.erl # deps/rabbitmq_auth_backend_ldap/src/rabbit_auth_backend_ldap.erl # deps/rabbitmq_auth_backend_oauth2/BUILD.bazel # deps/rabbitmq_auth_backend_oauth2/Makefile # deps/rabbitmq_auth_backend_oauth2/README.md # deps/rabbitmq_auth_backend_oauth2/app.bzl # deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema # deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl # deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_schema.erl # deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_scope.erl # deps/rabbitmq_auth_backend_oauth2/src/uaa_jwks.erl # deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt.erl # deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt_jwt.erl # deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets # deps/rabbitmq_auth_backend_oauth2/test/jwks_SUITE.erl # deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE.erl # deps/rabbitmq_auth_backend_oauth2/test/system_SUITE.erl # deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl # deps/rabbitmq_cli/Makefile # deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_connections_command.ex # deps/rabbitmq_cli/mix.exs # deps/rabbitmq_event_exchange/BUILD.bazel # deps/rabbitmq_event_exchange/Makefile # deps/rabbitmq_event_exchange/README.md # deps/rabbitmq_event_exchange/app.bzl # deps/rabbitmq_event_exchange/priv/schema/rabbitmq_event_exchange.schema # deps/rabbitmq_event_exchange/src/rabbit_exchange_type_event.erl # deps/rabbitmq_event_exchange/test/config_schema_SUITE_data/rabbitmq_event_exchange.snippets # deps/rabbitmq_event_exchange/test/system_SUITE.erl # deps/rabbitmq_management/.gitignore # deps/rabbitmq_management/BUILD.bazel # deps/rabbitmq_management/Makefile # deps/rabbitmq_management/app.bzl # deps/rabbitmq_management/priv/schema/rabbitmq_management.schema # deps/rabbitmq_management/priv/www/css/main.css # deps/rabbitmq_management/priv/www/js/dispatcher.js # deps/rabbitmq_management/priv/www/js/global.js # deps/rabbitmq_management/priv/www/js/main.js # deps/rabbitmq_management/priv/www/js/oidc-oauth/helper.js # deps/rabbitmq_management/priv/www/js/tmpl/connection.ejs # deps/rabbitmq_management/priv/www/js/tmpl/connections.ejs # deps/rabbitmq_management/priv/www/js/tmpl/deprecated-features.ejs # deps/rabbitmq_management/priv/www/js/tmpl/feature-flags.ejs # deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl # deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl # deps/rabbitmq_management/test/clustering_prop_SUITE.erl # deps/rabbitmq_management/test/config_schema_SUITE_data/rabbitmq_management.snippets # deps/rabbitmq_management/test/rabbit_mgmt_http_SUITE.erl # deps/rabbitmq_management/test/rabbit_mgmt_wm_auth_SUITE.erl # deps/rabbitmq_management_agent/src/rabbit_mgmt_ff.erl # deps/rabbitmq_mqtt/BUILD.bazel # deps/rabbitmq_mqtt/Makefile # deps/rabbitmq_mqtt/src/mc_mqtt.erl # deps/rabbitmq_mqtt/src/rabbit_mqtt.erl # deps/rabbitmq_mqtt/src/rabbit_mqtt_ff.erl # deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl # deps/rabbitmq_mqtt/src/rabbit_mqtt_reader.erl # deps/rabbitmq_mqtt/test/java_SUITE_data/pom.xml # deps/rabbitmq_mqtt/test/mc_mqtt_SUITE.erl # deps/rabbitmq_mqtt/test/mqtt_shared_SUITE.erl # deps/rabbitmq_prometheus/BUILD.bazel # deps/rabbitmq_prometheus/app.bzl # deps/rabbitmq_prometheus/src/collectors/prometheus_rabbitmq_global_metrics_collector.erl # deps/rabbitmq_prometheus/src/rabbit_prometheus_dispatcher.erl # deps/rabbitmq_prometheus/test/rabbit_prometheus_http_SUITE.erl # deps/rabbitmq_stream/test/protocol_interop_SUITE.erl # deps/rabbitmq_stream/test/rabbit_stream_SUITE_data/pom.xml # deps/rabbitmq_stream_management/priv/www/js/tmpl/streamConnection.ejs # deps/rabbitmq_stream_management/test/http_SUITE_data/pom.xml # deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_handler.erl # moduleindex.yaml # rabbitmq-components.mk # rabbitmq.bzl # release-notes/4.0.1.md # selenium/.gitignore # selenium/bin/components/devkeycloak # selenium/bin/components/fakeportal # selenium/bin/components/fakeproxy # selenium/bin/components/prodkeycloak # selenium/bin/components/rabbitmq # selenium/bin/components/uaa # selenium/bin/gen-env-file # selenium/bin/suite_template # selenium/fakeportal/app.js # selenium/full-suite-authnz-messaging # selenium/full-suite-management-ui # selenium/package.json # selenium/run-suites.sh # selenium/short-suite-management-ui # selenium/suites/authnz-messaging/auth-internal-backend.sh # selenium/suites/authnz-mgt/oauth-with-uaa.sh # selenium/test/amqp.js # selenium/test/authnz-msg-protocols/amqp10.js # selenium/test/authnz-msg-protocols/env.auth-oauth-dev.docker # selenium/test/authnz-msg-protocols/env.auth-oauth-dev.local # selenium/test/authnz-msg-protocols/env.auth-oauth-prod.docker # selenium/test/authnz-msg-protocols/env.auth-oauth-prod.local # selenium/test/authnz-msg-protocols/env.docker.devkeycloak # selenium/test/authnz-msg-protocols/env.docker.prodkeycloak # selenium/test/authnz-msg-protocols/env.local.devkeycloak # selenium/test/authnz-msg-protocols/env.local.prodkeycloak # selenium/test/authnz-msg-protocols/mqtt.js # selenium/test/basic-auth/env.local # selenium/test/basic-auth/imports/users.json # selenium/test/basic-auth/rabbitmq.conf # selenium/test/connections/amqp10/sessions-for-monitoring-user.js # selenium/test/env.docker # selenium/test/env.local # selenium/test/env.tls.docker # selenium/test/env.tls.local # selenium/test/exchanges/management.js # selenium/test/multi-oauth/env.local # selenium/test/multi-oauth/env.local.devkeycloak # selenium/test/multi-oauth/env.local.prodkeycloak # selenium/test/multi-oauth/rabbitmq.tls.conf # selenium/test/oauth/env.docker.fakeportal # selenium/test/oauth/env.docker.fakeproxy # selenium/test/oauth/env.docker.uaa # selenium/test/oauth/env.local # selenium/test/oauth/env.local.fakeportal # selenium/test/oauth/env.local.keycloak # selenium/test/oauth/env.local.uaa # selenium/test/oauth/rabbitmq.conf # selenium/test/oauth/rabbitmq.keycloak-mgt-oauth-provider.conf # selenium/test/oauth/rabbitmq.tls.conf # selenium/test/oauth/uaa/uaa.yml # selenium/test/pageobjects/BasePage.js # selenium/test/pageobjects/OverviewPage.js --- .github/DISCUSSION_TEMPLATE/ideas.yml | 54 + .github/DISCUSSION_TEMPLATE/other.yml | 54 + .github/DISCUSSION_TEMPLATE/questions.yml | 209 ++++ .../check-build-system-equivalence.yaml | 4 + .github/workflows/gazelle-scheduled.yaml | 6 + .github/workflows/oci-arm64-make.yaml | 4 + .github/workflows/oci-make.yaml | 19 + .github/workflows/peer-discovery-aws.yaml | 105 ++ .github/workflows/release-4.1.x-alphas.yaml | 36 + .../workflows/templates/test.template.yaml | 4 + .github/workflows/test-authnz.yaml | 27 + .github/workflows/test-make-target.yaml | 31 + .github/workflows/test-make.yaml | 3 + .../workflows/test-management-ui-for-pr.yaml | 15 + .github/workflows/test-management-ui.yaml | 20 + .github/workflows/test-mixed-versions.yaml | 29 + .github/workflows/test-plugin-mixed.yaml | 4 + .github/workflows/test-plugin.yaml | 4 + .github/workflows/test.yaml | 29 + COMMUNITY_SUPPORT.md | 10 + MODULE.bazel | 9 + Makefile | 4 + bazel/BUILD.horus | 9 + bazel/BUILD.redbug | 4 + bazel/bzlmod/secondary_umbrella.bzl | 10 + deps/amqp10_client/BUILD.bazel | 4 + deps/amqp10_client/app.bzl | 3 + deps/amqp10_client/src/amqp10_client.erl | 10 + .../src/amqp10_client_connection.erl | 69 ++ .../src/amqp10_client_frame_reader.erl | 5 + .../src/amqp10_client_session.erl | 98 ++ .../amqp10_client/src/amqp10_client_types.erl | 33 + deps/amqp10_client/src/amqp10_msg.erl | 42 + deps/amqp10_client/test/system_SUITE.erl | 132 +++ deps/amqp10_common/app.bzl | 4 + deps/amqp10_common/include/amqp10_filtex.hrl | 15 + .../src/amqp_network_connection.erl | 4 + deps/oauth2_client/app.bzl | 12 + deps/oauth2_client/include/oauth2_client.hrl | 7 + deps/oauth2_client/include/types.hrl | 75 ++ deps/oauth2_client/src/oauth2_client.erl | 378 +++++++ deps/oauth2_client/test/system_SUITE.erl | 238 +++++ deps/oauth2_client/test/unit_SUITE.erl | 83 ++ deps/rabbit/BUILD.bazel | 58 ++ deps/rabbit/Makefile | 8 + deps/rabbit/app.bzl | 73 ++ deps/rabbit/ct.test.spec | 13 + deps/rabbit/include/rabbit_amqp.hrl | 4 + deps/rabbit/src/mc.erl | 50 + deps/rabbit/src/mc_amqp.erl | 66 ++ deps/rabbit/src/mc_amqpl.erl | 38 + deps/rabbit/src/mc_compat.erl | 10 + deps/rabbit/src/mc_util.erl | 26 + deps/rabbit/src/rabbit_access_control.erl | 4 + deps/rabbit/src/rabbit_amqp_filtex.erl | 200 ++++ deps/rabbit/src/rabbit_amqp_management.erl | 16 + deps/rabbit/src/rabbit_amqp_reader.erl | 273 +++++ deps/rabbit/src/rabbit_amqp_reader.hrl | 17 + deps/rabbit/src/rabbit_amqp_session.erl | 491 +++++++++ deps/rabbit/src/rabbit_amqp_util.erl | 16 + deps/rabbit/src/rabbit_amqp_writer.erl | 31 + deps/rabbit/src/rabbit_amqqueue.erl | 23 + deps/rabbit/src/rabbit_amqqueue_process.erl | 23 + deps/rabbit/src/rabbit_backing_queue.erl | 5 + deps/rabbit/src/rabbit_binding.erl | 185 ++++ deps/rabbit/src/rabbit_channel.erl | 8 + deps/rabbit/src/rabbit_core_ff.erl | 96 ++ deps/rabbit/src/rabbit_db_binding.erl | 42 + deps/rabbit/src/rabbit_db_cluster.erl | 4 + deps/rabbit/src/rabbit_db_exchange.erl | 16 + deps/rabbit/src/rabbit_depr_ff_extra.erl | 4 + .../rabbit/src/rabbit_deprecated_features.erl | 10 + deps/rabbit/src/rabbit_exchange.erl | 19 + deps/rabbit/src/rabbit_feature_flags.erl | 243 +++++ deps/rabbit/src/rabbit_ff_controller.erl | 465 +++++++++ deps/rabbit/src/rabbit_ff_extra.erl | 33 + deps/rabbit/src/rabbit_ff_registry.erl | 10 + .../rabbit/src/rabbit_ff_registry_factory.erl | 89 ++ .../rabbit/src/rabbit_ff_registry_wrapper.erl | 10 + deps/rabbit/src/rabbit_global_counters.erl | 19 + deps/rabbit/src/rabbit_khepri.erl | 4 + deps/rabbit/src/rabbit_mnesia.erl | 45 + deps/rabbit/src/rabbit_msg_size_metrics.erl | 143 +++ deps/rabbit/src/rabbit_networking.erl | 12 + .../src/rabbit_prelaunch_feature_flags.erl | 6 + deps/rabbit/src/rabbit_queue_type.erl | 17 + deps/rabbit/src/rabbit_quorum_queue.erl | 4 + deps/rabbit/src/rabbit_reader.erl | 44 + deps/rabbit/src/rabbit_stream_queue.erl | 114 +++ deps/rabbit/test/amqp_address_SUITE.erl | 18 + deps/rabbit/test/amqp_auth_SUITE.erl | 13 + deps/rabbit/test/amqp_client_SUITE.erl | 493 +++++++++ deps/rabbit/test/amqp_filtex_SUITE.erl | 665 ++++++++++++ deps/rabbit/test/amqp_system_SUITE.erl | 8 + deps/rabbit/test/amqp_utils.erl | 144 +++ deps/rabbit/test/dead_lettering_SUITE.erl | 15 + ...disconnect_detected_during_alarm_SUITE.erl | 4 + deps/rabbit/test/feature_flags_v2_SUITE.erl | 171 ++++ deps/rabbit/test/mc_unit_SUITE.erl | 122 +++ deps/rabbit/test/msg_size_metrics_SUITE.erl | 154 +++ deps/rabbit/test/quorum_queue_SUITE.erl | 32 + deps/rabbit/test/rabbit_db_binding_SUITE.erl | 10 + deps/rabbit/test/rabbit_db_queue_SUITE.erl | 5 + deps/rabbit/test/topic_permission_SUITE.erl | 100 ++ .../test/unit_msg_size_metrics_SUITE.erl | 64 ++ .../rabbit_common/mk/rabbitmq-early-plugin.mk | 4 + .../rabbit_common/src/rabbit_core_metrics.erl | 6 + deps/rabbit_common/src/rabbit_env.erl | 18 + deps/rabbit_common/src/rabbit_event.erl | 39 + deps/rabbit_common/src/rabbit_ssl_options.erl | 28 + deps/rabbit_common/test/rabbit_env_SUITE.erl | 21 + .../src/rabbitmq_amqp_client.erl | 36 + .../rabbitmq_auth_backend_spring_boot/pom.xml | 8 + .../pom.xml | 8 + .../src/rabbit_auth_backend_http.erl | 4 + .../src/rabbit_auth_backend_ldap.erl | 4 + deps/rabbitmq_auth_backend_oauth2/BUILD.bazel | 11 + deps/rabbitmq_auth_backend_oauth2/Makefile | 4 + deps/rabbitmq_auth_backend_oauth2/README.md | 20 + deps/rabbitmq_auth_backend_oauth2/app.bzl | 57 ++ .../include/oauth2.hrl | 47 + .../rabbitmq_auth_backend_oauth2.schema | 97 ++ .../src/rabbit_auth_backend_oauth2.erl | 287 ++++++ .../src/rabbit_oauth2_keycloak.erl | 41 + .../src/rabbit_oauth2_provider.erl | 197 ++++ .../src/rabbit_oauth2_rar.erl | 183 ++++ .../src/rabbit_oauth2_resource_server.erl | 240 +++++ .../src/rabbit_oauth2_schema.erl | 269 +++++ .../src/rabbit_oauth2_scope.erl | 28 + .../src/uaa_jwks.erl | 4 + .../src/uaa_jwt.erl | 131 +++ .../src/uaa_jwt_jwt.erl | 21 + .../rabbitmq_auth_backend_oauth2.snippets | 164 +++ .../test/jwks_SUITE.erl | 473 +++++++++ .../test/rabbit_oauth2_provider_SUITE.erl | 523 ++++++++++ .../rabbit_oauth2_resource_server_SUITE.erl | 452 ++++++++ .../test/rabbit_oauth2_schema_SUITE.erl | 299 ++++++ .../test/system_SUITE.erl | 302 ++++++ .../test/unit_SUITE.erl | 962 ++++++++++++++++++ deps/rabbitmq_cli/Makefile | 5 + .../ctl/commands/list_connections_command.ex | 8 + deps/rabbitmq_cli/mix.exs | 4 + deps/rabbitmq_event_exchange/BUILD.bazel | 4 + deps/rabbitmq_event_exchange/Makefile | 13 + deps/rabbitmq_event_exchange/README.md | 8 + deps/rabbitmq_event_exchange/app.bzl | 8 + .../schema/rabbitmq_event_exchange.schema | 7 + .../src/rabbit_exchange_type_event.erl | 171 ++++ .../rabbitmq_event_exchange.snippets | 35 + .../test/system_SUITE.erl | 509 +++++++++ deps/rabbitmq_management/.gitignore | 5 + deps/rabbitmq_management/BUILD.bazel | 16 + deps/rabbitmq_management/Makefile | 4 + deps/rabbitmq_management/app.bzl | 39 + .../priv/schema/rabbitmq_management.schema | 55 + .../rabbitmq_management/priv/www/css/main.css | 64 ++ .../priv/www/js/dispatcher.js | 18 + .../rabbitmq_management/priv/www/js/global.js | 39 + deps/rabbitmq_management/priv/www/js/main.js | 20 + .../priv/www/js/oidc-oauth/helper.js | 78 ++ .../priv/www/js/tmpl/connection.ejs | 52 + .../priv/www/js/tmpl/connections.ejs | 29 + .../priv/www/js/tmpl/deprecated-features.ejs | 6 + .../priv/www/js/tmpl/feature-flags.ejs | 371 +++++++ .../priv/www/js/tmpl/sessions-list.ejs | 112 ++ .../src/rabbit_mgmt_dispatcher.erl | 4 + .../src/rabbit_mgmt_schema.erl | 67 ++ .../src/rabbit_mgmt_wm_auth.erl | 217 ++++ .../rabbit_mgmt_wm_connection_sessions.erl | 91 ++ .../test/clustering_prop_SUITE.erl | 25 + .../rabbitmq_management.snippets | 49 + deps/rabbitmq_management/test/js/.babelrc | 3 + deps/rabbitmq_management/test/js/package.json | 35 + .../test/js/test/oidc-oauth/helper.test.js | 22 + .../test/rabbit_mgmt_http_SUITE.erl | 180 ++++ .../test/rabbit_mgmt_schema_SUITE.erl | 76 ++ .../test/rabbit_mgmt_wm_auth_SUITE.erl | 539 ++++++++++ .../src/rabbit_mgmt_ff.erl | 13 + deps/rabbitmq_mqtt/BUILD.bazel | 13 + deps/rabbitmq_mqtt/Makefile | 5 + deps/rabbitmq_mqtt/src/mc_mqtt.erl | 12 + deps/rabbitmq_mqtt/src/rabbit_mqtt.erl | 10 + deps/rabbitmq_mqtt/src/rabbit_mqtt_ff.erl | 14 + .../src/rabbit_mqtt_processor.erl | 4 + deps/rabbitmq_mqtt/src/rabbit_mqtt_reader.erl | 21 + .../test/java_SUITE_data/pom.xml | 8 + deps/rabbitmq_mqtt/test/mc_mqtt_SUITE.erl | 11 + deps/rabbitmq_mqtt/test/mqtt_shared_SUITE.erl | 35 + deps/rabbitmq_prometheus/BUILD.bazel | 14 + deps/rabbitmq_prometheus/app.bzl | 12 + ...heus_rabbitmq_global_metrics_collector.erl | 16 + ...abbitmq_message_size_metrics_collector.erl | 33 + .../src/rabbit_prometheus_dispatcher.erl | 9 + .../test/rabbit_prometheus_http_SUITE.erl | 42 + .../test/protocol_interop_SUITE.erl | 63 ++ .../test/rabbit_stream_SUITE_data/pom.xml | 8 + .../priv/www/js/tmpl/streamConnection.ejs | 4 + .../test/http_SUITE_data/pom.xml | 8 + .../src/rabbit_web_mqtt_handler.erl | 17 + moduleindex.yaml | 27 + rabbitmq-components.mk | 4 + rabbitmq.bzl | 8 + release-notes/4.0.1.md | 9 + release-notes/4.1.0.md | 371 +++++++ selenium/.gitignore | 6 +- selenium/bin/components/devkeycloak | 4 + selenium/bin/components/fakeportal | 19 + selenium/bin/components/fakeproxy | 19 + selenium/bin/components/prodkeycloak | 9 + selenium/bin/components/rabbitmq | 23 + selenium/bin/components/uaa | 14 + selenium/bin/gen-env-file | 12 + selenium/bin/suite_template | 47 + selenium/fakeportal/app.js | 7 + selenium/full-suite-authnz-messaging | 4 + selenium/full-suite-management-ui | 7 + selenium/package.json | 8 + selenium/run-suites.sh | 4 + selenium/short-suite-management-ui | 8 + .../authnz-messaging/auth-internal-backend.sh | 4 + .../auth-internal-mtls-backend.sh | 9 + selenium/suites/authnz-mgt/oauth-with-uaa.sh | 4 + selenium/suites/mgt/amqp10-connections.sh | 9 + selenium/test/amqp.js | 23 + selenium/test/authnz-msg-protocols/amqp10.js | 22 + .../test/authnz-msg-protocols/env.auth-mtls | 2 + .../env.auth-oauth-dev.docker | 4 + .../env.auth-oauth-dev.local | 4 + .../env.auth-oauth-prod.docker | 4 + .../env.auth-oauth-prod.local | 4 + .../env.docker.devkeycloak | 4 + .../env.docker.prodkeycloak | 4 + .../env.local.devkeycloak | 4 + .../env.local.prodkeycloak | 4 + selenium/test/authnz-msg-protocols/env.tls | 2 + selenium/test/authnz-msg-protocols/mqtt.js | 37 + .../rabbitmq.auth-mtls.conf | 13 + .../authnz-msg-protocols/rabbitmq.tls.conf | 13 + selenium/test/basic-auth/env.local | 4 + selenium/test/basic-auth/imports/users.json | 20 + selenium/test/basic-auth/rabbitmq.conf | 4 + .../amqp10/sessions-for-monitoring-user.js | 16 + selenium/test/env.docker | 4 + selenium/test/env.local | 5 + selenium/test/env.tls.docker | 5 + selenium/test/env.tls.local | 6 + selenium/test/exchanges/management.js | 14 + selenium/test/multi-oauth/env.local | 4 + .../test/multi-oauth/env.local.devkeycloak | 4 + .../test/multi-oauth/env.local.prodkeycloak | 4 + selenium/test/multi-oauth/rabbitmq.tls.conf | 12 + selenium/test/oauth/env.docker.fakeportal | 4 + selenium/test/oauth/env.docker.fakeproxy | 4 + selenium/test/oauth/env.docker.uaa | 4 + selenium/test/oauth/env.local | 4 + selenium/test/oauth/env.local.fakeportal | 4 + selenium/test/oauth/env.local.keycloak | 4 + selenium/test/oauth/env.local.uaa | 4 + selenium/test/oauth/keycloak/openssl.cnf.in | 3 + selenium/test/oauth/rabbitmq.conf | 4 + .../rabbitmq.keycloak-mgt-oauth-provider.conf | 4 + selenium/test/oauth/rabbitmq.tls.conf | 12 + selenium/test/oauth/uaa/server.xml | 43 + selenium/test/oauth/uaa/uaa.yml | 6 + selenium/test/pageobjects/BasePage.js | 7 + selenium/test/pageobjects/ConnectionPage.js | 65 ++ selenium/test/pageobjects/ConnectionsPage.js | 25 + selenium/test/pageobjects/OverviewPage.js | 3 + 268 files changed, 16449 insertions(+), 1 deletion(-) create mode 100644 .github/DISCUSSION_TEMPLATE/ideas.yml create mode 100644 .github/DISCUSSION_TEMPLATE/other.yml create mode 100644 .github/DISCUSSION_TEMPLATE/questions.yml create mode 100644 .github/workflows/peer-discovery-aws.yaml create mode 100644 .github/workflows/release-4.1.x-alphas.yaml create mode 100644 deps/amqp10_common/include/amqp10_filtex.hrl create mode 100644 deps/oauth2_client/include/types.hrl create mode 100644 deps/rabbit/src/rabbit_amqp_filtex.erl create mode 100644 deps/rabbit/src/rabbit_amqp_reader.hrl create mode 100644 deps/rabbit/src/rabbit_msg_size_metrics.erl create mode 100644 deps/rabbit/test/amqp_filtex_SUITE.erl create mode 100644 deps/rabbit/test/amqp_utils.erl create mode 100644 deps/rabbit/test/msg_size_metrics_SUITE.erl create mode 100644 deps/rabbit/test/unit_msg_size_metrics_SUITE.erl create mode 100644 deps/rabbitmq_auth_backend_oauth2/include/oauth2.hrl create mode 100644 deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_keycloak.erl create mode 100644 deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_provider.erl create mode 100644 deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_rar.erl create mode 100644 deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_resource_server.erl create mode 100644 deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_provider_SUITE.erl create mode 100644 deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_resource_server_SUITE.erl create mode 100644 deps/rabbitmq_management/priv/www/js/tmpl/sessions-list.ejs create mode 100644 deps/rabbitmq_management/src/rabbit_mgmt_schema.erl create mode 100644 deps/rabbitmq_management/src/rabbit_mgmt_wm_connection_sessions.erl create mode 100644 deps/rabbitmq_management/test/js/.babelrc create mode 100644 deps/rabbitmq_management/test/js/package.json create mode 100644 deps/rabbitmq_management/test/js/test/oidc-oauth/helper.test.js create mode 100644 deps/rabbitmq_management/test/rabbit_mgmt_schema_SUITE.erl create mode 100644 deps/rabbitmq_prometheus/src/collectors/prometheus_rabbitmq_message_size_metrics_collector.erl create mode 100644 release-notes/4.1.0.md create mode 100755 selenium/suites/authnz-messaging/auth-internal-mtls-backend.sh create mode 100755 selenium/suites/mgt/amqp10-connections.sh create mode 100644 selenium/test/authnz-msg-protocols/env.auth-mtls create mode 100644 selenium/test/authnz-msg-protocols/env.tls create mode 100644 selenium/test/authnz-msg-protocols/rabbitmq.auth-mtls.conf create mode 100644 selenium/test/authnz-msg-protocols/rabbitmq.tls.conf create mode 100644 selenium/test/oauth/keycloak/openssl.cnf.in create mode 100644 selenium/test/oauth/uaa/server.xml create mode 100644 selenium/test/pageobjects/ConnectionPage.js create mode 100644 selenium/test/pageobjects/ConnectionsPage.js diff --git a/.github/DISCUSSION_TEMPLATE/ideas.yml b/.github/DISCUSSION_TEMPLATE/ideas.yml new file mode 100644 index 000000000000..e004231f38ac --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/ideas.yml @@ -0,0 +1,54 @@ +title: "[Suggestion] " +body: + - type: markdown + attributes: + value: | + ## Before We Start + + Please provide reasonably detailed responses to the question below to help the Core Team and maintainers + to understand how you run RabbitMQ and why you'd like to see the suggested changes. + - type: markdown + attributes: + value: | + ## Relevant Details + - type: dropdown + id: rabbitmq_series + attributes: + label: RabbitMQ series + options: + - 4.0.x + - 4.1.x + validations: + required: true + - type: input + id: os + attributes: + label: Operating system (distribution) used + description: What OS or distribution do you run RabbitMQ on? + validations: + required: true + - type: dropdown + id: deployment_type + attributes: + label: How is RabbitMQ deployed? + options: + - Community Docker image + - Debian package + - RPM package + - Generic binary package + - Kubernetes Operator(s) from Team RabbitMQ + - Bitnami Helm chart + - Chocolatey package + - Windows installer + - Windows binary package + - RabbitMQ-as-a-Service from a public cloud provider + - Other + validations: + required: true + - type: textarea + id: details + attributes: + label: What would you like to suggest for a future version of RabbitMQ? + description: Please take the time to explain how you use RabbitMQ and why this change is important + validations: + required: true diff --git a/.github/DISCUSSION_TEMPLATE/other.yml b/.github/DISCUSSION_TEMPLATE/other.yml new file mode 100644 index 000000000000..204e307a8cff --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/other.yml @@ -0,0 +1,54 @@ +title: "[Other] " +body: + - type: markdown + attributes: + value: | + ## Before We Start + + This category exists for free form questions where deployment details are less relevant, e.g. application and topology + design kind of questions. Please provide a reasonably detailed description of what you are trying to do with RabbitMQ. + - type: checkboxes + attributes: + label: Community Support Policy + description: + options: + - label: I have read [RabbitMQ's Community Support Policy](https://github.com/rabbitmq/rabbitmq-server/blob/main/COMMUNITY_SUPPORT.md) + required: true + - type: markdown + attributes: + value: | + ## Relevant Details + - type: dropdown + id: rabbitmq_version + attributes: + label: RabbitMQ version used + options: + - 4.0.3 + - 3.13.7 or older + validations: + required: true + - type: dropdown + id: deployment_type + attributes: + label: How is RabbitMQ deployed? + options: + - Community Docker image + - Debian package + - RPM package + - Generic binary package + - Kubernetes Operator(s) from Team RabbitMQ + - Bitnami Helm chart + - Chocolatey package + - Windows installer + - Windows binary package + - RabbitMQ-as-a-Service from a public cloud provider + - Other + validations: + required: true + - type: textarea + id: details + attributes: + label: Steps to reproduce the behavior in question + description: What specific steps need to be performed in order to reproduce this behavior? Why? + validations: + required: true diff --git a/.github/DISCUSSION_TEMPLATE/questions.yml b/.github/DISCUSSION_TEMPLATE/questions.yml new file mode 100644 index 000000000000..2afa9bd49df5 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/questions.yml @@ -0,0 +1,209 @@ +title: "[Questions] " +body: + - type: markdown + attributes: + value: | + ## Before We Start + + Please provide reasonably detailed responses to the question below to help others help you. + + If you omit relevant information, those trying to reproduce what you are about to report will have to guess. + Guessing is a very time consuming, and therefore expensive, approach to troubleshooting distributed messaging infrastructure. + - type: checkboxes + attributes: + label: Community Support Policy + description: + options: + - label: I have read [RabbitMQ's Community Support Policy](https://github.com/rabbitmq/rabbitmq-server/blob/main/COMMUNITY_SUPPORT.md) + required: true + - label: I run RabbitMQ 4.x, the only series currently covered by [community support](https://www.rabbitmq.com/release-information) + required: true + - label: I promise to provide all relevant information (versions, logs from all nodes, rabbitmq-diagnostics output, detailed reproduction steps) + required: true + - type: markdown + attributes: + value: | + ## Relevant Details + - type: dropdown + id: rabbitmq_version + attributes: + label: RabbitMQ version used + options: + - 4.0.4 + - 4.0.3 + validations: + required: true + - type: dropdown + id: erlang_version + attributes: + label: Erlang version used + options: + - 26.2.x + - 26.1.x + - 26.0.x + validations: + required: true + - type: input + id: os + attributes: + label: Operating system (distribution) used + description: What OS or distribution do you run RabbitMQ on? + validations: + required: true + - type: dropdown + id: deployment_type + attributes: + label: How is RabbitMQ deployed? + options: + - Community Docker image + - Debian package + - RPM package + - Generic binary package + - Kubernetes Operator(s) from Team RabbitMQ + - Bitnami Helm chart + - Chocolatey package + - Windows installer + - Windows binary package + - RabbitMQ-as-a-Service from a public cloud provider + - Other + validations: + required: true + - type: textarea + id: diagnostics_status + attributes: + label: rabbitmq-diagnostics status output + value: | + See https://www.rabbitmq.com/docs/cli to learn how to use rabbitmq-diagnostics +
+ + ``` + # PASTE OUTPUT HERE, BETWEEN BACKTICKS + ``` +
+ validations: + required: true + - type: textarea + id: rabbitmq_logs + attributes: + label: Logs from node 1 (with sensitive values edited out) + description: Relevant RabbitMQ logs with sensitive values edited out + value: | + See https://www.rabbitmq.com/docs/logging to learn how to collect logs +
+ + ``` + # PASTE LOG HERE, BETWEEN BACKTICKS + ``` +
+ validations: + required: true + - type: textarea + id: logs_node_2 + attributes: + label: Logs from node 2 (if applicable, with sensitive values edited out) + description: Relevant RabbitMQ logs with sensitive values edited out + value: | + See https://www.rabbitmq.com/docs/logging to learn how to collect logs +
+ + ``` + # PASTE LOG HERE, BETWEEN BACKTICKS + ``` +
+ validations: + required: false + - type: textarea + id: logs_node_3 + attributes: + label: Logs from node 3 (if applicable, with sensitive values edited out) + description: Relevant RabbitMQ logs with sensitive values edited out + value: | + See https://www.rabbitmq.com/docs/logging to learn how to collect logs +
+ + ``` + # PASTE LOG HERE, BETWEEN BACKTICKS + ``` +
+ validations: + required: false + - type: textarea + id: rabbitmq_conf + attributes: + label: rabbitmq.conf + description: rabbitmq.conf contents + value: | + See https://www.rabbitmq.com/docs/configure#config-location to learn how to find rabbitmq.conf file location +
+ + ``` + # PASTE rabbitmq.conf HERE, BETWEEN BACKTICKS + ``` +
+ validations: + required: true + - type: textarea + id: deployment_steps + attributes: + label: Steps to deploy RabbitMQ cluster + description: How would you explain how you deploy RabbitMQ to a new colleague? + validations: + required: true + - type: textarea + id: reproduction_steps + attributes: + label: Steps to reproduce the behavior in question + description: What specific steps need to be performed in order to reproduce this behavior? Why? + validations: + required: true + - type: textarea + id: advanced_config + attributes: + label: advanced.config + description: advanced.config contents (if applicable) + value: | + See https://www.rabbitmq.com/docs/configure#config-location to learn how to find advanced.config file location +
+ + ``` + # PASTE advanced.config HERE, BETWEEN BACKTICKS + ``` +
+ validations: + required: false + - type: textarea + id: app_code + attributes: + label: Application code + description: Relevant messaging-related parts of application code + value: | +
+ + ```python + # PASTE CODE HERE, BETWEEN BACKTICKS + ``` +
+ validations: + required: false + - type: textarea + id: k8s_deployment + attributes: + label: Kubernetes deployment file + description: Kubernetes deployment YAML that demonstrates how RabbitMQ is deployed (if applicable) + value: | +
+ + ```yaml + # Relevant parts of K8S deployment that demonstrate how RabbitMQ is deployed + # PASTE YAML HERE, BETWEEN BACKTICKS + ``` +
+ validations: + required: false + - type: textarea + id: question + attributes: + label: What problem are you trying to solve? + description: and why? + validations: + required: true \ No newline at end of file diff --git a/.github/workflows/check-build-system-equivalence.yaml b/.github/workflows/check-build-system-equivalence.yaml index d79d8297340f..437b48c471c8 100644 --- a/.github/workflows/check-build-system-equivalence.yaml +++ b/.github/workflows/check-build-system-equivalence.yaml @@ -23,7 +23,11 @@ on: elixir_version: description: 'Elixir version to build with' required: true +<<<<<<< HEAD default: "1.17" +======= + default: "1.15" +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) project_version: description: 'PROJECT_VERSION used for make' required: true diff --git a/.github/workflows/gazelle-scheduled.yaml b/.github/workflows/gazelle-scheduled.yaml index b6bb0648434c..443f9b60232d 100644 --- a/.github/workflows/gazelle-scheduled.yaml +++ b/.github/workflows/gazelle-scheduled.yaml @@ -12,9 +12,15 @@ jobs: matrix: target_branch: - main +<<<<<<< HEAD - v3.12.x - v3.11.x - v3.10.x +======= + - v4.0.x + - v3.13.x + - v3.12.x +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) timeout-minutes: 10 steps: - name: CHECKOUT REPOSITORY diff --git a/.github/workflows/oci-arm64-make.yaml b/.github/workflows/oci-arm64-make.yaml index 0e4dbf212645..d9ad79d79439 100644 --- a/.github/workflows/oci-arm64-make.yaml +++ b/.github/workflows/oci-arm64-make.yaml @@ -47,7 +47,11 @@ jobs: - name: make package-generic-unix if: steps.authorized.outputs.authorized == 'true' run: | +<<<<<<< HEAD make package-generic-unix PROJECT_VERSION=4.0.0 +======= + make package-generic-unix PROJECT_VERSION=4.1.0-alpha.1 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - name: Upload package-generic-unix if: steps.authorized.outputs.authorized == 'true' uses: actions/upload-artifact@v4.3.1 diff --git a/.github/workflows/oci-make.yaml b/.github/workflows/oci-make.yaml index 6c1a2ba61583..a3bf2f67a604 100644 --- a/.github/workflows/oci-make.yaml +++ b/.github/workflows/oci-make.yaml @@ -40,7 +40,11 @@ jobs: - name: make package-generic-unix if: steps.authorized.outputs.authorized == 'true' run: | +<<<<<<< HEAD make package-generic-unix PROJECT_VERSION=4.0.0 +======= + make package-generic-unix PROJECT_VERSION=4.1.0-alpha.1 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - name: Upload package-generic-unix if: steps.authorized.outputs.authorized == 'true' uses: actions/upload-artifact@v4.3.1 @@ -65,7 +69,11 @@ jobs: - name: Prepare run: | platform=${{ matrix.platform }} +<<<<<<< HEAD echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV +======= + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - name: Checkout uses: actions/checkout@v4 - name: Download package-generic-unix @@ -116,7 +124,11 @@ jobs: run: | mkdir -p /tmp/digests digest="${{ steps.build.outputs.digest }}" +<<<<<<< HEAD touch "/tmp/digests/${digest#sha256:}" +======= + touch "/tmp/digests/${digest#sha256:}" +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - name: Upload digest uses: actions/upload-artifact@v4 with: @@ -157,10 +169,17 @@ jobs: working-directory: /tmp/digests run: | docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ +<<<<<<< HEAD $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) - name: Inspect image run: | docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} +======= + $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) summary-oci: needs: diff --git a/.github/workflows/peer-discovery-aws.yaml b/.github/workflows/peer-discovery-aws.yaml new file mode 100644 index 000000000000..2e94da990fcd --- /dev/null +++ b/.github/workflows/peer-discovery-aws.yaml @@ -0,0 +1,105 @@ +name: Peer Discovery AWS Integration Test +on: + push: + paths: + - "deps/rabbitmq_peer_discovery_aws/**" + - "deps/rabbitmq_peer_discovery_common/**" + - "deps/rabbit/src/rabbit_peer_discovery.erl" + schedule: + - cron: "4 0 * * MON" + workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true +jobs: + peer-discovery-aws-integration-test: + name: Integration Test + runs-on: ubuntu-22.04 + timeout-minutes: 45 + steps: + - name: CHECK IF IMAGE WILL PUSH + id: authorized + run: | + if [ -n "${{ secrets.DOCKERHUB_PASSWORD }}" ]; then + echo "authorized=true" | tee -a $GITHUB_OUTPUT + else + echo "authorized=false" | tee -a $GITHUB_OUTPUT + fi + - name: CHECKOUT REPOSITORY + if: steps.authorized.outputs.authorized == 'true' + uses: actions/checkout@v4 + - uses: docker/metadata-action@v5 + if: steps.authorized.outputs.authorized == 'true' + id: metadata + with: + images: pivotalrabbitmq/rabbitmq + tags: | + type=sha,format=long + - uses: int128/wait-for-docker-image-action@v1 + if: steps.authorized.outputs.authorized == 'true' + with: + tags: ${{ steps.metadata.outputs.tags }} + timeout-seconds: 3600 + polling-seconds: 60 + - name: COMPUTE REPO CACHE KEY + if: steps.authorized.outputs.authorized == 'true' + id: repo-cache-key + run: | + echo "value=bazel-repo-cache-${{ hashFiles('MODULE.bazel') }}" | tee -a $GITHUB_OUTPUT + - name: LOAD REPO CACHE + if: steps.authorized.outputs.authorized == 'true' + uses: actions/cache/restore@v4 + with: + key: ${{ steps.repo-cache-key.outputs.value }} + path: /home/runner/repo-cache/ + - name: CONFIGURE OTP & ELIXIR + if: steps.authorized.outputs.authorized == 'true' + uses: erlef/setup-beam@v1.17 + with: + otp-version: 26 + elixir-version: 1.15 + - name: SETUP ecs-cli + if: steps.authorized.outputs.authorized == 'true' + env: + ECS_CLI_VERSION: 1.21.0 + run: | + curl -Lo /usr/local/bin/ecs-cli https://amazon-ecs-cli.s3.amazonaws.com/ecs-cli-linux-amd64-v${ECS_CLI_VERSION} && \ + chmod +x /usr/local/bin/ecs-cli && \ + ecs-cli --version + - name: AUTHENTICATE TO GOOGLE CLOUD + if: steps.authorized.outputs.authorized == 'true' + uses: google-github-actions/auth@v2.1.7 + with: + credentials_json: ${{ secrets.REMOTE_CACHE_CREDENTIALS_JSON }} + - name: CONFIGURE BAZEL + if: steps.authorized.outputs.authorized == 'true' + run: | + if [ -n "${{ secrets.REMOTE_CACHE_BUCKET_NAME }}" ]; then + cat << EOF >> user.bazelrc + build --remote_cache=https://storage.googleapis.com/${{ secrets.REMOTE_CACHE_BUCKET_NAME }} + build --google_default_credentials + + build --experimental_guard_against_concurrent_changes + EOF + fi + cat << EOF >> user.bazelrc + build --repository_cache=/home/runner/repo-cache/ + build --color=yes + EOF + + bazelisk info release + #! - name: Setup tmate session + #! uses: mxschmitt/action-tmate@v3 + - name: RUN INTEGRATION TESTS + if: steps.authorized.outputs.authorized == 'true' + run: | + branch_or_tag="${GITHUB_REF##*/}" + bazelisk test //deps/rabbitmq_peer_discovery_aws:integration_SUITE \ + --test_tag_filters=aws \ + --build_tests_only \ + --test_env AWS_ACCESS_KEY_ID=${{ secrets.CONCOURSE_AWS_ACCESS_KEY_ID }} \ + --test_env AWS_SECRET_ACCESS_KEY=${{ secrets.CONCOURSE_AWS_SECRET_ACCESS_KEY }} \ + --test_env RABBITMQ_IMAGE="pivotalrabbitmq/rabbitmq:sha-${{ github.sha }}" \ + --test_env AWS_ECS_CLUSTER_NAME="rabbitmq-peer-discovery-aws-actions-${branch_or_tag//[._]/-}" \ + --test_output=streamed \ + --verbose_failures diff --git a/.github/workflows/release-4.1.x-alphas.yaml b/.github/workflows/release-4.1.x-alphas.yaml new file mode 100644 index 000000000000..2c1f44ed2ed4 --- /dev/null +++ b/.github/workflows/release-4.1.x-alphas.yaml @@ -0,0 +1,36 @@ +name: "Trigger a 4.1.x alpha release build" +on: + workflow_dispatch: + push: + branches: + # 4.1.x + - "main" + paths: + - "deps/*/src/**" + - 'deps/rabbitmq_management/priv/**' + - ".github/workflows/**" + - "rabbitmq-components.mk" +env: + DEV_WORKFLOW_REPOSITORY: "rabbitmq/server-packages" +jobs: + trigger_alpha_build: + runs-on: ubuntu-latest + steps: + - name: Compute prerelease identifier from commit SHA + run: echo "PRERELEASE_IDENTIFIER=`echo ${{ github.sha }} | cut -c1-8`" >> $GITHUB_ENV + - name: Trigger a 4.0.x alpha build in ${{ env.DEV_WORKFLOW_REPOSITORY }} + uses: peter-evans/repository-dispatch@v3 + with: + token: ${{ secrets.RABBITMQCI_BOT_TOKEN }} + repository: ${{ env.DEV_WORKFLOW_REPOSITORY }} + event-type: "new_4.1.x_alpha" + client-payload: |- + { + "release_repository": "${{ env.DEV_WORKFLOW_REPOSITORY }}", + "release_description": "Commit: https://github.com/rabbitmq/rabbitmq-server/commit/${{ github.sha }}, pushed at: ${{ github.event.repository.pushed_at }}", + "prerelease": true, + "prerelease_kind": "alpha", + "prerelease_identifier": "${{ env.PRERELEASE_IDENTIFIER }}", + "release_title": "RabbitMQ ${{ vars.SERVER_41_NEXT_PATCH_VERSION }}-alpha.${{ env.PRERELEASE_IDENTIFIER }} (from ${{ github.event.repository.pushed_at }})", + "base_version": "${{ vars.SERVER_41_NEXT_PATCH_VERSION }}" + } diff --git a/.github/workflows/templates/test.template.yaml b/.github/workflows/templates/test.template.yaml index bbb361518df6..268256941324 100644 --- a/.github/workflows/templates/test.template.yaml +++ b/.github/workflows/templates/test.template.yaml @@ -23,7 +23,11 @@ on: push: branches: #! - main +<<<<<<< HEAD #! - v4.0.x +======= + - v4.0.x +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - v3.13.x - v3.12.x - v3.11.x diff --git a/.github/workflows/test-authnz.yaml b/.github/workflows/test-authnz.yaml index e652fee19879..e6e9d2773461 100644 --- a/.github/workflows/test-authnz.yaml +++ b/.github/workflows/test-authnz.yaml @@ -10,7 +10,11 @@ on: - 'deps/rabbitmq_auth_**' - 'deps/rabbitmq_management/src/**' - 'deps/rabbitmq_management/priv/**' +<<<<<<< HEAD - 'selenium/**' +======= + - 'deps/rabbitmq_management/selenium/**' +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - 'scripts/**' - .bazelrc - .bazelversion @@ -20,12 +24,19 @@ on: - .github/workflows/test-authnz.yaml pull_request: paths: +<<<<<<< HEAD - 'deps/rabbit/**' - 'deps/rabbitmq_auth_/**' - 'deps/rabbitmq_mqtt/**' - 'selenium/full-suite-authnz-messaging' - 'selenium/suites/authnz-messaging' - 'selenium/test/authnz-msg-protocols' +======= + - 'selenium/**' + - 'deps/rabbit/**' + - 'deps/rabbitmq_auth_/**' + - 'deps/rabbitmq_mqtt/**' +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - .github/workflows/test-authnz.yaml concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -64,6 +75,7 @@ jobs: with: credentials_json: ${{ secrets.REMOTE_CACHE_CREDENTIALS_JSON }} +<<<<<<< HEAD - name: Configure Bazel run: | if [ -n "${{ secrets.REMOTE_CACHE_BUCKET_NAME }}" ]; then @@ -81,6 +93,12 @@ jobs: - name: Build & Load RabbitMQ OCI run: | bazelisk run packaging/docker-image:rabbitmq-amd64 +======= + - name: Build & Load RabbitMQ OCI + run: | + make package-generic-unix + make docker-image +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - name: Configure Docker Network run: | @@ -93,9 +111,18 @@ jobs: - name: Run Suites run: | +<<<<<<< HEAD RABBITMQ_DOCKER_IMAGE=bazel/packaging/docker-image:rabbitmq-amd64 \ ${SELENIUM_DIR}/run-suites.sh full-suite-authnz-messaging +======= + IMAGE_TAG=$(find PACKAGES/rabbitmq-server-generic-unix-*.tar.xz | awk -F 'PACKAGES/rabbitmq-server-generic-unix-|.tar.xz' '{print $2}') + RABBITMQ_DOCKER_IMAGE=pivotalrabbitmq/rabbitmq:$IMAGE_TAG \ + ${SELENIUM_DIR}/run-suites.sh full-suite-authnz-messaging + mkdir -p /tmp/full-suite-authnz-messaging + mv /tmp/selenium/* /tmp/full-suite-authnz-messaging + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - name: Upload Test Artifacts if: always() uses: actions/upload-artifact@v4.3.2 diff --git a/.github/workflows/test-make-target.yaml b/.github/workflows/test-make-target.yaml index 8b57eb5044b0..2e8dac608ab9 100644 --- a/.github/workflows/test-make-target.yaml +++ b/.github/workflows/test-make-target.yaml @@ -57,35 +57,66 @@ jobs: uses: dsaltares/fetch-gh-release-asset@master if: inputs.mixed_clusters with: +<<<<<<< HEAD regex: true file: "rabbitmq-server-generic-unix-[\\d.]*\\.tar.xz" +======= + version: 'tags/v4.0.3' + regex: true + file: "rabbitmq-server-generic-unix-\\d.+\\.tar\\.xz" +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) target: ./ - name: MIXED CLUSTERS - SETUP SECONDARY_DIST if: inputs.mixed_clusters run: | +<<<<<<< HEAD gpg --import rabbitmq-release-signing-key.asc gpg --verify rabbitmq-server-generic-unix-*.asc rabbitmq-server-generic-unix-*.tar.xz tar xf rabbitmq-server-generic-unix-*.tar.xz echo "SECONDARY_DIST=${GITHUB_WORKSPACE}/rabbitmq_server-`echo -n ${{ steps.fetch_secondary_dist.outputs.version }} | sed s/v//`" >> $GITHUB_ENV +======= + ls -l rabbitmq-server-generic-unix-*.tar.xz* + + archive_name=$(echo rabbitmq-server-generic-unix-*.tar.xz) + archive_version=$(echo $archive_name | sed -E -e 's/^rabbitmq-server-generic-unix-//' -e 's/\.tar\.xz$//') + + gpg --import rabbitmq-release-signing-key.asc + gpg --verify $archive_name.asc $archive_name + tar xf $archive_name + + echo "SECONDARY_DIST=${GITHUB_WORKSPACE}/rabbitmq_server-$archive_version" >> $GITHUB_ENV +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - name: SETUP DOTNET (rabbit) uses: actions/setup-dotnet@v4 if: inputs.plugin == 'rabbit' with: +<<<<<<< HEAD dotnet-version: '3.1.x' +======= + dotnet-version: '8.0' +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - name: SETUP SLAPD (rabbitmq_auth_backend_ldap) if: inputs.plugin == 'rabbitmq_auth_backend_ldap' run: | sudo apt-get update && \ sudo apt-get install -y \ +<<<<<<< HEAD apparmor-utils \ ldap-utils \ slapd sudo aa-complain `which slapd` +======= + ldap-utils \ + slapd + + sudo systemctl is-active --quiet apparmor.service && sudo systemctl stop apparmor.service + sudo systemctl disable apparmor.service +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - name: RUN TESTS if: inputs.plugin != 'rabbitmq_cli' diff --git a/.github/workflows/test-make.yaml b/.github/workflows/test-make.yaml index 60d61dad9fc4..af4f949e8435 100644 --- a/.github/workflows/test-make.yaml +++ b/.github/workflows/test-make.yaml @@ -3,7 +3,10 @@ on: push: branches: - main +<<<<<<< HEAD - v4.0.x +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) paths: - deps/** - scripts/** diff --git a/.github/workflows/test-management-ui-for-pr.yaml b/.github/workflows/test-management-ui-for-pr.yaml index 76c0181a14f2..9340dc81b60f 100644 --- a/.github/workflows/test-management-ui-for-pr.yaml +++ b/.github/workflows/test-management-ui-for-pr.yaml @@ -42,6 +42,7 @@ jobs: with: credentials_json: ${{ secrets.REMOTE_CACHE_CREDENTIALS_JSON }} +<<<<<<< HEAD - name: Configure Bazel run: | if [ -n "${{ secrets.REMOTE_CACHE_BUCKET_NAME }}" ]; then @@ -59,6 +60,12 @@ jobs: - name: Build & Load RabbitMQ OCI run: | bazelisk run packaging/docker-image:rabbitmq-amd64 +======= + - name: Build & Load RabbitMQ OCI + run: | + make package-generic-unix + make docker-image +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - name: Configure Docker Network run: | @@ -71,6 +78,7 @@ jobs: - name: Run short ui suites on a standalone rabbitmq server run: | +<<<<<<< HEAD RABBITMQ_DOCKER_IMAGE=bazel/packaging/docker-image:rabbitmq-amd64 \ ADDON_PROFILES=cluster ${SELENIUM_DIR}/run-suites.sh short-suite-management-ui mkdir -p /tmp/short-suite @@ -79,6 +87,13 @@ jobs: mv ${SELENIUM_DIR}/logs/* /tmp/short-suite/logs mkdir -p /tmp/short-suite/screens mv ${SELENIUM_DIR}/screens/* /tmp/short-suite/screens +======= + IMAGE_TAG=$(find PACKAGES/rabbitmq-server-generic-unix-*.tar.xz | awk -F 'PACKAGES/rabbitmq-server-generic-unix-|.tar.xz' '{print $2}') + RABBITMQ_DOCKER_IMAGE=pivotalrabbitmq/rabbitmq:$IMAGE_TAG \ + ${SELENIUM_DIR}/run-suites.sh short-suite-management-ui + mkdir -p /tmp/short-suite + mv /tmp/selenium/* /tmp/short-suite +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - name: Upload Test Artifacts if: always() diff --git a/.github/workflows/test-management-ui.yaml b/.github/workflows/test-management-ui.yaml index 53902913d8c0..d48b03f10604 100644 --- a/.github/workflows/test-management-ui.yaml +++ b/.github/workflows/test-management-ui.yaml @@ -57,6 +57,7 @@ jobs: with: credentials_json: ${{ secrets.REMOTE_CACHE_CREDENTIALS_JSON }} +<<<<<<< HEAD - name: Configure Bazel run: | if [ -n "${{ secrets.REMOTE_CACHE_BUCKET_NAME }}" ]; then @@ -74,6 +75,12 @@ jobs: - name: Build & Load RabbitMQ OCI run: | bazelisk run packaging/docker-image:rabbitmq-amd64 +======= + - name: Build & Load RabbitMQ OCI + run: | + make package-generic-unix + make docker-image +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - name: Configure Docker Network run: | @@ -86,6 +93,7 @@ jobs: - name: Run short ui suite on a 3-node rabbitmq cluster run: | +<<<<<<< HEAD RABBITMQ_DOCKER_IMAGE=bazel/packaging/docker-image:rabbitmq-amd64 \ ADDON_PROFILES=cluster ${SELENIUM_DIR}/run-suites.sh full-suite-management-ui mkdir -p /tmp/full-suite @@ -95,12 +103,24 @@ jobs: mkdir -p /tmp/full-suite/screens mv ${SELENIUM_DIR}/screens/* /tmp/full-suite/screens +======= + IMAGE_TAG=$(find PACKAGES/rabbitmq-server-generic-unix-*.tar.xz | awk -F 'PACKAGES/rabbitmq-server-generic-unix-|.tar.xz' '{print $2}') + RABBITMQ_DOCKER_IMAGE=pivotalrabbitmq/rabbitmq:$IMAGE_TAG \ + ${SELENIUM_DIR}/run-suites.sh short-suite-management-ui + mkdir -p /tmp/short-suite + mv /tmp/selenium/* /tmp/short-suite + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - name: Upload Test Artifacts if: always() uses: actions/upload-artifact@v4.3.2 with: name: test-artifacts-${{ matrix.browser }}-${{ matrix.erlang_version }} path: | +<<<<<<< HEAD +======= + /tmp/full-suite +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) /tmp/short-suite summary-selenium: diff --git a/.github/workflows/test-mixed-versions.yaml b/.github/workflows/test-mixed-versions.yaml index bf2ed9ae2fdb..858c183bb9ac 100644 --- a/.github/workflows/test-mixed-versions.yaml +++ b/.github/workflows/test-mixed-versions.yaml @@ -2,6 +2,10 @@ name: Test Mixed Version Clusters on: push: branches: +<<<<<<< HEAD +======= + - v4.0.x +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - v3.13.x - bump-otp-* - bump-elixir-* @@ -467,6 +471,27 @@ jobs: repo_cache_key: ${{ needs.check-workflow.outputs.repo_cache_key }} plugin: rabbitmq_auth_backend_http secrets: inherit +<<<<<<< HEAD +======= + test-rabbitmq_auth_backend_ldap-mixed: + needs: + - check-workflow + - test-rabbit-0-mixed + - test-rabbit-1-mixed + - test-rabbit-2-mixed + - test-rabbit-3-mixed + - test-rabbit-4-mixed + - test-rabbit-5-mixed + - test-rabbit-6-mixed + - test-rabbit-7-mixed + - test-rabbit-8-mixed + - test-rabbit-9-mixed + uses: ./.github/workflows/test-plugin-mixed.yaml + with: + repo_cache_key: ${{ needs.check-workflow.outputs.repo_cache_key }} + plugin: rabbitmq_auth_backend_ldap + secrets: inherit +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) test-rabbitmq_auth_backend_oauth2-mixed: needs: - check-workflow @@ -1130,6 +1155,10 @@ jobs: - test-rabbitmq_amqp1_0-mixed - test-rabbitmq_auth_backend_cache-mixed - test-rabbitmq_auth_backend_http-mixed +<<<<<<< HEAD +======= + - test-rabbitmq_auth_backend_ldap-mixed +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - test-rabbitmq_auth_backend_oauth2-mixed - test-rabbitmq_auth_mechanism_ssl-mixed - test-rabbitmq_aws-mixed diff --git a/.github/workflows/test-plugin-mixed.yaml b/.github/workflows/test-plugin-mixed.yaml index 416db6f0745f..425a6b28707c 100644 --- a/.github/workflows/test-plugin-mixed.yaml +++ b/.github/workflows/test-plugin-mixed.yaml @@ -29,10 +29,14 @@ jobs: - 26 metadata_store: - mnesia +<<<<<<< HEAD # Khepri is currently skipped because Khepri is an unstable feature: we don't guarantee upgrability. # Mixed-version tests currently fail with Khepri because of a new machine version introduced in # Khepri v0.14.0. # - khepri +======= + - khepri +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) include: - erlang_version: 26 elixir_version: 1.17 diff --git a/.github/workflows/test-plugin.yaml b/.github/workflows/test-plugin.yaml index 3c163ba059a3..02863aeb8bb3 100644 --- a/.github/workflows/test-plugin.yaml +++ b/.github/workflows/test-plugin.yaml @@ -101,6 +101,10 @@ jobs: sudo systemctl is-active --quiet apparmor.service && sudo systemctl stop apparmor.service sudo systemctl disable apparmor.service +<<<<<<< HEAD +======= + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) cat << EOF >> user.bazelrc build --strategy=TestRunner=local EOF diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6c3c003670ef..c7d4e3522480 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -2,6 +2,10 @@ name: Test on: push: branches: +<<<<<<< HEAD +======= + - v4.0.x +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - v3.13.x - v3.12.x - v3.11.x @@ -408,6 +412,27 @@ jobs: repo_cache_key: ${{ needs.check-workflow.outputs.repo_cache_key }} plugin: rabbitmq_auth_backend_http secrets: inherit +<<<<<<< HEAD +======= + test-rabbitmq_auth_backend_ldap: + needs: + - check-workflow + - test-rabbit-0 + - test-rabbit-1 + - test-rabbit-2 + - test-rabbit-3 + - test-rabbit-4 + - test-rabbit-5 + - test-rabbit-6 + - test-rabbit-7 + - test-rabbit-8 + - test-rabbit-9 + uses: ./.github/workflows/test-plugin.yaml + with: + repo_cache_key: ${{ needs.check-workflow.outputs.repo_cache_key }} + plugin: rabbitmq_auth_backend_ldap + secrets: inherit +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) test-rabbitmq_auth_backend_oauth2: needs: - check-workflow @@ -1071,6 +1096,10 @@ jobs: - test-rabbitmq_amqp1_0 - test-rabbitmq_auth_backend_cache - test-rabbitmq_auth_backend_http +<<<<<<< HEAD +======= + - test-rabbitmq_auth_backend_ldap +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - test-rabbitmq_auth_backend_oauth2 - test-rabbitmq_auth_mechanism_ssl - test-rabbitmq_aws diff --git a/COMMUNITY_SUPPORT.md b/COMMUNITY_SUPPORT.md index 492a057bfcd5..caf0adcfc441 100644 --- a/COMMUNITY_SUPPORT.md +++ b/COMMUNITY_SUPPORT.md @@ -6,7 +6,17 @@ This document explains who is eligible for community support for open source Rab ### What is Community Support? Community support is defined as all questions, root cause analysis requests, issue reports, and other interactions the RabbitMQ core team has with open source RabbitMQ users on GitHub +<<<<<<< HEAD and our community forums. +======= +and our community forums. + +### How is Community Support Related to Patch Releases? + +Being covered by community support for a release series also means that patch releases, general and security-related ones, +are produced regularly and are available publicly. Patch releases, even if produced, **will not be made available to non-paying users** for series out of community support, with potential +exception for very high severity CVEs. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ### What is Broadcom's Obligation to Reply to Messages or Issues Reported? diff --git a/MODULE.bazel b/MODULE.bazel index 6a0a57e07196..124a56ab7c7b 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -284,8 +284,13 @@ erlang_package.hex_package( erlang_package.hex_package( name = "redbug", build_file = "@rabbitmq-server//bazel:BUILD.redbug", +<<<<<<< HEAD sha256 = "55d6d59697481ca4cc5ad54749aa6d78299aa8a8096027e7ae1f59db9dc94c78", version = "2.1.0", +======= + sha256 = "3624feb7a4b78fd9ae0e66cc3158fe7422770ad6987a1ebf8df4d3303b1c4b0c", + version = "2.0.7", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ) erlang_package.hex_package( @@ -428,7 +433,11 @@ secondary_umbrella = use_extension( use_repo( secondary_umbrella, +<<<<<<< HEAD "rabbitmq-server-generic-unix-3.13", +======= + "rabbitmq-server-generic-unix-4.0", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ) hex = use_extension( diff --git a/Makefile b/Makefile index 2e4fe88c9f7a..8fd06eff4528 100644 --- a/Makefile +++ b/Makefile @@ -599,6 +599,10 @@ TIER1_PLUGINS := \ rabbitmq_amqp1_0 \ rabbitmq_auth_backend_cache \ rabbitmq_auth_backend_http \ +<<<<<<< HEAD +======= + rabbitmq_auth_backend_ldap \ +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbitmq_auth_backend_oauth2 \ rabbitmq_auth_mechanism_ssl \ rabbitmq_aws \ diff --git a/bazel/BUILD.horus b/bazel/BUILD.horus index 0f7e2369acbb..aacba50d84b5 100644 --- a/bazel/BUILD.horus +++ b/bazel/BUILD.horus @@ -24,7 +24,11 @@ erlang_bytecode( srcs = [ "src/horus.erl", "src/horus_cover.erl", +<<<<<<< HEAD "src/horus_utils.erl" +======= + "src/horus_utils.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ], hdrs = [":public_and_private_hdrs"], app_name = "horus", @@ -50,9 +54,14 @@ filegroup( filegroup( name = "private_hdrs", srcs = [ +<<<<<<< HEAD "src/horus_cover.hrl", "src/horus_error.hrl", "src/horus_fun.hrl" +======= + "src/horus_error.hrl", + "src/horus_fun.hrl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ], ) diff --git a/bazel/BUILD.redbug b/bazel/BUILD.redbug index 06c43294b29e..4f096add7fcb 100644 --- a/bazel/BUILD.redbug +++ b/bazel/BUILD.redbug @@ -49,12 +49,16 @@ filegroup( ], ) +<<<<<<< HEAD filegroup( name = "private_hdrs", srcs = [ "src/redbug_dbg.hrl", ], ) +======= +filegroup(name = "private_hdrs") +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) filegroup(name = "public_hdrs") diff --git a/bazel/bzlmod/secondary_umbrella.bzl b/bazel/bzlmod/secondary_umbrella.bzl index 613961d5a616..5cdf1edea626 100644 --- a/bazel/bzlmod/secondary_umbrella.bzl +++ b/bazel/bzlmod/secondary_umbrella.bzl @@ -25,6 +25,7 @@ EOF def secondary_umbrella(): http_archive( +<<<<<<< HEAD name = "rabbitmq-server-generic-unix-3.13", build_file = "@//:BUILD.package_generic_unix", patch_cmds = [ADD_PLUGINS_DIR_BUILD_FILE], @@ -32,5 +33,14 @@ def secondary_umbrella(): # This file is produced just in time by the test-mixed-versions.yaml GitHub Actions workflow. urls = [ "https://rabbitmq-github-actions.s3.eu-west-1.amazonaws.com/secondary-umbrellas/26.1/package-generic-unix-for-mixed-version-testing-v3.13.7.tar.xz", +======= + name = "rabbitmq-server-generic-unix-4.0", + build_file = "@//:BUILD.package_generic_unix", + patch_cmds = [ADD_PLUGINS_DIR_BUILD_FILE], + strip_prefix = "rabbitmq_server-4.0.0", + # This file is produced just in time by the test-mixed-versions.yaml GitHub Actions workflow. + urls = [ + "https://rabbitmq-github-actions.s3.eu-west-1.amazonaws.com/secondary-umbrellas/26.1/package-generic-unix-for-mixed-version-testing-v4.0.2.tar.xz", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ], ) diff --git a/deps/amqp10_client/BUILD.bazel b/deps/amqp10_client/BUILD.bazel index df8b879adae1..e9919f481ac0 100644 --- a/deps/amqp10_client/BUILD.bazel +++ b/deps/amqp10_client/BUILD.bazel @@ -76,6 +76,10 @@ rabbitmq_app( priv = [":priv"], deps = [ "//deps/amqp10_common:erlang_app", +<<<<<<< HEAD +======= + "//deps/rabbit_common:erlang_app", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "@credentials_obfuscation//:erlang_app", ], ) diff --git a/deps/amqp10_client/app.bzl b/deps/amqp10_client/app.bzl index 8fcdad73cf9d..95619a349d26 100644 --- a/deps/amqp10_client/app.bzl +++ b/deps/amqp10_client/app.bzl @@ -113,7 +113,10 @@ def test_suite_beam_files(name = "test_suite_beam_files"): testonly = True, srcs = ["test/system_SUITE.erl"], outs = ["test/system_SUITE.beam"], +<<<<<<< HEAD hdrs = ["src/amqp10_client.hrl"], +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) app_name = "amqp10_client", erlc_opts = "//:test_erlc_opts", deps = ["//deps/amqp10_common:erlang_app"], diff --git a/deps/amqp10_client/src/amqp10_client.erl b/deps/amqp10_client/src/amqp10_client.erl index c5ebc7ba123f..c84418a89969 100644 --- a/deps/amqp10_client/src/amqp10_client.erl +++ b/deps/amqp10_client/src/amqp10_client.erl @@ -144,6 +144,11 @@ begin_session_sync(Connection, Timeout) when is_pid(Connection) -> receive {amqp10_event, {session, Session, begun}} -> {ok, Session}; +<<<<<<< HEAD +======= + {amqp10_event, {session, Session, {begun, #'v1_0.begin'{}}}} -> + {ok, Session}; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {amqp10_event, {session, Session, {ended, Err}}} -> {error, Err} after Timeout -> session_timeout @@ -186,6 +191,11 @@ attach_sender_link_sync(Session, Name, Target, SettleMode, Durability) -> receive {amqp10_event, {link, Ref, attached}} -> {ok, Ref}; +<<<<<<< HEAD +======= + {amqp10_event, {link, Ref, {attached, #'v1_0.attach'{}}}} -> + {ok, Ref}; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {amqp10_event, {link, Ref, {detached, Err}}} -> {error, Err} after ?TIMEOUT -> link_timeout diff --git a/deps/amqp10_client/src/amqp10_client_connection.erl b/deps/amqp10_client/src/amqp10_client_connection.erl index df0548aa9ef1..7e9eb4d61e46 100644 --- a/deps/amqp10_client/src/amqp10_client_connection.erl +++ b/deps/amqp10_client/src/amqp10_client_connection.erl @@ -63,6 +63,10 @@ notify => pid() | none, % the pid to send connection events to notify_when_opened => pid() | none, notify_when_closed => pid() | none, +<<<<<<< HEAD +======= + notify_with_performative => boolean(), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% incoming maximum frame size set by our client application max_frame_size => pos_integer(), % TODO: constrain to large than 512 %% outgoing maximum frame size set by AMQP peer in OPEN performative @@ -253,7 +257,11 @@ hdr_sent({call, From}, begin_session, {keep_state, State1}. open_sent(_EvtType, #'v1_0.open'{max_frame_size = MaybeMaxFrameSize, +<<<<<<< HEAD idle_time_out = Timeout}, +======= + idle_time_out = Timeout} = Open, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) #state{pending_session_reqs = PendingSessionReqs, config = Config} = State0) -> State = case Timeout of @@ -278,7 +286,11 @@ open_sent(_EvtType, #'v1_0.open'{max_frame_size = MaybeMaxFrameSize, _ = gen_statem:reply(From, Ret), S2 end, State1, PendingSessionReqs), +<<<<<<< HEAD ok = notify_opened(Config), +======= + ok = notify_opened(Config, Open), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {next_state, opened, State2#state{pending_session_reqs = []}}; open_sent({call, From}, begin_session, #state{pending_session_reqs = PendingSessionReqs} = State) -> @@ -292,19 +304,32 @@ opened(_EvtType, heartbeat, State = #state{idle_time_out = T}) -> ok = send_heartbeat(State), {ok, Tmr} = start_heartbeat_timer(T), {keep_state, State#state{heartbeat_timer = Tmr}}; +<<<<<<< HEAD opened(_EvtType, {close, Reason}, State = #state{config = Config}) -> %% We send the first close frame and wait for the reply. %% TODO: stop all sessions writing %% We could still accept incoming frames (See: 2.4.6) ok = notify_closed(Config, Reason), +======= +opened(_EvtType, {close, Reason}, State) -> + %% We send the first close frame and wait for the reply. + %% TODO: stop all sessions writing + %% We could still accept incoming frames (See: 2.4.6) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case send_close(State, Reason) of ok -> {next_state, close_sent, State}; {error, closed} -> {stop, normal, State}; Error -> {stop, Error, State} end; +<<<<<<< HEAD opened(_EvtType, #'v1_0.close'{error = Error}, State = #state{config = Config}) -> %% We receive the first close frame, reply and terminate. ok = notify_closed(Config, translate_err(Error)), +======= +opened(_EvtType, #'v1_0.close'{} = Close, State = #state{config = Config}) -> + %% We receive the first close frame, reply and terminate. + ok = notify_closed(Config, Close), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) _ = send_close(State, none), {stop, normal, State}; opened({call, From}, begin_session, State) -> @@ -329,7 +354,12 @@ close_sent(_EvtType, {'DOWN', _Ref, process, ReaderPid, _}, #state{reader = ReaderPid} = State) -> %% if the reader exits we probably wont receive a close frame {stop, normal, State}; +<<<<<<< HEAD close_sent(_EvtType, #'v1_0.close'{}, State) -> +======= +close_sent(_EvtType, #'v1_0.close'{} = Close, State = #state{config = Config}) -> + ok = notify_closed(Config, Close), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% TODO: we should probably set up a timer before this to ensure %% we close down event if no reply is received {stop, normal, State}. @@ -489,6 +519,7 @@ socket_shutdown({tcp, Socket}, How) -> socket_shutdown({ssl, Socket}, How) -> ssl:shutdown(Socket, How). +<<<<<<< HEAD notify_opened(#{notify_when_opened := none}) -> ok; notify_opened(#{notify_when_opened := Pid}) when is_pid(Pid) -> @@ -498,16 +529,54 @@ notify_opened(#{notify := Pid}) when is_pid(Pid) -> Pid ! amqp10_event(opened), ok; notify_opened(_) -> +======= +notify_opened(#{notify_when_opened := none}, _) -> + ok; +notify_opened(#{notify_when_opened := Pid} = Config, Perf) + when is_pid(Pid) -> + notify_opened0(Config, Pid, Perf); +notify_opened(#{notify := Pid} = Config, Perf) + when is_pid(Pid) -> + notify_opened0(Config, Pid, Perf); +notify_opened(_, _) -> + ok. + +notify_opened0(Config, Pid, Perf) -> + Evt = case Config of + #{notify_with_performative := true} -> + {opened, Perf}; + _ -> + opened + end, + Pid ! amqp10_event(Evt), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ok. notify_closed(#{notify_when_closed := none}, _Reason) -> ok; notify_closed(#{notify := none}, _Reason) -> ok; +<<<<<<< HEAD notify_closed(#{notify_when_closed := Pid}, Reason) when is_pid(Pid) -> Pid ! amqp10_event({closed, Reason}), ok; notify_closed(#{notify := Pid}, Reason) when is_pid(Pid) -> +======= +notify_closed(#{notify_when_closed := Pid} = Config, Reason) + when is_pid(Pid) -> + notify_closed0(Config, Pid, Reason); +notify_closed(#{notify := Pid} = Config, Reason) + when is_pid(Pid) -> + notify_closed0(Config, Pid, Reason). + +notify_closed0(#{notify_with_performative := true}, Pid, Perf = #'v1_0.close'{}) -> + Pid ! amqp10_event({closed, Perf}), + ok; +notify_closed0(_, Pid, #'v1_0.close'{error = Error}) -> + Pid ! amqp10_event({closed, translate_err(Error)}), + ok; +notify_closed0(_, Pid, Reason) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Pid ! amqp10_event({closed, Reason}), ok. diff --git a/deps/amqp10_client/src/amqp10_client_frame_reader.erl b/deps/amqp10_client/src/amqp10_client_frame_reader.erl index 05d8823999b1..e3417362dee3 100644 --- a/deps/amqp10_client/src/amqp10_client_frame_reader.erl +++ b/deps/amqp10_client/src/amqp10_client_frame_reader.erl @@ -105,7 +105,12 @@ init([Sup, ConnConfig]) when is_map(ConnConfig) -> {ok, expecting_connection_pid, State} end. +<<<<<<< HEAD connect(Address, Port, #{tls_opts := {secure_port, Opts}}) -> +======= +connect(Address, Port, #{tls_opts := {secure_port, Opts0}}) -> + Opts = rabbit_ssl_options:fix_client(Opts0), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case ssl:connect(Address, Port, ?RABBIT_TCP_OPTS ++ Opts) of {ok, S} -> {ssl, S}; diff --git a/deps/amqp10_client/src/amqp10_client_session.erl b/deps/amqp10_client/src/amqp10_client_session.erl index e55775539206..c60ddae9efce 100644 --- a/deps/amqp10_client/src/amqp10_client_session.erl +++ b/deps/amqp10_client/src/amqp10_client_session.erl @@ -254,7 +254,11 @@ unmapped({call, From}, {attach, Attach}, begin_sent(cast, #'v1_0.begin'{remote_channel = {ushort, RemoteChannel}, next_outgoing_id = {uint, NOI}, incoming_window = {uint, InWindow}, +<<<<<<< HEAD outgoing_window = {uint, OutWindow}}, +======= + outgoing_window = {uint, OutWindow}} = Begin, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) #state{early_attach_requests = EARs} = State) -> State1 = State#state{remote_channel = RemoteChannel}, @@ -264,7 +268,11 @@ begin_sent(cast, #'v1_0.begin'{remote_channel = {ushort, RemoteChannel}, S2 end, State1, EARs), +<<<<<<< HEAD ok = notify_session_begun(State2), +======= + ok = notify_session_begun(Begin, State2), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {next_state, mapped, State2#state{early_attach_requests = [], next_incoming_id = NOI, @@ -291,18 +299,30 @@ mapped(cast, {flow_session, Flow0 = #'v1_0.flow'{incoming_window = {uint, Incomi outgoing_window = ?UINT_OUTGOING_WINDOW}, ok = send(Flow, State), {keep_state, State#state{incoming_window = IncomingWindow}}; +<<<<<<< HEAD mapped(cast, #'v1_0.end'{error = Err}, State) -> %% We receive the first end frame, reply and terminate. _ = send_end(State), % TODO: send notifications for links? Reason = reason(Err), ok = notify_session_ended(State, Reason), +======= +mapped(cast, #'v1_0.end'{} = End, State) -> + %% We receive the first end frame, reply and terminate. + _ = send_end(State), + % TODO: send notifications for links? + ok = notify_session_ended(End, State), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {stop, normal, State}; mapped(cast, #'v1_0.attach'{name = {utf8, Name}, initial_delivery_count = IDC, handle = {uint, InHandle}, role = PeerRoleBool, +<<<<<<< HEAD max_message_size = MaybeMaxMessageSize}, +======= + max_message_size = MaybeMaxMessageSize} = Attach, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) #state{links = Links, link_index = LinkIndex, link_handle_index = LHI} = State0) -> @@ -311,7 +331,11 @@ mapped(cast, #'v1_0.attach'{name = {utf8, Name}, LinkIndexKey = {OurRole, Name}, #{LinkIndexKey := OutHandle} = LinkIndex, #{OutHandle := Link0} = Links, +<<<<<<< HEAD ok = notify_link_attached(Link0), +======= + ok = notify_link_attached(Link0, Attach, State0), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {DeliveryCount, MaxMessageSize} = case Link0 of @@ -334,6 +358,7 @@ mapped(cast, #'v1_0.attach'{name = {utf8, Name}, link_index = maps:remove(LinkIndexKey, LinkIndex), link_handle_index = LHI#{InHandle => OutHandle}}, {keep_state, State}; +<<<<<<< HEAD mapped(cast, #'v1_0.detach'{handle = {uint, InHandle}, error = Err}, #state{links = Links, link_handle_index = LHI} = State0) -> @@ -341,6 +366,13 @@ mapped(cast, #'v1_0.detach'{handle = {uint, InHandle}, fun (#link{output_handle = OutHandle} = Link, State) -> Reason = reason(Err), ok = notify_link_detached(Link, Reason), +======= +mapped(cast, #'v1_0.detach'{handle = {uint, InHandle}} = Detach, + #state{links = Links, link_handle_index = LHI} = State0) -> + with_link(InHandle, State0, + fun (#link{output_handle = OutHandle} = Link, State) -> + ok = notify_link_detached(Link, Detach, State), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {keep_state, State#state{links = maps:remove(OutHandle, Links), link_handle_index = maps:remove(InHandle, LHI)}} @@ -547,9 +579,14 @@ mapped(_EvtType, Msg, _State) -> [Msg, 10]), keep_state_and_data. +<<<<<<< HEAD end_sent(_EvtType, #'v1_0.end'{error = Err}, State) -> Reason = reason(Err), ok = notify_session_ended(State, Reason), +======= +end_sent(_EvtType, #'v1_0.end'{} = End, State) -> + ok = notify_session_ended(End, State), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {stop, normal, State}; end_sent(_EvtType, _Frame, _State) -> % just drop frames here @@ -732,6 +769,7 @@ translate_terminus_durability(configuration) -> 1; translate_terminus_durability(unsettled_state) -> 2. translate_filters(Filters) +<<<<<<< HEAD when is_map(Filters) andalso map_size(Filters) == 0 -> undefined; @@ -741,6 +779,15 @@ translate_filters(Filters) maps:fold( fun (<<"apache.org:legacy-amqp-headers-binding:map">> = K, V, Acc) when is_map(V) -> +======= + when map_size(Filters) =:= 0 -> + undefined; +translate_filters(Filters) -> + {map, + maps:fold( + fun + (<<"apache.org:legacy-amqp-headers-binding:map">> = K, V, Acc) when is_map(V) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% special case conversion Key = sym(K), [{Key, {described, Key, translate_legacy_amqp_headers_binding(V)}} | Acc]; @@ -986,10 +1033,31 @@ maybe_notify_link_credit(#link{role = sender, maybe_notify_link_credit(_Old, _New) -> ok. +<<<<<<< HEAD notify_link_attached(Link) -> notify_link(Link, attached). notify_link_detached(Link, Reason) -> +======= +notify_link_attached(Link, Perf, #state{connection_config = Cfg}) -> + What = case Cfg of + #{notify_with_performative := true} -> + {attached, Perf}; + _ -> + attached + end, + notify_link(Link, What). + +notify_link_detached(Link, + Perf = #'v1_0.detach'{error = Err}, + #state{connection_config = Cfg}) -> + Reason = case Cfg of + #{notify_with_performative := true} -> + Perf; + _ -> + reason(Err) + end, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) notify_link(Link, {detached, Reason}). notify_link(#link{notify = Pid, ref = Ref}, What) -> @@ -997,11 +1065,34 @@ notify_link(#link{notify = Pid, ref = Ref}, What) -> Pid ! Evt, ok. +<<<<<<< HEAD notify_session_begun(#state{notify = Pid}) -> Pid ! amqp10_session_event(begun), ok. notify_session_ended(#state{notify = Pid}, Reason) -> +======= +notify_session_begun(Perf, #state{notify = Pid, + connection_config = Cfg}) -> + Evt = case Cfg of + #{notify_with_performative := true} -> + {begun, Perf}; + _ -> + begun + end, + Pid ! amqp10_session_event(Evt), + ok. + +notify_session_ended(Perf = #'v1_0.end'{error = Err}, + #state{notify = Pid, + connection_config = Cfg}) -> + Reason = case Cfg of + #{notify_with_performative := true} -> + Perf; + _ -> + reason(Err) + end, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Pid ! amqp10_session_event({ended, Reason}), ok. @@ -1166,11 +1257,16 @@ make_link_ref(Role, Session, Handle) -> translate_message_annotations(MA) when map_size(MA) > 0 -> {map, maps:fold(fun(K, V, Acc) -> +<<<<<<< HEAD [{sym(K), wrap_map_value(V)} | Acc] +======= + [{sym(K), amqp10_client_types:infer(V)} | Acc] +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end, [], MA)}; translate_message_annotations(_MA) -> undefined. +<<<<<<< HEAD wrap_map_value(true) -> {boolean, true}; wrap_map_value(false) -> @@ -1193,6 +1289,8 @@ wrap_map_value(TaggedValue) when is_atom(element(1, TaggedValue)) -> utf8(V) -> amqp10_client_types:utf8(V). +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) sym(B) when is_binary(B) -> {symbol, B}; sym(B) when is_list(B) -> {symbol, list_to_binary(B)}; sym(B) when is_atom(B) -> {symbol, atom_to_binary(B, utf8)}. diff --git a/deps/amqp10_client/src/amqp10_client_types.erl b/deps/amqp10_client/src/amqp10_client_types.erl index 5758012e9335..80f6eeadf835 100644 --- a/deps/amqp10_client/src/amqp10_client_types.erl +++ b/deps/amqp10_client/src/amqp10_client_types.erl @@ -9,6 +9,10 @@ -include_lib("amqp10_common/include/amqp10_framing.hrl"). -export([unpack/1, +<<<<<<< HEAD +======= + infer/1, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) utf8/1, uint/1, make_properties/1]). @@ -73,6 +77,7 @@ properties/0]). +<<<<<<< HEAD unpack({_, Value}) -> Value; unpack(Value) -> Value. @@ -80,6 +85,34 @@ utf8(S) when is_list(S) -> {utf8, list_to_binary(S)}; utf8(B) when is_binary(B) -> {utf8, B}. uint(N) -> {uint, N}. +======= +unpack({_, Value}) -> + Value; +unpack(Value) -> + Value. + +infer(V) when is_integer(V) -> + {long, V}; +infer(V) when is_number(V) -> + %% AMQP double and Erlang float are both 64-bit. + {double, V}; +infer(V) when is_boolean(V) -> + {boolean, V}; +infer(V) when is_atom(V) -> + {utf8, atom_to_binary(V, utf8)}; +infer(TaggedValue) when is_atom(element(1, TaggedValue)) -> + TaggedValue; +infer(V) -> + utf8(V). + +utf8(V) when is_binary(V) -> + {utf8, V}; +utf8(V) when is_list(V) -> + {utf8, unicode:characters_to_binary(V)}. + +uint(N) -> + {uint, N}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) make_properties(#{properties := Props}) when map_size(Props) > 0 -> diff --git a/deps/amqp10_client/src/amqp10_msg.erl b/deps/amqp10_client/src/amqp10_msg.erl index 673617acc6a0..ea1b191596b3 100644 --- a/deps/amqp10_client/src/amqp10_msg.erl +++ b/deps/amqp10_client/src/amqp10_msg.erl @@ -38,6 +38,11 @@ set_message_annotations/2 ]). +<<<<<<< HEAD +======= +-import(amqp10_client_types, [utf8/1]). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -include_lib("amqp10_common/include/amqp10_framing.hrl"). -type opt(T) :: T | undefined. @@ -380,13 +385,21 @@ set_application_properties( Props0, #amqp10_msg{application_properties = #'v1_0.application_properties'{content = APs0}} = Msg) -> Props = maps:fold(fun (K, V, S) -> +<<<<<<< HEAD S#{utf8(K) => wrap_ap_value(V)} +======= + S#{utf8(K) => amqp10_client_types:infer(V)} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end, maps:from_list(APs0), Props0), APs = #'v1_0.application_properties'{content = maps:to_list(Props)}, Msg#amqp10_msg{application_properties = APs}. -spec set_delivery_annotations(#{binary() => binary() | integer() | string()}, +<<<<<<< HEAD amqp10_msg()) -> amqp10_msg(). +======= + amqp10_msg()) -> amqp10_msg(). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) set_delivery_annotations(Props, #amqp10_msg{delivery_annotations = undefined} = Msg) -> @@ -394,6 +407,7 @@ set_delivery_annotations(Props, set_delivery_annotations(Props, Msg#amqp10_msg{delivery_annotations = Anns}); set_delivery_annotations( +<<<<<<< HEAD Props0, #amqp10_msg{delivery_annotations = #'v1_0.delivery_annotations'{content = Anns0}} = Msg) -> Anns = maps:fold(fun (K, V, S) -> @@ -401,10 +415,20 @@ set_delivery_annotations( end, maps:from_list(Anns0), Props0), Anns1 = #'v1_0.delivery_annotations'{content = maps:to_list(Anns)}, Msg#amqp10_msg{delivery_annotations = Anns1}. +======= + Props, #amqp10_msg{delivery_annotations = + #'v1_0.delivery_annotations'{content = Anns0}} = Msg) -> + Anns1 = maps:fold(fun (K, V, S) -> + S#{sym(K) => amqp10_client_types:infer(V)} + end, maps:from_list(Anns0), Props), + Anns = #'v1_0.delivery_annotations'{content = maps:to_list(Anns1)}, + Msg#amqp10_msg{delivery_annotations = Anns}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec set_message_annotations(#{binary() => binary() | number() | string() | tuple()}, amqp10_msg()) -> amqp10_msg(). set_message_annotations(Props, +<<<<<<< HEAD #amqp10_msg{message_annotations = undefined} = Msg) -> Anns = #'v1_0.message_annotations'{content = []}, @@ -439,6 +463,21 @@ wrap_ap_value(V) when is_number(V) -> {double, V}; wrap_ap_value(TaggedValue) when is_tuple(TaggedValue) -> TaggedValue. +======= + #amqp10_msg{message_annotations = undefined} = + Msg) -> + Anns = #'v1_0.message_annotations'{content = []}, + set_message_annotations(Props, + Msg#amqp10_msg{message_annotations = Anns}); +set_message_annotations( + Props, #amqp10_msg{message_annotations = + #'v1_0.message_annotations'{content = Anns0}} = Msg) -> + Anns1 = maps:fold(fun (K, V, S) -> + S#{sym(K) => amqp10_client_types:infer(V)} + end, maps:from_list(Anns0), Props), + Anns = #'v1_0.message_annotations'{content = maps:to_list(Anns1)}, + Msg#amqp10_msg{message_annotations = Anns}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% LOCAL header_value(durable, undefined) -> false; @@ -474,7 +513,10 @@ parse_from_amqp(#'v1_0.footer'{} = Header, AmqpMsg) -> AmqpMsg#amqp10_msg{footer = Header}. unpack(V) -> amqp10_client_types:unpack(V). +<<<<<<< HEAD utf8(V) -> amqp10_client_types:utf8(V). +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) sym(B) when is_list(B) -> {symbol, list_to_binary(B)}; sym(B) when is_binary(B) -> {symbol, B}. uint(B) -> {uint, B}. diff --git a/deps/amqp10_client/test/system_SUITE.erl b/deps/amqp10_client/test/system_SUITE.erl index 7a64425c7583..578318b5742c 100644 --- a/deps/amqp10_client/test/system_SUITE.erl +++ b/deps/amqp10_client/test/system_SUITE.erl @@ -12,10 +12,17 @@ -include_lib("amqp10_common/include/amqp10_framing.hrl"). +<<<<<<< HEAD -include("src/amqp10_client.hrl"). -compile([export_all, nowarn_export_all]). +======= +-compile([export_all, nowarn_export_all]). + +-define(TIMEOUT, 30000). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) suite() -> [{timetrap, {minutes, 4}}]. @@ -30,7 +37,11 @@ all() -> groups() -> [ +<<<<<<< HEAD {rabbitmq, [], shared()}, +======= + {rabbitmq, [], shared() ++ [notify_with_performative]}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {activemq, [], shared()}, {rabbitmq_strict, [], [ basic_roundtrip_tls, @@ -184,7 +195,11 @@ open_close_connection(Config) -> {ok, Connection2} = amqp10_client:open_connection(OpnConf), receive {amqp10_event, {connection, Connection2, opened}} -> ok +<<<<<<< HEAD after 5000 -> exit(connection_timeout) +======= + after ?TIMEOUT -> exit(connection_timeout) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end, ok = amqp10_client:close_connection(Connection2), ok = amqp10_client:close_connection(Connection). @@ -201,7 +216,11 @@ open_connection_plain_sasl(Config) -> {ok, Connection} = amqp10_client:open_connection(OpnConf), receive {amqp10_event, {connection, Connection, opened}} -> ok +<<<<<<< HEAD after 5000 -> exit(connection_timeout) +======= + after ?TIMEOUT -> exit(connection_timeout) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end, ok = amqp10_client:close_connection(Connection). @@ -225,7 +244,11 @@ open_connection_plain_sasl_parse_uri(Config) -> {ok, Connection} = amqp10_client:open_connection(OpnConf), receive {amqp10_event, {connection, Connection, opened}} -> ok +<<<<<<< HEAD after 5000 -> exit(connection_timeout) +======= + after ?TIMEOUT -> exit(connection_timeout) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end, ok = amqp10_client:close_connection(Connection). @@ -245,7 +268,11 @@ open_connection_plain_sasl_failure(Config) -> % some implementation may simply close the tcp_connection {amqp10_event, {connection, Connection, {closed, shutdown}}} -> ok +<<<<<<< HEAD after 5000 -> +======= + after ?TIMEOUT -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ct:pal("Connection process is alive? = ~tp~n", [erlang:is_process_alive(Connection)]), exit(connection_timeout) @@ -458,6 +485,55 @@ transfer_id_vs_delivery_id(Config) -> ?assertEqual(serial_number:add(amqp10_msg:delivery_id(RcvMsg1), 1), amqp10_msg:delivery_id(RcvMsg2)). +<<<<<<< HEAD +======= +notify_with_performative(Config) -> + Hostname = ?config(rmq_hostname, Config), + Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp), + + OpenConf = #{?FUNCTION_NAME => true, + address => Hostname, + port => Port, + sasl => anon}, + + {ok, Connection} = amqp10_client:open_connection(OpenConf), + receive {amqp10_event, {connection, Connection, {opened, #'v1_0.open'{}}}} -> ok + after ?TIMEOUT -> ct:fail({missing_event, ?LINE}) + end, + + {ok, Session1} = amqp10_client:begin_session(Connection), + receive {amqp10_event, {session, Session1, {begun, #'v1_0.begin'{}}}} -> ok + after ?TIMEOUT -> ct:fail({missing_event, ?LINE}) + end, + + {ok, Sender1} = amqp10_client:attach_sender_link(Session1, <<"sender 1">>, <<"/exchanges/amq.fanout">>), + receive {amqp10_event, {link, Sender1, {attached, #'v1_0.attach'{}}}} -> ok + after ?TIMEOUT -> ct:fail({missing_event, ?LINE}) + end, + + ok = amqp10_client:detach_link(Sender1), + receive {amqp10_event, {link, Sender1, {detached, #'v1_0.detach'{}}}} -> ok + after ?TIMEOUT -> ct:fail({missing_event, ?LINE}) + end, + + ok = amqp10_client:end_session(Session1), + receive {amqp10_event, {session, Session1, {ended, #'v1_0.end'{}}}} -> ok + after ?TIMEOUT -> ct:fail({missing_event, ?LINE}) + end, + + %% Test that the amqp10_client:*_sync functions work. + {ok, Session2} = amqp10_client:begin_session_sync(Connection), + {ok, Sender2} = amqp10_client:attach_sender_link_sync(Session2, <<"sender 2">>, <<"/exchanges/amq.fanout">>), + ok = amqp10_client:detach_link(Sender2), + ok = amqp10_client:end_session(Session2), + flush(), + + ok = amqp10_client:close_connection(Connection), + receive {amqp10_event, {connection, Connection, {closed, #'v1_0.close'{}}}} -> ok + after ?TIMEOUT -> ct:fail({missing_event, ?LINE}) + end. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) % a message is sent before the link attach is guaranteed to % have completed and link credit granted % also queue a link detached immediately after transfer @@ -554,13 +630,21 @@ subscribe(Config) -> [begin receive {amqp10_msg, Receiver, Msg} -> ok = amqp10_client:accept_msg(Receiver, Msg) +<<<<<<< HEAD after 2000 -> ct:fail(timeout) +======= + after ?TIMEOUT -> ct:fail(timeout) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end end || _ <- lists:seq(1, 10)], ok = assert_no_message(Receiver), receive {amqp10_event, {link, Receiver, credit_exhausted}} -> ok +<<<<<<< HEAD after 5000 -> flush(), +======= + after ?TIMEOUT -> flush(), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) exit(credit_exhausted_assert) end, @@ -808,7 +892,11 @@ multi_transfer_without_delivery_id(Config) -> receive {amqp10_msg, Receiver, _InMsg} -> ok +<<<<<<< HEAD after 2000 -> +======= + after ?TIMEOUT -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) exit(delivery_timeout) end, @@ -832,8 +920,15 @@ incoming_heartbeat(Config) -> Hostname = ?config(mock_host, Config), Port = ?config(mock_port, Config), OpenStep = fun({0 = Ch, #'v1_0.open'{}, _Pay}) -> +<<<<<<< HEAD {Ch, [#'v1_0.open'{container_id = {utf8, <<"mock">>}, idle_time_out = {uint, 0}}]} +======= + {Ch, [#'v1_0.open'{ + container_id = {utf8, <<"mock">>}, + %% The server doesn't expect any heartbeats from us (client). + idle_time_out = {uint, 0}}]} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end, CloseStep = fun({0 = Ch, #'v1_0.close'{error = _TODO}, _Pay}) -> @@ -847,6 +942,7 @@ incoming_heartbeat(Config) -> MockRef = monitor(process, MockPid), ok = mock_server:set_steps(Mock, Steps), CConf = #{address => Hostname, port => Port, sasl => ?config(sasl, Config), +<<<<<<< HEAD idle_time_out => 1000, notify => self()}, {ok, Connection} = amqp10_client:open_connection(CConf), receive @@ -856,11 +952,31 @@ incoming_heartbeat(Config) -> when Connection0 =:= Connection -> ok after 5000 -> +======= + %% If the server does not send any traffic to us (client), we will expect + %% our client to close the connection after 1 second because + %% "the value in idle-time-out SHOULD be half the peer's actual timeout threshold." + idle_time_out => 500, + notify => self()}, + {ok, Connection} = amqp10_client:open_connection(CConf), + %% We expect our client to initiate closing the connection + %% and the server to reply with a close frame. + receive + {amqp10_event, + {connection, Connection0, + {closed, _}}} + when Connection0 =:= Connection -> + ok + after ?TIMEOUT -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) exit(incoming_heartbeat_assert) end, demonitor(MockRef). +<<<<<<< HEAD +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %%% HELPERS %%% @@ -873,7 +989,11 @@ await_link(Who, What, Err) -> {amqp10_event, {link, Who0, {detached, Why}}} when Who0 =:= Who -> ct:fail(Why) +<<<<<<< HEAD after 5000 -> +======= + after ?TIMEOUT -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) flush(), ct:fail(Err) end. @@ -890,7 +1010,11 @@ await_disposition(DeliveryTag) -> receive {amqp10_disposition, {accepted, DeliveryTag0}} when DeliveryTag0 =:= DeliveryTag -> ok +<<<<<<< HEAD after 3000 -> +======= + after ?TIMEOUT -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) flush(), ct:fail(dispostion_timeout) end. @@ -902,7 +1026,11 @@ count_received_messages0(Receiver, Count) -> receive {amqp10_msg, Receiver, _Msg} -> count_received_messages0(Receiver, Count + 1) +<<<<<<< HEAD after 500 -> +======= + after 5000 -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Count end. @@ -915,7 +1043,11 @@ receive_messages0(Receiver, N, Acc) -> receive {amqp10_msg, Receiver, Msg} -> receive_messages0(Receiver, N - 1, [Msg | Acc]) +<<<<<<< HEAD after 5000 -> +======= + after ?TIMEOUT -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) LastReceivedMsg = case Acc of [] -> none; [M | _] -> M diff --git a/deps/amqp10_common/app.bzl b/deps/amqp10_common/app.bzl index a233c945cebe..0386291f7779 100644 --- a/deps/amqp10_common/app.bzl +++ b/deps/amqp10_common/app.bzl @@ -72,7 +72,11 @@ def all_srcs(name = "all_srcs"): ) filegroup( name = "public_hdrs", +<<<<<<< HEAD srcs = ["include/amqp10_framing.hrl", "include/amqp10_types.hrl"], +======= + srcs = ["include/amqp10_filtex.hrl", "include/amqp10_framing.hrl", "include/amqp10_types.hrl"], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ) filegroup( name = "private_hdrs", diff --git a/deps/amqp10_common/include/amqp10_filtex.hrl b/deps/amqp10_common/include/amqp10_filtex.hrl new file mode 100644 index 000000000000..a1743ea9669c --- /dev/null +++ b/deps/amqp10_common/include/amqp10_filtex.hrl @@ -0,0 +1,15 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. + + +%% AMQP Filter Expressions Version 1.0 Working Draft 09 +%% https://groups.oasis-open.org/higherlogic/ws/public/document?document_id=66227 + +-define(DESCRIPTOR_NAME_PROPERTIES_FILTER, <<"amqp:properties-filter">>). +-define(DESCRIPTOR_CODE_PROPERTIES_FILTER, 16#173). + +-define(DESCRIPTOR_NAME_APPLICATION_PROPERTIES_FILTER, <<"amqp:application-properties-filter">>). +-define(DESCRIPTOR_CODE_APPLICATION_PROPERTIES_FILTER, 16#174). diff --git a/deps/amqp_client/src/amqp_network_connection.erl b/deps/amqp_client/src/amqp_network_connection.erl index a5ef739ea0f3..c7305782e12b 100644 --- a/deps/amqp_client/src/amqp_network_connection.erl +++ b/deps/amqp_client/src/amqp_network_connection.erl @@ -137,7 +137,11 @@ do_connect({Addr, Family}, [Family | ?RABBIT_TCP_OPTS] ++ ExtraOpts, Timeout) of {ok, Sock} -> +<<<<<<< HEAD SslOpts = rabbit_ssl_options:fix( +======= + SslOpts = rabbit_ssl_options:fix_client( +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) orddict:to_list( orddict:merge(fun (_, _A, B) -> B end, orddict:from_list(GlobalSslOpts), diff --git a/deps/oauth2_client/app.bzl b/deps/oauth2_client/app.bzl index 6b4b31789a16..6c0fe0851c6b 100644 --- a/deps/oauth2_client/app.bzl +++ b/deps/oauth2_client/app.bzl @@ -64,7 +64,11 @@ def all_srcs(name = "all_srcs"): ) filegroup( name = "public_hdrs", +<<<<<<< HEAD srcs = ["include/oauth2_client.hrl"], +======= + srcs = ["include/oauth2_client.hrl", "include/types.hrl"], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ) filegroup( name = "license_files", @@ -88,7 +92,11 @@ def test_suite_beam_files(name = "test_suite_beam_files"): testonly = True, srcs = ["test/system_SUITE.erl"], outs = ["test/system_SUITE.beam"], +<<<<<<< HEAD hdrs = ["include/oauth2_client.hrl"], +======= + hdrs = ["include/oauth2_client.hrl", "include/types.hrl"], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) app_name = "oauth2_client", erlc_opts = "//:test_erlc_opts", ) @@ -97,7 +105,11 @@ def test_suite_beam_files(name = "test_suite_beam_files"): testonly = True, srcs = ["test/unit_SUITE.erl"], outs = ["test/unit_SUITE.beam"], +<<<<<<< HEAD hdrs = ["include/oauth2_client.hrl"], +======= + hdrs = ["include/oauth2_client.hrl", "include/types.hrl"], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) app_name = "oauth2_client", erlc_opts = "//:test_erlc_opts", ) diff --git a/deps/oauth2_client/include/oauth2_client.hrl b/deps/oauth2_client/include/oauth2_client.hrl index b7f93104f167..7f052a67ba13 100644 --- a/deps/oauth2_client/include/oauth2_client.hrl +++ b/deps/oauth2_client/include/oauth2_client.hrl @@ -5,6 +5,10 @@ %% Copyright (c) 2020-2023 VMware, Inc. or its affiliates. All rights reserved. %% +<<<<<<< HEAD +======= +-include("types.hrl"). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) % define access token request common constants @@ -44,6 +48,7 @@ -define(RESPONSE_END_SESSION_ENDPOINT, <<"end_session_endpoint">>). -define(RESPONSE_JWKS_URI, <<"jwks_uri">>). -define(RESPONSE_TLS_OPTIONS, <<"ssl_options">>). +<<<<<<< HEAD %% The closest we have to a type import in Erlang -type option(T) :: rabbit_types:option(T). @@ -107,3 +112,5 @@ }). -type refresh_token_request() :: #refresh_token_request{}. +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/oauth2_client/include/types.hrl b/deps/oauth2_client/include/types.hrl new file mode 100644 index 000000000000..622cae22202c --- /dev/null +++ b/deps/oauth2_client/include/types.hrl @@ -0,0 +1,75 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2020-2023 VMware, Inc. or its affiliates. All rights reserved. +%% + +%% Matches the option type in rabbit_types without introducing a dependency +%% on that module and RabbitMQ core (rabbit_common) +-type(option(T) :: T | 'none' | 'undefined'). + +-type oauth_provider_id() :: root | binary(). + +-record(openid_configuration, { + issuer :: option(uri_string:uri_string()), + token_endpoint :: option(uri_string:uri_string()), + authorization_endpoint :: option(uri_string:uri_string()), + end_session_endpoint :: option(uri_string:uri_string()), + jwks_uri :: option(uri_string:uri_string()) +}). +-type openid_configuration() :: #openid_configuration{}. + +-record(oauth_provider, { + id :: oauth_provider_id(), + issuer :: option(uri_string:uri_string()), + discovery_endpoint :: option(uri_string:uri_string()), + token_endpoint :: option(uri_string:uri_string()), + authorization_endpoint :: option(uri_string:uri_string()), + end_session_endpoint :: option(uri_string:uri_string()), + jwks_uri :: option(uri_string:uri_string()), + ssl_options :: option(list()) +}). + +-type query_list() :: [{unicode:chardata(), unicode:chardata() | true}]. + +-type oauth_provider() :: #oauth_provider{}. + +-record(access_token_request, { + client_id :: string() | binary(), + client_secret :: string() | binary(), + scope :: option(string() | binary()), + extra_parameters :: option(query_list()), + timeout :: option(integer()) +}). + +-type access_token_request() :: #access_token_request{}. + +-record(successful_access_token_response, { + access_token :: binary(), + token_type :: binary(), + %% Note: a refresh token SHOULD NOT be included + %% ... for client-credentials flow. + %% See https://www.rfc-editor.org/rfc/rfc6749#section-4.4.3 + refresh_token :: option(binary()), + expires_in :: option(integer()) +}). + +-type successful_access_token_response() :: #successful_access_token_response{}. + +-record(unsuccessful_access_token_response, { + error :: integer(), + error_description :: binary() | string() | undefined +}). + +-type unsuccessful_access_token_response() :: #unsuccessful_access_token_response{}. + +-record(refresh_token_request, { + client_id :: string() | binary(), + client_secret :: string() | binary(), + scope :: string() | binary() | undefined, + refresh_token :: binary(), + timeout :: option(integer()) +}). + +-type refresh_token_request() :: #refresh_token_request{}. diff --git a/deps/oauth2_client/src/oauth2_client.erl b/deps/oauth2_client/src/oauth2_client.erl index 335bcfdfba1b..19191a4ca79d 100644 --- a/deps/oauth2_client/src/oauth2_client.erl +++ b/deps/oauth2_client/src/oauth2_client.erl @@ -8,6 +8,7 @@ -export([get_access_token/2, get_expiration_time/1, refresh_access_token/2, get_oauth_provider/1, get_oauth_provider/2, +<<<<<<< HEAD get_openid_configuration/2, get_openid_configuration/3, merge_openid_configuration/2, merge_oauth_provider/2, @@ -17,6 +18,21 @@ -include("oauth2_client.hrl"). -spec get_access_token(oauth_provider(), access_token_request()) -> {ok, successful_access_token_response()} | {error, unsuccessful_access_token_response() | any()}. +======= + get_openid_configuration/2, + build_openid_discovery_endpoint/3, + merge_openid_configuration/2, + merge_oauth_provider/2, + extract_ssl_options_as_list/1, + format_ssl_options/1, format_oauth_provider/1, format_oauth_provider_id/1 + ]). + +-include("oauth2_client.hrl"). + +-spec get_access_token(oauth_provider(), access_token_request()) -> + {ok, successful_access_token_response()} | + {error, unsuccessful_access_token_response() | any()}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) get_access_token(OAuthProvider, Request) -> rabbit_log:debug("get_access_token using OAuthProvider:~p and client_id:~p", [OAuthProvider, Request#access_token_request.client_id]), @@ -31,7 +47,12 @@ get_access_token(OAuthProvider, Request) -> parse_access_token_response(Response). -spec refresh_access_token(oauth_provider(), refresh_token_request()) -> +<<<<<<< HEAD {ok, successful_access_token_response()} | {error, unsuccessful_access_token_response() | any()}. +======= + {ok, successful_access_token_response()} | + {error, unsuccessful_access_token_response() | any()}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) refresh_access_token(OAuthProvider, Request) -> URL = OAuthProvider#oauth_provider.token_endpoint, Header = [], @@ -46,6 +67,7 @@ refresh_access_token(OAuthProvider, Request) -> append_paths(Path1, Path2) -> erlang:iolist_to_binary([Path1, Path2]). +<<<<<<< HEAD -spec get_openid_configuration(uri_string:uri_string(), erlang:iodata() | <<>>, ssl:tls_option() | []) -> {ok, openid_configuration()} | {error, term()}. get_openid_configuration(IssuerURI, OpenIdConfigurationPath, TLSOptions) -> @@ -77,21 +99,90 @@ merge_openid_configuration(OpendIdConfiguration, OAuthProvider) -> OAuthProvider#oauth_provider{issuer = Issuer} end, OAuthProvider1 = case OpendIdConfiguration#openid_configuration.token_endpoint of +======= +-spec build_openid_discovery_endpoint(Issuer :: uri_string:uri_string(), + OpenIdConfigurationPath :: uri_string:uri_string() | undefined, + Params :: query_list()) -> uri_string:uri_string() | undefined. + +build_openid_discovery_endpoint(undefined, _, _) -> undefined; +build_openid_discovery_endpoint(Issuer, undefined, Params) -> + build_openid_discovery_endpoint(Issuer, ?DEFAULT_OPENID_CONFIGURATION_PATH, + Params); +build_openid_discovery_endpoint(Issuer, OpenIdConfigurationPath, Params) -> + URLMap0 = uri_string:parse(Issuer), + OpenIdPath = ensure_leading_path_separator(OpenIdConfigurationPath), + URLMap1 = URLMap0#{ + path := case maps:get(path, URLMap0) of + [] -> OpenIdPath; + P -> append_paths(drop_trailing_path_separator(P), OpenIdPath) + end + }, + uri_string:recompose( + case {Params, maps:get(query, URLMap1, undefined)} of + {undefined, undefined} -> + URLMap1; + {_, undefined} -> + URLMap1#{query => uri_string:compose_query(Params)}; + {_, Q} -> + URLMap1#{query => uri_string:compose_query(Q ++ Params)} + end). +ensure_leading_path_separator(Path) when is_binary(Path) -> + ensure_leading_path_separator(binary:bin_to_list(Path)); +ensure_leading_path_separator(Path) when is_list(Path) -> + case string:slice(Path, 0, 1) of + "/" -> Path; + _ -> "/" ++ Path + end. +drop_trailing_path_separator(Path) when is_binary(Path) -> + drop_trailing_path_separator(binary:bin_to_list(Path)); +drop_trailing_path_separator("") -> ""; +drop_trailing_path_separator(Path) when is_list(Path) -> + case string:slice(Path, string:len(Path)-1, 1) of + "/" -> lists:droplast(Path); + _ -> Path + end. + +-spec get_openid_configuration(DiscoveryEndpoint :: uri_string:uri_string(), + ssl:tls_option() | []) -> {ok, openid_configuration()} | {error, term()}. +get_openid_configuration(DiscoverEndpoint, TLSOptions) -> + rabbit_log:debug("get_openid_configuration from ~p (~p)", [DiscoverEndpoint, + format_ssl_options(TLSOptions)]), + Options = [], + Response = httpc:request(get, {DiscoverEndpoint, []}, TLSOptions, Options), + parse_openid_configuration_response(Response). + +-spec merge_openid_configuration(openid_configuration(), oauth_provider()) -> + oauth_provider(). +merge_openid_configuration(OpenId, OAuthProvider0) -> + OAuthProvider1 = case OpenId#openid_configuration.token_endpoint of +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) undefined -> OAuthProvider0; TokenEndpoint -> OAuthProvider0#oauth_provider{token_endpoint = TokenEndpoint} end, +<<<<<<< HEAD OAuthProvider2 = case OpendIdConfiguration#openid_configuration.authorization_endpoint of +======= + OAuthProvider2 = case OpenId#openid_configuration.authorization_endpoint of +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) undefined -> OAuthProvider1; AuthorizationEndpoint -> OAuthProvider1#oauth_provider{authorization_endpoint = AuthorizationEndpoint} end, +<<<<<<< HEAD OAuthProvider3 = case OpendIdConfiguration#openid_configuration.end_session_endpoint of +======= + OAuthProvider3 = case OpenId#openid_configuration.end_session_endpoint of +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) undefined -> OAuthProvider2; EndSessionEndpoint -> OAuthProvider2#oauth_provider{end_session_endpoint = EndSessionEndpoint} end, +<<<<<<< HEAD case OpendIdConfiguration#openid_configuration.jwks_uri of +======= + case OpenId#openid_configuration.jwks_uri of +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) undefined -> OAuthProvider3; JwksUri -> OAuthProvider3#oauth_provider{jwks_uri = JwksUri} @@ -126,7 +217,12 @@ parse_openid_configuration_response({error, Reason}) -> parse_openid_configuration_response({ok,{{_,Code,Reason}, Headers, Body}}) -> map_response_to_openid_configuration(Code, Reason, Headers, Body). map_response_to_openid_configuration(Code, Reason, Headers, Body) -> +<<<<<<< HEAD case decode_body(proplists:get_value("content-type", Headers, ?CONTENT_JSON), Body) of +======= + case decode_body(proplists:get_value("content-type", Headers, + ?CONTENT_JSON), Body) of +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {error, {error, InternalError}} -> {error, InternalError}; {error, _} = Error -> @@ -142,13 +238,25 @@ map_to_openid_configuration(Map) -> #openid_configuration{ issuer = maps:get(?RESPONSE_ISSUER, Map), token_endpoint = maps:get(?RESPONSE_TOKEN_ENDPOINT, Map, undefined), +<<<<<<< HEAD authorization_endpoint = maps:get(?RESPONSE_AUTHORIZATION_ENDPOINT, Map, undefined), end_session_endpoint = maps:get(?RESPONSE_END_SESSION_ENDPOINT, Map, undefined), +======= + authorization_endpoint = maps:get(?RESPONSE_AUTHORIZATION_ENDPOINT, + Map, undefined), + end_session_endpoint = maps:get(?RESPONSE_END_SESSION_ENDPOINT, + Map, undefined), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) jwks_uri = maps:get(?RESPONSE_JWKS_URI, Map, undefined) }. -spec get_expiration_time(successful_access_token_response()) -> +<<<<<<< HEAD {ok, [{expires_in, integer() }| {exp, integer() }]} | {error, missing_exp_field}. +======= + {ok, [{expires_in, integer() }| {exp, integer() }]} | + {error, missing_exp_field}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) get_expiration_time(#successful_access_token_response{expires_in = ExpiresInSec, access_token = AccessToken}) -> case ExpiresInSec of @@ -168,6 +276,7 @@ update_oauth_provider_endpoints_configuration(OAuthProvider) -> unlock(LockId) end. +<<<<<<< HEAD update_oauth_provider_endpoints_configuration(OAuthProviderId, OAuthProvider) -> LockId = lock(), try do_update_oauth_provider_endpoints_configuration(OAuthProviderId, OAuthProvider) of @@ -210,11 +319,46 @@ do_update_oauth_provider_endpoints_configuration(OAuthProviderId, OAuthProvider) ModifiedOAuthProviders = maps:put(OAuthProviderId, merge_oauth_provider(OAuthProvider, Proplist), OAuthProviders), application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, ModifiedOAuthProviders), +======= +do_update_oauth_provider_endpoints_configuration(OAuthProvider) when + OAuthProvider#oauth_provider.id == root -> + case OAuthProvider#oauth_provider.token_endpoint of + undefined -> do_nothing; + TokenEndpoint -> set_env(token_endpoint, TokenEndpoint) + end, + case OAuthProvider#oauth_provider.authorization_endpoint of + undefined -> do_nothing; + AuthzEndpoint -> set_env(authorization_endpoint, AuthzEndpoint) + end, + case OAuthProvider#oauth_provider.end_session_endpoint of + undefined -> do_nothing; + EndSessionEndpoint -> set_env(end_session_endpoint, EndSessionEndpoint) + end, + case OAuthProvider#oauth_provider.jwks_uri of + undefined -> do_nothing; + JwksUri -> set_env(jwks_uri, JwksUri) + end, + rabbit_log:debug("Updated oauth_provider details: ~p ", + [format_oauth_provider(OAuthProvider)]), + OAuthProvider; + +do_update_oauth_provider_endpoints_configuration(OAuthProvider) -> + OAuthProviderId = OAuthProvider#oauth_provider.id, + OAuthProviders = get_env(oauth_providers, #{}), + Proplist = maps:get(OAuthProviderId, OAuthProviders), + ModifiedOAuthProviders = maps:put(OAuthProviderId, + merge_oauth_provider(OAuthProvider, Proplist), OAuthProviders), + set_env(oauth_providers, ModifiedOAuthProviders), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_log:debug("Replaced oauth_providers "), OAuthProvider. use_global_locks_on_all_nodes() -> +<<<<<<< HEAD case application:get_env(rabbitmq_auth_backend_oauth2, use_global_locks, true) of +======= + case get_env(use_global_locks, true) of +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) true -> {rabbit_nodes:list_running(), rabbit_nodes:lock_retries()}; _ -> {} end. @@ -227,7 +371,12 @@ lock() -> false -> undefined end; {Nodes, Retries} -> +<<<<<<< HEAD case global:set_lock({oauth2_config_lock, rabbitmq_auth_backend_oauth2}, Nodes, Retries) of +======= + case global:set_lock({oauth2_config_lock, rabbitmq_auth_backend_oauth2}, + Nodes, Retries) of +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) true -> rabbitmq_auth_backend_oauth2; false -> undefined end @@ -238,13 +387,21 @@ unlock(LockId) -> undefined -> ok; Value -> case use_global_locks_on_all_nodes() of +<<<<<<< HEAD {} -> global:del_lock({oauth2_config_lock, Value}); {Nodes, _Retries} -> global:del_lock({oauth2_config_lock, Value}, Nodes) +======= + {} -> + global:del_lock({oauth2_config_lock, Value}); + {Nodes, _Retries} -> + global:del_lock({oauth2_config_lock, Value}, Nodes) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end end. -spec get_oauth_provider(list()) -> {ok, oauth_provider()} | {error, any()}. get_oauth_provider(ListOfRequiredAttributes) -> +<<<<<<< HEAD case application:get_env(rabbitmq_auth_backend_oauth2, default_oauth_provider) of undefined -> get_oauth_provider_from_keyconfig(ListOfRequiredAttributes); {ok, DefaultOauthProviderId} -> @@ -255,10 +412,50 @@ get_oauth_provider(ListOfRequiredAttributes) -> get_oauth_provider_from_keyconfig(ListOfRequiredAttributes) -> OAuthProvider = lookup_oauth_provider_from_keyconfig(), rabbit_log:debug("Using oauth_provider ~s from keyconfig", [format_oauth_provider(OAuthProvider)]), +======= + case get_env(default_oauth_provider) of + undefined -> get_root_oauth_provider(ListOfRequiredAttributes); + DefaultOauthProviderId -> + rabbit_log:debug("Using default_oauth_provider ~p", + [DefaultOauthProviderId]), + get_oauth_provider(DefaultOauthProviderId, ListOfRequiredAttributes) + end. + +-spec download_oauth_provider(oauth_provider()) -> {ok, oauth_provider()} | + {error, any()}. +download_oauth_provider(OAuthProvider) -> + case OAuthProvider#oauth_provider.discovery_endpoint of + undefined -> {error, {missing_oauth_provider_attributes, [issuer]}}; + URL -> + rabbit_log:debug("Downloading oauth_provider using ~p ", [URL]), + case get_openid_configuration(URL, get_ssl_options_if_any(OAuthProvider)) of + {ok, OpenIdConfiguration} -> + {ok, update_oauth_provider_endpoints_configuration( + merge_openid_configuration(OpenIdConfiguration, OAuthProvider))}; + {error, _} = Error2 -> Error2 + end + end. + +ensure_oauth_provider_has_attributes(OAuthProvider, ListOfRequiredAttributes) -> + case find_missing_attributes(OAuthProvider, ListOfRequiredAttributes) of + [] -> + rabbit_log:debug("Resolved oauth_provider ~p", + [format_oauth_provider(OAuthProvider)]), + {ok, OAuthProvider}; + _ = Attrs -> + {error, {missing_oauth_provider_attributes, Attrs}} + end. + +get_root_oauth_provider(ListOfRequiredAttributes) -> + OAuthProvider = lookup_root_oauth_provider(), + rabbit_log:debug("Using root oauth_provider ~p", + [format_oauth_provider(OAuthProvider)]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case find_missing_attributes(OAuthProvider, ListOfRequiredAttributes) of [] -> {ok, OAuthProvider}; _ = MissingAttributes -> +<<<<<<< HEAD rabbit_log:debug("OauthProvider has following missing attributes ~p", [MissingAttributes]), Result2 = case OAuthProvider#oauth_provider.issuer of undefined -> {error, {missing_oauth_provider_attributes, [issuer]}}; @@ -281,10 +478,21 @@ get_oauth_provider_from_keyconfig(ListOfRequiredAttributes) -> {error, {missing_oauth_provider_attributes, Attrs}} end; {error, _} = Error3 -> Error3 +======= + rabbit_log:debug("Looking up missing attributes ~p ...", + [MissingAttributes]), + case download_oauth_provider(OAuthProvider) of + {ok, OAuthProvider2} -> + ensure_oauth_provider_has_attributes(OAuthProvider2, + ListOfRequiredAttributes); + {error, _} = Error3 -> + Error3 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end end. +<<<<<<< HEAD -spec get_oauth_provider(oauth_provider_id(), list()) -> {ok, oauth_provider()} | {error, any()}. get_oauth_provider(root, ListOfRequiredAttributes) -> get_oauth_provider(ListOfRequiredAttributes); @@ -294,6 +502,22 @@ get_oauth_provider(OAuth2ProviderId, ListOfRequiredAttributes) when is_list(OAut get_oauth_provider(OAuthProviderId, ListOfRequiredAttributes) when is_binary(OAuthProviderId) -> rabbit_log:debug("get_oauth_provider ~p with at least these attributes: ~p", [OAuthProviderId, ListOfRequiredAttributes]), +======= +-spec get_oauth_provider(oauth_provider_id(), list()) -> {ok, oauth_provider()} | + {error, any()}. +get_oauth_provider(root, ListOfRequiredAttributes) -> + get_oauth_provider(ListOfRequiredAttributes); + +get_oauth_provider(OAuth2ProviderId, ListOfRequiredAttributes) + when is_list(OAuth2ProviderId) -> + get_oauth_provider(list_to_binary(OAuth2ProviderId), + ListOfRequiredAttributes); + +get_oauth_provider(OAuthProviderId, ListOfRequiredAttributes) + when is_binary(OAuthProviderId) -> + rabbit_log:debug("get_oauth_provider ~p with at least these attributes: ~p", + [OAuthProviderId, ListOfRequiredAttributes]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case lookup_oauth_provider_config(OAuthProviderId) of {error, _} = Error0 -> rabbit_log:debug("Failed to find oauth_provider ~p configuration due to ~p", @@ -308,6 +532,7 @@ get_oauth_provider(OAuthProviderId, ListOfRequiredAttributes) when is_binary(OAu {ok, OAuthProvider}; _ = MissingAttributes -> rabbit_log:debug("OauthProvider has following missing attributes ~p", [MissingAttributes]), +<<<<<<< HEAD Result2 = case OAuthProvider#oauth_provider.issuer of undefined -> {error, {missing_oauth_provider_attributes, [issuer]}}; Issuer -> @@ -330,6 +555,14 @@ get_oauth_provider(OAuthProviderId, ListOfRequiredAttributes) when is_binary(OAu {error, {missing_oauth_provider_attributes, Attrs}} end; {error, _} = Error3 -> Error3 +======= + case download_oauth_provider(OAuthProvider) of + {ok, OAuthProvider2} -> + ensure_oauth_provider_has_attributes(OAuthProvider2, + ListOfRequiredAttributes); + {error, _} = Error3 -> + Error3 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end end end. @@ -357,6 +590,7 @@ find_missing_attributes(#oauth_provider{} = OAuthProvider, RequiredAttributes) - Filtered = filter_undefined_props(PropList), intersection(Filtered, RequiredAttributes). +<<<<<<< HEAD lookup_oauth_provider_from_keyconfig() -> Issuer = application:get_env(rabbitmq_auth_backend_oauth2, issuer, undefined), TokenEndpoint = application:get_env(rabbitmq_auth_backend_oauth2, token_endpoint, undefined), @@ -370,6 +604,21 @@ lookup_oauth_provider_from_keyconfig() -> token_endpoint = TokenEndpoint, authorization_endpoint = AuthorizationEndpoint, end_session_endpoint = EndSessionEndpoint, +======= +lookup_root_oauth_provider() -> + Map = maps:from_list(get_env(key_config, [])), + Issuer = get_env(issuer), + DiscoverEndpoint = build_openid_discovery_endpoint(Issuer, + get_env(discovery_endpoint_path), get_env(discovery_endpoint_params)), + #oauth_provider{ + id = root, + issuer = Issuer, + discovery_endpoint = DiscoverEndpoint, + jwks_uri = get_env(jwks_uri, maps:get(jwks_url, Map, undefined)), + token_endpoint = get_env(token_endpoint), + authorization_endpoint = get_env(authorization_endpoint), + end_session_endpoint = get_env(end_session_endpoint), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ssl_options = extract_ssl_options_as_list(Map) }. @@ -410,7 +659,12 @@ extract_ssl_options_as_list(Map) -> ++ case maps:get(hostname_verification, Map, none) of wildcard -> +<<<<<<< HEAD [{customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]}]; +======= + [{customize_hostname_check, [{match_fun, + public_key:pkix_verify_hostname_match_fun(https)}]}]; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) none -> [] end. @@ -418,7 +672,12 @@ extract_ssl_options_as_list(Map) -> % Replace peer_verification with verify to make it more consistent with other % ssl_options in RabbitMQ and Erlang's ssl options % Eventually, peer_verification will be removed. For now, both are allowed +<<<<<<< HEAD -spec get_verify_or_peer_verification(#{atom() => any()}, verify_none | verify_peer ) -> verify_none | verify_peer. +======= +-spec get_verify_or_peer_verification(#{atom() => + any()}, verify_none | verify_peer ) -> verify_none | verify_peer. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) get_verify_or_peer_verification(Ssl_options, Default) -> case maps:get(verify, Ssl_options, undefined) of undefined -> @@ -430,14 +689,25 @@ get_verify_or_peer_verification(Ssl_options, Default) -> end. lookup_oauth_provider_config(OAuth2ProviderId) -> +<<<<<<< HEAD case application:get_env(rabbitmq_auth_backend_oauth2, oauth_providers) of undefined -> {error, oauth_providers_not_found}; {ok, MapOfProviders} when is_map(MapOfProviders) -> +======= + case get_env(oauth_providers) of + undefined -> {error, oauth_providers_not_found}; + MapOfProviders when is_map(MapOfProviders) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case maps:get(OAuth2ProviderId, MapOfProviders, undefined) of undefined -> {error, {oauth_provider_not_found, OAuth2ProviderId}}; OAuthProvider -> +<<<<<<< HEAD ensure_oauth_provider_has_id_property(OAuth2ProviderId, OAuthProvider) +======= + ensure_oauth_provider_has_id_property(OAuth2ProviderId, + OAuthProvider) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end; _ -> {error, invalid_oauth_provider_configuration} end. @@ -448,6 +718,7 @@ ensure_oauth_provider_has_id_property(OAuth2ProviderId, OAuth2Provider) -> end. build_access_token_request_body(Request) -> +<<<<<<< HEAD uri_string:compose_query([ grant_type_request_parameter(?CLIENT_CREDENTIALS_GRANT_TYPE), client_id_request_parameter(Request#access_token_request.client_id), @@ -475,6 +746,52 @@ scope_request_parameter_or_default(Scope, Default) -> undefined -> Default; <<>> -> Default; Scope -> [{?REQUEST_SCOPE, Scope}] +======= + uri_string:compose_query( + append_extra_parameters(Request, + append_scope_request_parameter(Request#access_token_request.scope, [ + grant_type_request_parameter(?CLIENT_CREDENTIALS_GRANT_TYPE), + client_id_request_parameter( + Request#access_token_request.client_id), + client_secret_request_parameter( + Request#access_token_request.client_secret)]))). + +build_refresh_token_request_body(Request) -> + uri_string:compose_query( + append_scope_request_parameter(Request#refresh_token_request.scope, [ + grant_type_request_parameter(?REFRESH_TOKEN_GRANT_TYPE), + refresh_token_request_parameter(Request), + client_id_request_parameter(Request#refresh_token_request.client_id), + client_secret_request_parameter( + Request#refresh_token_request.client_secret)])). + +grant_type_request_parameter(Type) -> + {?REQUEST_GRANT_TYPE, Type}. + +client_id_request_parameter(ClientId) -> + {?REQUEST_CLIENT_ID, + binary_to_list(ClientId)}. + +client_secret_request_parameter(ClientSecret) -> + {?REQUEST_CLIENT_SECRET, + binary_to_list(ClientSecret)}. + +refresh_token_request_parameter(Request) -> + {?REQUEST_REFRESH_TOKEN, Request#refresh_token_request.refresh_token}. + +append_scope_request_parameter(Scope, QueryList) -> + case Scope of + undefined -> QueryList; + <<>> -> QueryList; + Scope -> [{?REQUEST_SCOPE, Scope} | QueryList] + end. + +append_extra_parameters(Request, QueryList) -> + case Request#access_token_request.extra_parameters of + undefined -> QueryList; + [] -> QueryList; + Params -> Params ++ QueryList +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end. get_ssl_options_if_any(OAuthProvider) -> @@ -491,8 +808,14 @@ get_timeout_of_default(Timeout) -> is_json(?CONTENT_JSON) -> true; is_json(_) -> false. +<<<<<<< HEAD -spec decode_body(string(), string() | binary() | term()) -> 'false' | 'null' | 'true' | binary() | [any()] | number() | map() | {error, term()}. +======= +-spec decode_body(string(), string() | binary() | term()) -> + 'false' | 'null' | 'true' | binary() | [any()] | number() | map() | + {error, term()}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) decode_body(_, []) -> []; decode_body(?CONTENT_JSON, Body) -> @@ -521,6 +844,7 @@ map_to_unsuccessful_access_token_response(Map) -> error_description = maps:get(?RESPONSE_ERROR_DESCRIPTION, Map, undefined) }. map_to_oauth_provider(PropList) when is_list(PropList) -> +<<<<<<< HEAD #oauth_provider{ id = proplists:get_value(id, PropList), issuer = proplists:get_value(issuer, PropList), @@ -529,6 +853,30 @@ map_to_oauth_provider(PropList) when is_list(PropList) -> end_session_endpoint = proplists:get_value(end_session_endpoint, PropList, undefined), jwks_uri = proplists:get_value(jwks_uri, PropList, undefined), ssl_options = extract_ssl_options_as_list(maps:from_list(proplists:get_value(https, PropList, []))) +======= + Issuer = proplists:get_value(issuer, PropList), + DiscoveryEndpoint = build_openid_discovery_endpoint(Issuer, + proplists:get_value(discovery_endpoint_path, PropList), + proplists:get_value(discovery_endpoint_params, PropList)), + #oauth_provider{ + id = + proplists:get_value(id, PropList), + issuer = + Issuer, + discovery_endpoint = + DiscoveryEndpoint, + token_endpoint = + proplists:get_value(token_endpoint, PropList), + authorization_endpoint = + proplists:get_value(authorization_endpoint, PropList, undefined), + end_session_endpoint = + proplists:get_value(end_session_endpoint, PropList, undefined), + jwks_uri = + proplists:get_value(jwks_uri, PropList, undefined), + ssl_options = + extract_ssl_options_as_list(maps:from_list( + proplists:get_value(https, PropList, []))) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }. map_to_access_token_response(Code, Reason, Headers, Body) -> case decode_body(proplists:get_value("content-type", Headers, ?CONTENT_JSON), Body) of @@ -557,27 +905,57 @@ format_ssl_options(TlsOptions) -> [] -> 0; Certs -> length(Certs) end, +<<<<<<< HEAD io_lib:format("{verify: ~p, fail_if_no_peer_cert: ~p, crl_check: ~p, " ++ "depth: ~p, cacertfile: ~p, cacerts(count): ~p }", [ +======= + lists:flatten(io_lib:format("{verify: ~p, fail_if_no_peer_cert: ~p, " ++ + "crl_check: ~p, depth: ~p, cacertfile: ~p, cacerts(count): ~p }", [ +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) proplists:get_value(verify, TlsOptions), proplists:get_value(fail_if_no_peer_cert, TlsOptions), proplists:get_value(crl_check, TlsOptions), proplists:get_value(depth, TlsOptions), proplists:get_value(cacertfile, TlsOptions), +<<<<<<< HEAD CaCertsCount]). +======= + CaCertsCount])). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) format_oauth_provider_id(root) -> ""; format_oauth_provider_id(Id) -> binary_to_list(Id). -spec format_oauth_provider(oauth_provider()) -> string(). format_oauth_provider(OAuthProvider) -> +<<<<<<< HEAD io_lib:format("{id: ~p, issuer: ~p, token_endpoint: ~p, " ++ "authorization_endpoint: ~p, end_session_endpoint: ~p, " ++ "jwks_uri: ~p, ssl_options: ~s }", [ format_oauth_provider_id(OAuthProvider#oauth_provider.id), OAuthProvider#oauth_provider.issuer, +======= + lists:flatten(io_lib:format("{id: ~p, issuer: ~p, discovery_endpoint: ~p, " ++ + " token_endpoint: ~p, " ++ + "authorization_endpoint: ~p, end_session_endpoint: ~p, " ++ + "jwks_uri: ~p, ssl_options: ~p }", [ + format_oauth_provider_id(OAuthProvider#oauth_provider.id), + OAuthProvider#oauth_provider.issuer, + OAuthProvider#oauth_provider.discovery_endpoint, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) OAuthProvider#oauth_provider.token_endpoint, OAuthProvider#oauth_provider.authorization_endpoint, OAuthProvider#oauth_provider.end_session_endpoint, OAuthProvider#oauth_provider.jwks_uri, +<<<<<<< HEAD format_ssl_options(OAuthProvider#oauth_provider.ssl_options)]). +======= + format_ssl_options(OAuthProvider#oauth_provider.ssl_options)])). + +get_env(Par) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par, undefined). +get_env(Par, Def) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par, Def). +set_env(Par, Val) -> + application:set_env(rabbitmq_auth_backend_oauth2, Par, Val). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/oauth2_client/test/system_SUITE.erl b/deps/oauth2_client/test/system_SUITE.erl index ecc5aa733bef..17bc5099885d 100644 --- a/deps/oauth2_client/test/system_SUITE.erl +++ b/deps/oauth2_client/test/system_SUITE.erl @@ -11,6 +11,12 @@ -include_lib("eunit/include/eunit.hrl"). -include_lib("oauth2_client.hrl"). +<<<<<<< HEAD +======= +-import(oauth2_client, [ + build_openid_discovery_endpoint/3 +]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -compile(export_all). @@ -31,11 +37,22 @@ all() -> groups() -> [ +<<<<<<< HEAD {with_all_oauth_provider_settings, [], [ {group, verify_get_oauth_provider} ]}, {without_all_oauth_providers_settings, [], [ {group, verify_get_oauth_provider} +======= + + {with_all_oauth_provider_settings, [], [ + {group, verify_get_oauth_provider}, + jwks_uri_takes_precedence_over_jwks_url, + jwks_url_is_used_in_absense_of_jwks_uri + ]}, + {without_all_oauth_providers_settings, [], [ + {group, verify_get_oauth_provider} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]}, {verify_openid_configuration, [], [ get_openid_configuration, @@ -54,7 +71,11 @@ groups() -> expiration_time_in_token ]}, {verify_get_oauth_provider, [], [ +<<<<<<< HEAD get_oauth_provider, +======= + get_oauth_provider, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {with_default_oauth_provider, [], [ get_oauth_provider ]}, @@ -75,10 +96,23 @@ groups() -> init_per_suite(Config) -> [ +<<<<<<< HEAD {denies_access_token, [ {token_endpoint, denies_access_token_expectation()} ]}, {auth_server_error, [ {token_endpoint, auth_server_error_when_access_token_request_expectation()} ]}, {non_json_payload, [ {token_endpoint, non_json_payload_when_access_token_request_expectation()} ]}, {grants_refresh_token, [ {token_endpoint, grants_refresh_token_expectation()} ]} +======= + {jwks_url, build_jwks_uri("https", "/certs4url")}, + {jwks_uri, build_jwks_uri("https")}, + {denies_access_token, [ + {token_endpoint, denies_access_token_expectation()} ]}, + {auth_server_error, [ + {token_endpoint, auth_server_error_when_access_token_request_expectation()} ]}, + {non_json_payload, [ + {token_endpoint, non_json_payload_when_access_token_request_expectation()} ]}, + {grants_refresh_token, [ + {token_endpoint, grants_refresh_token_expectation()} ]} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) | Config]. end_per_suite(Config) -> @@ -90,11 +124,17 @@ init_per_group(https, Config) -> application:ensure_all_started(cowboy), Config0 = rabbit_ct_helpers:run_setup_steps(Config), CertsDir = ?config(rmq_certsdir, Config0), +<<<<<<< HEAD ct:log("certsdir: ~p", [CertsDir]), CaCertFile = filename:join([CertsDir, "testca", "cacert.pem"]), WrongCaCertFile = filename:join([CertsDir, "server", "server.pem"]), [{group, https}, {certsDir, CertsDir}, +======= + CaCertFile = filename:join([CertsDir, "testca", "cacert.pem"]), + WrongCaCertFile = filename:join([CertsDir, "server", "server.pem"]), + [{group, https}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {oauth_provider_id, <<"uaa">>}, {oauth_provider, build_https_oauth_provider(<<"uaa">>, CaCertFile)}, {oauth_provider_with_issuer, keep_only_issuer_and_ssl_options( @@ -119,18 +159,30 @@ init_per_group(openid_configuration_with_path, Config) -> init_per_group(with_all_oauth_provider_settings, Config) -> Config0 = rabbit_ct_helpers:run_setup_steps(Config), +<<<<<<< HEAD CertsDir = ?config(certsDir, Config0), CaCertFile = filename:join([CertsDir, "testca", "cacert.pem"]), ct:log("certsdir: ~p", [CertsDir]), +======= + CertsDir = ?config(rmq_certsdir, Config0), + CaCertFile = filename:join([CertsDir, "testca", "cacert.pem"]), + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) [{with_all_oauth_provider_settings, true}, {oauth_provider_id, <<"uaa">>}, {oauth_provider, build_https_oauth_provider(<<"uaa">>, CaCertFile)} | Config0]; init_per_group(without_all_oauth_providers_settings, Config) -> Config0 = rabbit_ct_helpers:run_setup_steps(Config), +<<<<<<< HEAD CertsDir = ?config(certsDir, Config0), CaCertFile = filename:join([CertsDir, "testca", "cacert.pem"]), ct:log("certsdir: ~p", [CertsDir]), +======= + CertsDir = ?config(rmq_certsdir, Config0), + CaCertFile = filename:join([CertsDir, "testca", "cacert.pem"]), + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) [{with_all_oauth_provider_settings, false}, {oauth_provider_id, <<"uaa">>}, {oauth_provider, keep_only_issuer_and_ssl_options( @@ -149,7 +201,10 @@ init_per_group(_, Config) -> get_http_oauth_server_expectations(TestCase, Config) -> case ?config(TestCase, Config) of undefined -> +<<<<<<< HEAD ct:log("get_openid_configuration_http_expectation : ~p", [get_openid_configuration_http_expectation(TestCase)]), +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) [ {token_endpoint, build_http_mock_behaviour(build_http_access_token_request(), build_http_200_access_token_response())}, {get_openid_configuration, get_openid_configuration_http_expectation(TestCase)} @@ -198,13 +253,18 @@ configure_all_oauth_provider_settings(Config) -> OAuthProvider#oauth_provider.end_session_endpoint), application:set_env(rabbitmq_auth_backend_oauth2, authorization_endpoint, OAuthProvider#oauth_provider.authorization_endpoint), +<<<<<<< HEAD KeyConfig = [ { jwks_url, OAuthProvider#oauth_provider.jwks_uri } ] ++ +======= + KeyConfig0 = +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case OAuthProvider#oauth_provider.ssl_options of undefined -> []; _ -> [ {peer_verification, proplists:get_value(verify, OAuthProvider#oauth_provider.ssl_options) }, +<<<<<<< HEAD {cacertfile, proplists:get_value(cacertfile, OAuthProvider#oauth_provider.ssl_options) } ] @@ -213,6 +273,33 @@ configure_all_oauth_provider_settings(Config) -> configure_minimum_oauth_provider_settings(Config) -> OAuthProvider = ?config(oauth_provider_with_issuer, Config), +======= + {cacertfile, proplists:get_value(cacertfile, + OAuthProvider#oauth_provider.ssl_options) } + ] + end, + KeyConfig = + case ?config(jwks_uri_type_of_config, Config) of + undefined -> + application:set_env(rabbitmq_auth_backend_oauth2, jwks_uri, + OAuthProvider#oauth_provider.jwks_uri), + KeyConfig0; + only_jwks_uri -> + application:set_env(rabbitmq_auth_backend_oauth2, jwks_uri, + OAuthProvider#oauth_provider.jwks_uri), + KeyConfig0; + only_jwks_url -> + [ { jwks_url, ?config(jwks_url, Config) } | KeyConfig0 ]; + both -> + application:set_env(rabbitmq_auth_backend_oauth2, jwks_uri, + OAuthProvider#oauth_provider.jwks_uri), + [ { jwks_url, ?config(jwks_url, Config) } | KeyConfig0 ] + end, + application:set_env(rabbitmq_auth_backend_oauth2, key_config, KeyConfig). + +configure_minimum_oauth_provider_settings(Config) -> + OAuthProvider = ?config(oauth_provider, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) OAuthProviders = #{ ?config(oauth_provider_id, Config) => oauth_provider_to_proplist(OAuthProvider) }, application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, @@ -232,9 +319,24 @@ configure_minimum_oauth_provider_settings(Config) -> end, application:set_env(rabbitmq_auth_backend_oauth2, key_config, KeyConfig). +<<<<<<< HEAD init_per_testcase(TestCase, Config) -> application:set_env(rabbitmq_auth_backend_oauth2, use_global_locks, false), +======= +init_per_testcase(TestCase, Config0) -> + application:set_env(rabbitmq_auth_backend_oauth2, use_global_locks, false), + + Config = [case TestCase of + jwks_url_is_used_in_absense_of_jwks_uri -> + {jwks_uri_type_of_config, only_jwks_url}; + jwks_uri_takes_precedence_over_jwks_url -> + {jwks_uri_type_of_config, both}; + _ -> + {jwks_uri_type_of_config, only_jwks_uri} + end | Config0], + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case ?config(with_all_oauth_provider_settings, Config) of false -> configure_minimum_oauth_provider_settings(Config); true -> configure_all_oauth_provider_settings(Config); @@ -246,8 +348,13 @@ init_per_testcase(TestCase, Config) -> case ?config(group, Config) of https -> +<<<<<<< HEAD start_https_oauth_server(?AUTH_PORT, ?config(certsDir, Config), ListOfExpectations); +======= + start_https_oauth_server(?AUTH_PORT, ?config(rmq_certsdir, Config), + ListOfExpectations); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) _ -> do_nothing end, @@ -256,6 +363,10 @@ init_per_testcase(TestCase, Config) -> end_per_testcase(_, Config) -> application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), application:unset_env(rabbitmq_auth_backend_oauth2, issuer), +<<<<<<< HEAD +======= + application:unset_env(rabbitmq_auth_backend_oauth2, jwks_uri), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) application:unset_env(rabbitmq_auth_backend_oauth2, token_endpoint), application:unset_env(rabbitmq_auth_backend_oauth2, authorization_endpoint), application:unset_env(rabbitmq_auth_backend_oauth2, end_session_endpoint), @@ -263,8 +374,11 @@ end_per_testcase(_, Config) -> case ?config(group, Config) of https -> stop_https_auth_server(); +<<<<<<< HEAD without_all_oauth_providers_settings -> stop_https_auth_server(); +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) _ -> do_nothing end, @@ -280,11 +394,24 @@ end_per_group(with_default_oauth_provider, Config) -> end_per_group(_, Config) -> Config. +<<<<<<< HEAD +======= +build_openid_discovery_endpoint(Issuer) -> + build_openid_discovery_endpoint(Issuer, undefined, undefined). + +build_openid_discovery_endpoint(Issuer, Path) -> + build_openid_discovery_endpoint(Issuer, Path, undefined). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) get_openid_configuration(Config) -> ExpectedOAuthProvider = ?config(oauth_provider, Config), SslOptions = [{ssl, ExpectedOAuthProvider#oauth_provider.ssl_options}], {ok, ActualOpenId} = oauth2_client:get_openid_configuration( +<<<<<<< HEAD build_issuer("https"), +======= + build_openid_discovery_endpoint(build_issuer("https")), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) SslOptions), ExpectedOpenId = map_oauth_provider_to_openid_configuration(ExpectedOAuthProvider), assertOpenIdConfiguration(ExpectedOpenId, ActualOpenId). @@ -306,7 +433,11 @@ get_openid_configuration_returns_partial_payload(Config) -> SslOptions = [{ssl, ExpectedOAuthProvider0#oauth_provider.ssl_options}], {ok, Actual} = oauth2_client:get_openid_configuration( +<<<<<<< HEAD build_issuer("https"), +======= + build_openid_discovery_endpoint(build_issuer("https")), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) SslOptions), ExpectedOpenId = map_oauth_provider_to_openid_configuration(ExpectedOAuthProvider), assertOpenIdConfiguration(ExpectedOpenId, Actual). @@ -315,7 +446,11 @@ get_openid_configuration_using_path(Config) -> ExpectedOAuthProvider = ?config(oauth_provider, Config), SslOptions = [{ssl, ExpectedOAuthProvider#oauth_provider.ssl_options}], {ok, Actual} = oauth2_client:get_openid_configuration( +<<<<<<< HEAD build_issuer("https", ?ISSUER_PATH), +======= + build_openid_discovery_endpoint(build_issuer("https", ?ISSUER_PATH)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) SslOptions), ExpectedOpenId = map_oauth_provider_to_openid_configuration(ExpectedOAuthProvider), assertOpenIdConfiguration(ExpectedOpenId,Actual). @@ -323,18 +458,28 @@ get_openid_configuration_using_path_and_custom_endpoint(Config) -> ExpectedOAuthProvider = ?config(oauth_provider, Config), SslOptions = [{ssl, ExpectedOAuthProvider#oauth_provider.ssl_options}], {ok, Actual} = oauth2_client:get_openid_configuration( +<<<<<<< HEAD build_issuer("https", ?ISSUER_PATH), ?CUSTOM_OPENID_CONFIGURATION_ENDPOINT, SslOptions), +======= + build_openid_discovery_endpoint(build_issuer("https", ?ISSUER_PATH), + ?CUSTOM_OPENID_CONFIGURATION_ENDPOINT), SslOptions), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ExpectedOpenId = map_oauth_provider_to_openid_configuration(ExpectedOAuthProvider), assertOpenIdConfiguration(ExpectedOpenId, Actual). get_openid_configuration_using_custom_endpoint(Config) -> ExpectedOAuthProvider = ?config(oauth_provider, Config), SslOptions = [{ssl, ExpectedOAuthProvider#oauth_provider.ssl_options}], {ok, Actual} = oauth2_client:get_openid_configuration( +<<<<<<< HEAD build_issuer("https"), ?CUSTOM_OPENID_CONFIGURATION_ENDPOINT, SslOptions), +======= + build_openid_discovery_endpoint(build_issuer("https"), + ?CUSTOM_OPENID_CONFIGURATION_ENDPOINT), SslOptions), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ExpectedOpenId = map_oauth_provider_to_openid_configuration(ExpectedOAuthProvider), assertOpenIdConfiguration(ExpectedOpenId, Actual). @@ -399,6 +544,26 @@ grants_access_token(Config) -> ?assertEqual(proplists:get_value(token_type, JsonPayload), TokenType), ?assertEqual(proplists:get_value(access_token, JsonPayload), AccessToken). +<<<<<<< HEAD +======= +grants_access_token_optional_parameters(Config) -> + #{request := #{parameters := Parameters}, + response := [ {code, 200}, {content_type, _CT}, {payload, JsonPayload}] } + = lookup_expectation(token_endpoint, Config), + + AccessTokenRequest0 = build_access_token_request(Parameters), + AccessTokenRequest = AccessTokenRequest0#access_token_request{ + scope = "some-scope", + extra_parameters = [{"param1", "value1"}] + }, + {ok, #successful_access_token_response{access_token = AccessToken, + token_type = TokenType} } = + oauth2_client:get_access_token(?config(oauth_provider, Config), + AccessTokenRequest), + ?assertEqual(proplists:get_value(token_type, JsonPayload), TokenType), + ?assertEqual(proplists:get_value(access_token, JsonPayload), AccessToken). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) grants_refresh_token(Config) -> #{request := #{parameters := Parameters}, response := [ {code, 200}, {content_type, _CT}, {payload, JsonPayload}] } @@ -447,16 +612,26 @@ ssl_connection_error(Config) -> {error, {failed_connect, _} } = oauth2_client:get_access_token( ?config(oauth_provider_with_wrong_ca, Config), build_access_token_request(Parameters)). +<<<<<<< HEAD verify_get_oauth_provider_returns_oauth_provider_from_key_config() -> +======= +verify_get_oauth_provider_returns_root_oauth_provider() -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {ok, #oauth_provider{id = Id, issuer = Issuer, token_endpoint = TokenEndPoint, jwks_uri = Jwks_uri}} = oauth2_client:get_oauth_provider([issuer, token_endpoint, jwks_uri]), +<<<<<<< HEAD ExpectedIssuer = application:get_env(rabbitmq_auth_backend_oauth2, issuer, undefined), ExpectedTokenEndPoint = application:get_env(rabbitmq_auth_backend_oauth2, token_endpoint, undefined), ExpectedJwks_uri = proplists:get_value(jwks_url, application:get_env(rabbitmq_auth_backend_oauth2, key_config, [])), +======= + ExpectedIssuer = get_env(issuer), + ExpectedTokenEndPoint = get_env(token_endpoint), + ExpectedJwks_uri = get_env(jwks_uri), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?assertEqual(root, Id), ?assertEqual(ExpectedIssuer, Issuer), ?assertEqual(ExpectedTokenEndPoint, TokenEndPoint), @@ -468,15 +643,24 @@ verify_get_oauth_provider_returns_default_oauth_provider(DefaultOAuthProviderId) {ok, OAuthProvider2} = oauth2_client:get_oauth_provider(DefaultOAuthProviderId, [issuer, token_endpoint, jwks_uri]), +<<<<<<< HEAD ct:log("verify_get_oauth_provider_returns_default_oauth_provider ~p vs ~p", [OAuthProvider1, OAuthProvider2]), +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?assertEqual(OAuthProvider1, OAuthProvider2). get_oauth_provider(Config) -> case ?config(with_all_oauth_provider_settings, Config) of true -> +<<<<<<< HEAD case application:get_env(rabbitmq_auth_backend_oauth2, default_oauth_provider, undefined) of undefined -> verify_get_oauth_provider_returns_oauth_provider_from_key_config(); +======= + case get_env(default_oauth_provider) of + undefined -> + verify_get_oauth_provider_returns_root_oauth_provider(); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) DefaultOAuthProviderId -> verify_get_oauth_provider_returns_default_oauth_provider(DefaultOAuthProviderId) end; @@ -507,8 +691,12 @@ get_oauth_provider_given_oauth_provider_id(Config) -> [issuer, token_endpoint, jwks_uri, authorization_endpoint, end_session_endpoint]), +<<<<<<< HEAD OAuthProviders = application:get_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{}), +======= + OAuthProviders = get_env(oauth_providers, #{}), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ExpectedProvider = maps:get(Id, OAuthProviders, []), ?assertEqual(proplists:get_value(issuer, ExpectedProvider), Issuer), @@ -546,9 +734,27 @@ get_oauth_provider_given_oauth_provider_id(Config) -> Jwks_uri) end. +<<<<<<< HEAD + + +%%% HELPERS +======= +jwks_url_is_used_in_absense_of_jwks_uri(Config) -> + {ok, #oauth_provider{ + jwks_uri = Jwks_uri}} = oauth2_client:get_oauth_provider([jwks_uri]), + ?assertEqual( + proplists:get_value(jwks_url, get_env(key_config, []), undefined), + Jwks_uri). + +jwks_uri_takes_precedence_over_jwks_url(Config) -> + {ok, #oauth_provider{ + jwks_uri = Jwks_uri}} = oauth2_client:get_oauth_provider([jwks_uri]), + ?assertEqual(get_env(jwks_uri), Jwks_uri). %%% HELPERS + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) build_issuer(Scheme) -> build_issuer(Scheme, ""). build_issuer(Scheme, Path) -> @@ -565,10 +771,20 @@ build_token_endpoint_uri(Scheme) -> path => "/token"}). build_jwks_uri(Scheme) -> +<<<<<<< HEAD uri_string:recompose(#{scheme => Scheme, host => "localhost", port => rabbit_data_coercion:to_integer(?AUTH_PORT), path => "/certs"}). +======= + build_jwks_uri(Scheme, "/certs"). + +build_jwks_uri(Scheme, Path) -> + uri_string:recompose(#{scheme => Scheme, + host => "localhost", + port => rabbit_data_coercion:to_integer(?AUTH_PORT), + path => Path}). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) build_access_token_request(Request) -> #access_token_request { @@ -604,11 +820,19 @@ oauth_provider_to_proplist(#oauth_provider{ authorization_endpoint = AuthorizationEndpoint, ssl_options = SslOptions, jwks_uri = Jwks_uri}) -> +<<<<<<< HEAD [ { issuer, Issuer}, {token_endpoint, TokenEndpoint}, {end_session_endpoint, EndSessionEndpoint}, {authorization_endpoint, AuthorizationEndpoint}, { https, +======= + [ {issuer, Issuer}, + {token_endpoint, TokenEndpoint}, + {end_session_endpoint, EndSessionEndpoint}, + {authorization_endpoint, AuthorizationEndpoint}, + {https, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case SslOptions of undefined -> []; Value -> Value @@ -621,8 +845,11 @@ start_https_oauth_server(Port, CertsDir, Expectations) when is_list(Expectations {'_', [{Path, oauth_http_mock, Expected} || #{request := #{path := Path}} = Expected <- Expectations ]} ]), +<<<<<<< HEAD ct:log("start_https_oauth_server with expectation : ~p -> dispatch: ~p . certsDir: ~p", [Expectations, Dispatch, CertsDir]), +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {ok, _} = cowboy:start_tls( mock_http_auth_listener, [{port, Port}, @@ -633,8 +860,11 @@ start_https_oauth_server(Port, CertsDir, Expectations) when is_list(Expectations start_https_oauth_server(Port, CertsDir, #{request := #{path := Path}} = Expected) -> Dispatch = cowboy_router:compile([{'_', [{Path, oauth_http_mock, Expected}]}]), +<<<<<<< HEAD ct:log("start_https_oauth_server with expectation : ~p -> dispatch: ~p . certsDir: ~p", [Expected, Dispatch, CertsDir]), +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {ok, _} = cowboy:start_tls( mock_http_auth_listener, [{port, Port}, @@ -662,6 +892,14 @@ token(ExpiresIn) -> EncodedToken. +<<<<<<< HEAD +======= +get_env(Par) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par, undefined). +get_env(Par, Default) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par, Default). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) build_http_mock_behaviour(Request, Response) -> #{request => Request, response => Response}. diff --git a/deps/oauth2_client/test/unit_SUITE.erl b/deps/oauth2_client/test/unit_SUITE.erl index ab632ceedc68..d058a351f207 100644 --- a/deps/oauth2_client/test/unit_SUITE.erl +++ b/deps/oauth2_client/test/unit_SUITE.erl @@ -15,13 +15,25 @@ -compile(export_all). +<<<<<<< HEAD +======= +-import(oauth2_client, [build_openid_discovery_endpoint/3]). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -define(UTIL_MOD, oauth2_client_test_util). all() -> [ +<<<<<<< HEAD {group, ssl_options}, {group, merge}, {group, get_expiration_time} +======= + build_openid_discovery_endpoint, + {group, ssl_options}, + {group, merge}, + {group, get_expiration_time} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]. groups() -> @@ -45,8 +57,43 @@ groups() -> ]} ]. +<<<<<<< HEAD merge_oauth_provider(_) -> OAuthProvider = #oauth_provider{id = "some_id", ssl_options = [ {verify, verify_none} ]}, +======= +build_openid_discovery_endpoint(_) -> + Issuer = "https://issuer", + ?assertEqual(Issuer ++ ?DEFAULT_OPENID_CONFIGURATION_PATH, + build_openid_discovery_endpoint(Issuer, undefined, undefined)), + + IssuerWithPath = "https://issuer/v2", + ?assertEqual(IssuerWithPath ++ ?DEFAULT_OPENID_CONFIGURATION_PATH, + build_openid_discovery_endpoint(IssuerWithPath, undefined, undefined)), + + IssuerWithPathAndExtraPathSeparator = "https://issuer/v2/", + ?assertEqual("https://issuer/v2" ++ ?DEFAULT_OPENID_CONFIGURATION_PATH, + build_openid_discovery_endpoint(IssuerWithPathAndExtraPathSeparator, + undefined, undefined)), + + IssuerWithPath = "https://issuer/v2", + CustomPath = "/.well-known/other", + ?assertEqual(IssuerWithPath ++ CustomPath, + build_openid_discovery_endpoint(IssuerWithPath, CustomPath, undefined)), + + IssuerWithPath = "https://issuer/v2", + CustomPath = "/.well-known/other", + WithParams = [{"param1", "v1"}, {"param2", "v2"}], + ?assertEqual("https://issuer/v2/.well-known/other?param1=v1¶m2=v2", + build_openid_discovery_endpoint(IssuerWithPath, CustomPath, WithParams)). + + +merge_oauth_provider(_) -> + OAuthProvider = #oauth_provider{ + id = "some_id", + issuer = "https://issuer", + discovery_endpoint = "https://issuer/.well-known/openid_configuration", + ssl_options = [ {verify, verify_none} ]}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Proplist = [], Proplist1 = oauth2_client:merge_oauth_provider(OAuthProvider, Proplist), ?assertEqual([], Proplist), @@ -74,11 +121,33 @@ merge_oauth_provider(_) -> {end_session_endpoint, OAuthProvider4#oauth_provider.end_session_endpoint}, {authorization_endpoint, OAuthProvider4#oauth_provider.authorization_endpoint}, {token_endpoint, OAuthProvider4#oauth_provider.token_endpoint}], +<<<<<<< HEAD Proplist5). merge_openid_configuration(_) -> OpenIdConfiguration = #openid_configuration{}, OAuthProvider = #oauth_provider{id = "some_id", ssl_options = [ {verify, verify_none} ]}, +======= + Proplist5), + + % ensure id, issuer, ssl_options and discovery_endpoint are not affected + ?assertEqual(OAuthProvider#oauth_provider.id, + OAuthProvider4#oauth_provider.id), + ?assertEqual(OAuthProvider#oauth_provider.issuer, + OAuthProvider4#oauth_provider.issuer), + ?assertEqual(OAuthProvider#oauth_provider.discovery_endpoint, + OAuthProvider4#oauth_provider.discovery_endpoint), + ?assertEqual(OAuthProvider#oauth_provider.ssl_options, + OAuthProvider4#oauth_provider.ssl_options). + +merge_openid_configuration(_) -> + OpenIdConfiguration = #openid_configuration{}, + OAuthProvider = #oauth_provider{ + id = "some_id", + issuer = "https://issuer", + discovery_endpoint = "https://issuer/.well-known/openid_configuration", + ssl_options = [ {verify, verify_none} ]}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) OAuthProvider1 = oauth2_client:merge_openid_configuration( OpenIdConfiguration, OAuthProvider), ?assertEqual(OAuthProvider#oauth_provider.id, OAuthProvider1#oauth_provider.id), @@ -125,7 +194,21 @@ merge_openid_configuration(_) -> ?assertEqual(OpenIdConfiguration2#openid_configuration.end_session_endpoint, OAuthProvider5#oauth_provider.end_session_endpoint), ?assertEqual(OpenIdConfiguration1#openid_configuration.jwks_uri, +<<<<<<< HEAD OAuthProvider5#oauth_provider.jwks_uri). +======= + OAuthProvider5#oauth_provider.jwks_uri), + + % ensure id, issuer, ssl_options and discovery_endpoint are not affected + ?assertEqual(OAuthProvider#oauth_provider.id, + OAuthProvider5#oauth_provider.id), + ?assertEqual(OAuthProvider#oauth_provider.issuer, + OAuthProvider5#oauth_provider.issuer), + ?assertEqual(OAuthProvider#oauth_provider.discovery_endpoint, + OAuthProvider5#oauth_provider.discovery_endpoint), + ?assertEqual(OAuthProvider#oauth_provider.ssl_options, + OAuthProvider5#oauth_provider.ssl_options). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) no_ssl_options_triggers_verify_peer(_) -> diff --git a/deps/rabbit/BUILD.bazel b/deps/rabbit/BUILD.bazel index 1b5d65cd4fed..9bbb4aff0f89 100644 --- a/deps/rabbit/BUILD.bazel +++ b/deps/rabbit/BUILD.bazel @@ -462,6 +462,16 @@ rabbitmq_integration_suite( ) rabbitmq_integration_suite( +<<<<<<< HEAD +======= + name = "msg_size_metrics_SUITE", + runtime_deps = [ + "//deps/rabbitmq_amqp_client:erlang_app", + ], +) + +rabbitmq_integration_suite( +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) name = "list_consumers_sanity_check_SUITE", size = "medium", ) @@ -857,6 +867,15 @@ rabbitmq_integration_suite( rabbitmq_integration_suite( name = "topic_permission_SUITE", size = "medium", +<<<<<<< HEAD +======= + additional_beam = [ + ":test_amqp_utils_beam", + ], + runtime_deps = [ + "//deps/rabbitmq_amqp_client:erlang_app", + ], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ) rabbitmq_integration_suite( @@ -977,6 +996,14 @@ rabbitmq_integration_suite( ) rabbitmq_suite( +<<<<<<< HEAD +======= + name = "unit_msg_size_metrics_SUITE", + size = "small", +) + +rabbitmq_suite( +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) name = "unit_operator_policy_SUITE", size = "small", deps = [ @@ -1197,6 +1224,10 @@ rabbitmq_integration_suite( name = "amqp_client_SUITE", size = "large", additional_beam = [ +<<<<<<< HEAD +======= + ":test_amqp_utils_beam", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ":test_event_recorder_beam", ], shard_count = 3, @@ -1206,6 +1237,19 @@ rabbitmq_integration_suite( ) rabbitmq_integration_suite( +<<<<<<< HEAD +======= + name = "amqp_filtex_SUITE", + additional_beam = [ + ":test_amqp_utils_beam", + ], + runtime_deps = [ + "//deps/rabbitmq_amqp_client:erlang_app", + ], +) + +rabbitmq_integration_suite( +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) name = "amqp_proxy_protocol_SUITE", size = "medium", ) @@ -1225,6 +1269,10 @@ rabbitmq_integration_suite( rabbitmq_integration_suite( name = "amqp_auth_SUITE", additional_beam = [ +<<<<<<< HEAD +======= + ":test_amqp_utils_beam", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ":test_event_recorder_beam", ], shard_count = 2, @@ -1235,6 +1283,12 @@ rabbitmq_integration_suite( rabbitmq_integration_suite( name = "amqp_address_SUITE", +<<<<<<< HEAD +======= + additional_beam = [ + ":test_amqp_utils_beam", + ], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) shard_count = 2, runtime_deps = [ "//deps/rabbitmq_amqp_client:erlang_app", @@ -1348,6 +1402,10 @@ eunit( ":test_clustering_utils_beam", ":test_event_recorder_beam", ":test_rabbit_ct_hook_beam", +<<<<<<< HEAD +======= + ":test_amqp_utils_beam", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ], target = ":test_erlang_app", test_env = { diff --git a/deps/rabbit/Makefile b/deps/rabbit/Makefile index 79ad84990739..253241b4157a 100644 --- a/deps/rabbit/Makefile +++ b/deps/rabbit/Makefile @@ -260,7 +260,11 @@ define ct_master.erl endef PARALLEL_CT_SET_1_A = amqp_client unit_cluster_formation_locking_mocks unit_cluster_formation_sort_nodes unit_collections unit_config_value_encryption unit_connection_tracking +<<<<<<< HEAD PARALLEL_CT_SET_1_B = amqp_address amqp_auth amqp_credit_api_v2 amqp_system signal_handling single_active_consumer unit_access_control_authn_authz_context_propagation unit_access_control_credential_validation unit_amqp091_content_framing unit_amqp091_server_properties unit_app_management +======= +PARALLEL_CT_SET_1_B = amqp_address amqp_auth amqp_credit_api_v2 amqp_filtex amqp_system signal_handling single_active_consumer unit_access_control_authn_authz_context_propagation unit_access_control_credential_validation unit_amqp091_content_framing unit_amqp091_server_properties unit_app_management +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) PARALLEL_CT_SET_1_C = amqp_proxy_protocol amqpl_consumer_ack amqpl_direct_reply_to backing_queue bindings rabbit_db_maintenance rabbit_db_msup rabbit_db_policy rabbit_db_queue rabbit_db_topic_exchange rabbit_direct_reply_to_prop cluster_limit cluster_minority term_to_binary_compat_prop topic_permission transactions unicode unit_access_control PARALLEL_CT_SET_1_D = amqqueue_backward_compatibility channel_interceptor channel_operation_timeout classic_queue classic_queue_prop config_schema peer_discovery_dns peer_discovery_tmp_hidden_node per_node_limit per_user_connection_channel_limit @@ -276,7 +280,11 @@ PARALLEL_CT_SET_3_D = metadata_store_phase1 metrics mirrored_supervisor msg_stor PARALLEL_CT_SET_4_A = clustering_events rabbit_local_random_exchange rabbit_message_interceptor rabbitmq_4_0_deprecations unit_pg_local unit_plugin_directories unit_plugin_versioning unit_policy_validators unit_priority_queue PARALLEL_CT_SET_4_B = per_user_connection_tracking per_vhost_connection_limit rabbit_fifo_dlx_integration rabbit_fifo_int +<<<<<<< HEAD PARALLEL_CT_SET_4_C = per_vhost_msg_store per_vhost_queue_limit priority_queue upgrade_preparation vhost +======= +PARALLEL_CT_SET_4_C = msg_size_metrics unit_msg_size_metrics per_vhost_msg_store per_vhost_queue_limit priority_queue upgrade_preparation vhost +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) PARALLEL_CT_SET_4_D = per_user_connection_channel_tracking product_info publisher_confirms_parallel queue_type rabbitmq_queues_cli_integration rabbitmqctl_integration rabbitmqctl_shutdown routing PARALLEL_CT_SET_1 = $(sort $(PARALLEL_CT_SET_1_A) $(PARALLEL_CT_SET_1_B) $(PARALLEL_CT_SET_1_C) $(PARALLEL_CT_SET_1_D)) diff --git a/deps/rabbit/app.bzl b/deps/rabbit/app.bzl index cfd7bc31a469..00a1291e16b6 100644 --- a/deps/rabbit/app.bzl +++ b/deps/rabbit/app.bzl @@ -45,6 +45,10 @@ def all_beam_files(name = "all_beam_files"): "src/rabbit_access_control.erl", "src/rabbit_alarm.erl", "src/rabbit_amqp1_0.erl", +<<<<<<< HEAD +======= + "src/rabbit_amqp_filtex.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_amqp_management.erl", "src/rabbit_amqp_reader.erl", "src/rabbit_amqp_session.erl", @@ -169,6 +173,10 @@ def all_beam_files(name = "all_beam_files"): "src/rabbit_metrics.erl", "src/rabbit_mirror_queue_misc.erl", "src/rabbit_mnesia.erl", +<<<<<<< HEAD +======= + "src/rabbit_msg_size_metrics.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_msg_store.erl", "src/rabbit_msg_store_gc.erl", "src/rabbit_networking.erl", @@ -302,6 +310,10 @@ def all_test_beam_files(name = "all_test_beam_files"): "src/rabbit_access_control.erl", "src/rabbit_alarm.erl", "src/rabbit_amqp1_0.erl", +<<<<<<< HEAD +======= + "src/rabbit_amqp_filtex.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_amqp_management.erl", "src/rabbit_amqp_reader.erl", "src/rabbit_amqp_session.erl", @@ -426,6 +438,10 @@ def all_test_beam_files(name = "all_test_beam_files"): "src/rabbit_metrics.erl", "src/rabbit_mirror_queue_misc.erl", "src/rabbit_mnesia.erl", +<<<<<<< HEAD +======= + "src/rabbit_msg_size_metrics.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_msg_store.erl", "src/rabbit_msg_store_gc.erl", "src/rabbit_networking.erl", @@ -544,6 +560,10 @@ def all_srcs(name = "all_srcs"): name = "private_hdrs", srcs = [ "src/mirrored_supervisor.hrl", +<<<<<<< HEAD +======= + "src/rabbit_amqp_reader.hrl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_feature_flags.hrl", "src/rabbit_ff_registry.hrl", "src/rabbit_fifo.hrl", @@ -578,6 +598,10 @@ def all_srcs(name = "all_srcs"): "src/rabbit_access_control.erl", "src/rabbit_alarm.erl", "src/rabbit_amqp1_0.erl", +<<<<<<< HEAD +======= + "src/rabbit_amqp_filtex.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_amqp_management.erl", "src/rabbit_amqp_reader.erl", "src/rabbit_amqp_session.erl", @@ -705,6 +729,10 @@ def all_srcs(name = "all_srcs"): "src/rabbit_metrics.erl", "src/rabbit_mirror_queue_misc.erl", "src/rabbit_mnesia.erl", +<<<<<<< HEAD +======= + "src/rabbit_msg_size_metrics.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_msg_store.erl", "src/rabbit_msg_store_gc.erl", "src/rabbit_networking.erl", @@ -1553,7 +1581,11 @@ def test_suite_beam_files(name = "test_suite_beam_files"): outs = ["test/topic_permission_SUITE.beam"], app_name = "rabbit", erlc_opts = "//:test_erlc_opts", +<<<<<<< HEAD deps = ["//deps/amqp_client:erlang_app"], +======= + deps = ["//deps/amqp10_common:erlang_app", "//deps/amqp_client:erlang_app"], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ) erlang_bytecode( name = "transactions_SUITE_beam_files", @@ -1710,6 +1742,17 @@ def test_suite_beam_files(name = "test_suite_beam_files"): deps = ["//deps/amqp_client:erlang_app", "//deps/rabbitmq_ct_helpers:erlang_app"], ) erlang_bytecode( +<<<<<<< HEAD +======= + name = "unit_msg_size_metrics_SUITE_beam_files", + testonly = True, + srcs = ["test/unit_msg_size_metrics_SUITE.erl"], + outs = ["test/unit_msg_size_metrics_SUITE.beam"], + app_name = "rabbit", + erlc_opts = "//:test_erlc_opts", + ) + erlang_bytecode( +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) name = "unit_operator_policy_SUITE_beam_files", testonly = True, srcs = ["test/unit_operator_policy_SUITE.erl"], @@ -2178,3 +2221,33 @@ def test_suite_beam_files(name = "test_suite_beam_files"): app_name = "rabbit", erlc_opts = "//:test_erlc_opts", ) +<<<<<<< HEAD +======= + erlang_bytecode( + name = "msg_size_metrics_SUITE_beam_files", + testonly = True, + srcs = ["test/msg_size_metrics_SUITE.erl"], + outs = ["test/msg_size_metrics_SUITE.beam"], + app_name = "rabbit", + erlc_opts = "//:test_erlc_opts", + deps = ["//deps/amqp_client:erlang_app"], + ) + erlang_bytecode( + name = "amqp_filtex_SUITE_beam_files", + testonly = True, + srcs = ["test/amqp_filtex_SUITE.erl"], + outs = ["test/amqp_filtex_SUITE.beam"], + app_name = "rabbit", + erlc_opts = "//:test_erlc_opts", + deps = ["//deps/amqp10_common:erlang_app"], + ) + erlang_bytecode( + name = "test_amqp_utils_beam", + testonly = True, + srcs = ["test/amqp_utils.erl"], + outs = ["test/amqp_utils.beam"], + app_name = "rabbit", + erlc_opts = "//:test_erlc_opts", + deps = ["//deps/amqp10_common:erlang_app"], + ) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbit/ct.test.spec b/deps/rabbit/ct.test.spec index 6740594c1500..25b6d476a950 100644 --- a/deps/rabbit/ct.test.spec +++ b/deps/rabbit/ct.test.spec @@ -16,6 +16,10 @@ , amqp_auth_SUITE , amqp_client_SUITE , amqp_credit_api_v2_SUITE +<<<<<<< HEAD +======= +, amqp_filtex_SUITE +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) , amqp_proxy_protocol_SUITE , amqp_system_SUITE , amqpl_consumer_ack_SUITE @@ -65,7 +69,12 @@ ]}. {define, 'Set4', [ +<<<<<<< HEAD peer_discovery_dns_SUITE +======= + msg_size_metrics_SUITE +, peer_discovery_dns_SUITE +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) , peer_discovery_tmp_hidden_node_SUITE , per_node_limit_SUITE , per_user_connection_channel_limit_SUITE @@ -80,6 +89,10 @@ , product_info_SUITE , proxy_protocol_SUITE , publisher_confirms_parallel_SUITE +<<<<<<< HEAD +======= +, unit_msg_size_metrics_SUITE +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]}. {define, 'Set5', [ diff --git a/deps/rabbit/include/rabbit_amqp.hrl b/deps/rabbit/include/rabbit_amqp.hrl index 84e98d5d565d..54b198685980 100644 --- a/deps/rabbit/include/rabbit_amqp.hrl +++ b/deps/rabbit/include/rabbit_amqp.hrl @@ -37,6 +37,10 @@ [pid, frame_max, timeout, +<<<<<<< HEAD +======= + container_id, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) vhost, user, node diff --git a/deps/rabbit/src/mc.erl b/deps/rabbit/src/mc.erl index b122c4780110..c7b06b5b9818 100644 --- a/deps/rabbit/src/mc.erl +++ b/deps/rabbit/src/mc.erl @@ -26,6 +26,10 @@ priority/1, set_ttl/2, x_header/2, +<<<<<<< HEAD +======= + x_headers/1, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) routing_headers/2, exchange/1, routing_keys/1, @@ -88,6 +92,10 @@ {timestamp, non_neg_integer()} | {list, [tagged_value()]} | {map, [{tagged_value(), tagged_value()}]} | +<<<<<<< HEAD +======= + {array, atom(), [tagged_value()]} | +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) null | undefined. @@ -104,11 +112,23 @@ {MetadataSize :: non_neg_integer(), PayloadSize :: non_neg_integer()}. +<<<<<<< HEAD %% retrieve and x- header from the protocol data +======= +%% retrieve an x- header from the protocol data +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% the return value should be tagged with an AMQP 1.0 type -callback x_header(binary(), proto_state()) -> tagged_value(). +<<<<<<< HEAD +======= +%% retrieve x- headers from the protocol data +%% the return values should be tagged with an AMQP 1.0 type +-callback x_headers(proto_state()) -> + #{binary() => tagged_value()}. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% retrieve a property field from the protocol data %% e.g. message_id, correlation_id -callback property(atom(), proto_state()) -> @@ -148,7 +168,11 @@ init(Proto, Data, Anns) -> -spec init(protocol(), term(), annotations(), environment()) -> state(). init(Proto, Data, Anns0, Env) -> {ProtoData, ProtoAnns} = Proto:init(Data), +<<<<<<< HEAD Anns1 = case map_size(Env) == 0 of +======= + Anns1 = case map_size(Env) =:= 0 of +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) true -> Anns0; false -> Anns0#{env => Env} end, @@ -214,6 +238,28 @@ x_header(Key, #?MODULE{protocol = Proto, x_header(Key, BasicMsg) -> mc_compat:x_header(Key, BasicMsg). +<<<<<<< HEAD +======= +-spec x_headers(state()) -> + #{binary() => tagged_value()}. +x_headers(#?MODULE{protocol = Proto, + annotations = Anns, + data = Data}) -> + %% x-headers may be have been added to the annotations map. + New = maps:filtermap( + fun(Key, Val) -> + case mc_util:is_x_header(Key) of + true -> + {true, mc_util:infer_type(Val)}; + false -> + false + end + end, Anns), + maps:merge(Proto:x_headers(Data), New); +x_headers(BasicMsg) -> + mc_compat:x_headers(BasicMsg). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec routing_headers(state(), [x_headers | complex_types]) -> #{binary() => property_value()}. routing_headers(#?MODULE{protocol = Proto, @@ -301,7 +347,11 @@ message_id(BasicMsg) -> mc_compat:message_id(BasicMsg). -spec property(atom(), state()) -> +<<<<<<< HEAD {utf8, binary()} | undefined. +======= + tagged_value(). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) property(Property, #?MODULE{protocol = Proto, data = Data}) -> Proto:property(Property, Data); diff --git a/deps/rabbit/src/mc_amqp.erl b/deps/rabbit/src/mc_amqp.erl index be63597c3f96..27897271f85b 100644 --- a/deps/rabbit/src/mc_amqp.erl +++ b/deps/rabbit/src/mc_amqp.erl @@ -8,6 +8,10 @@ init/1, size/1, x_header/2, +<<<<<<< HEAD +======= + x_headers/1, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) property/2, routing_headers/2, convert_to/3, @@ -21,7 +25,11 @@ -define(MESSAGE_ANNOTATIONS_GUESS_SIZE, 100). +<<<<<<< HEAD -define(SIMPLE_VALUE(V), +======= +-define(IS_SIMPLE_VALUE(V), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) is_binary(V) orelse is_number(V) orelse is_boolean(V)). @@ -125,6 +133,12 @@ size(#v1{message_annotations = MA, x_header(Key, Msg) -> message_annotation(Key, Msg, undefined). +<<<<<<< HEAD +======= +x_headers(Msg) -> + #{K => V || {{_T, K}, V} <- message_annotations(Msg)}. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) property(_Prop, #msg_body_encoded{properties = undefined}) -> undefined; property(Prop, #msg_body_encoded{properties = Props}) -> @@ -145,6 +159,7 @@ property(Prop, #v1{bare_and_footer = Bin, Props = amqp10_framing:decode(PropsDescribed), property0(Prop, Props). +<<<<<<< HEAD property0(correlation_id, #'v1_0.properties'{correlation_id = Corr}) -> Corr; property0(message_id, #'v1_0.properties'{message_id = MsgId}) -> @@ -155,6 +170,34 @@ property0(subject, #'v1_0.properties'{subject = Subject}) -> Subject; property0(to, #'v1_0.properties'{to = To}) -> To; +======= +property0(message_id, #'v1_0.properties'{message_id = Val}) -> + Val; +property0(user_id, #'v1_0.properties'{user_id = Val}) -> + Val; +property0(to, #'v1_0.properties'{to = Val}) -> + Val; +property0(subject, #'v1_0.properties'{subject = Val}) -> + Val; +property0(reply_to, #'v1_0.properties'{reply_to = Val}) -> + Val; +property0(correlation_id, #'v1_0.properties'{correlation_id = Val}) -> + Val; +property0(content_type, #'v1_0.properties'{content_type = Val}) -> + Val; +property0(content_encoding, #'v1_0.properties'{content_encoding = Val}) -> + Val; +property0(absolute_expiry_time, #'v1_0.properties'{absolute_expiry_time = Val}) -> + Val; +property0(creation_time, #'v1_0.properties'{creation_time = Val}) -> + Val; +property0(group_id, #'v1_0.properties'{group_id = Val}) -> + Val; +property0(group_sequence, #'v1_0.properties'{group_sequence = Val}) -> + Val; +property0(reply_to_group_id, #'v1_0.properties'{reply_to_group_id = Val}) -> + Val; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) property0(_Prop, #'v1_0.properties'{}) -> undefined. @@ -454,7 +497,11 @@ message_annotations_as_simple_map(#v1{message_annotations = Content}) -> message_annotations_as_simple_map0(Content) -> %% the section record format really is terrible lists:filtermap(fun({{symbol, K}, {_T, V}}) +<<<<<<< HEAD when ?SIMPLE_VALUE(V) -> +======= + when ?IS_SIMPLE_VALUE(V) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {true, {K, V}}; (_) -> false @@ -480,7 +527,11 @@ application_properties_as_simple_map( application_properties_as_simple_map0(Content, L) -> %% the section record format really is terrible lists:foldl(fun({{utf8, K}, {_T, V}}, Acc) +<<<<<<< HEAD when ?SIMPLE_VALUE(V) -> +======= + when ?IS_SIMPLE_VALUE(V) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) [{K, V} | Acc]; ({{utf8, K}, V}, Acc) when V =:= undefined orelse is_boolean(V) -> @@ -602,11 +653,16 @@ encode_deaths(Deaths) -> {map, Map} end, Deaths). +<<<<<<< HEAD essential_properties(#msg_body_encoded{message_annotations = MA} = Msg) -> +======= +essential_properties(Msg) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Durable = get_property(durable, Msg), Priority = get_property(priority, Msg), Timestamp = get_property(timestamp, Msg), Ttl = get_property(ttl, Msg), +<<<<<<< HEAD Anns0 = #{?ANN_DURABLE => Durable}, Anns = maps_put_truthy( ?ANN_PRIORITY, Priority, @@ -640,3 +696,13 @@ essential_properties(#msg_body_encoded{message_annotations = MA} = Msg) -> Acc end, Anns, MA) end. +======= + Anns = #{?ANN_DURABLE => Durable}, + maps_put_truthy( + ?ANN_PRIORITY, Priority, + maps_put_truthy( + ?ANN_TIMESTAMP, Timestamp, + maps_put_truthy( + ttl, Ttl, + Anns))). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbit/src/mc_amqpl.erl b/deps/rabbit/src/mc_amqpl.erl index 723e60cd3f79..b6507e06f7db 100644 --- a/deps/rabbit/src/mc_amqpl.erl +++ b/deps/rabbit/src/mc_amqpl.erl @@ -11,6 +11,10 @@ init/1, size/1, x_header/2, +<<<<<<< HEAD +======= + x_headers/1, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) routing_headers/2, convert_to/3, convert_from/3, @@ -42,7 +46,10 @@ -define(AMQP10_FOOTER, <<"x-amqp-1.0-footer">>). -define(PROTOMOD, rabbit_framing_amqp_0_9_1). -define(CLASS_ID, 60). +<<<<<<< HEAD -define(LONGSTR_UTF8_LIMIT, 4096). +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -opaque state() :: #content{}. @@ -273,6 +280,26 @@ x_header(Key, #content{properties = none} = Content0) -> Content = rabbit_binary_parser:ensure_content_decoded(Content0), x_header(Key, Content). +<<<<<<< HEAD +======= +x_headers(#content{properties = #'P_basic'{headers = undefined}}) -> + #{}; +x_headers(#content{properties = #'P_basic'{headers = Headers}}) -> + L = lists:filtermap( + fun({Name, Type, Val}) -> + case mc_util:is_x_header(Name) of + true -> + {true, {Name, from_091(Type, Val)}}; + false -> + false + end + end, Headers), + maps:from_list(L); +x_headers(#content{properties = none} = Content0) -> + Content = rabbit_binary_parser:ensure_content_decoded(Content0), + x_headers(Content). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) property(Prop, Content) -> mc_util:infer_type(mc_compat:get_property(Prop, Content)). @@ -664,6 +691,7 @@ wrap(_Type, undefined) -> wrap(Type, Val) -> {Type, Val}. +<<<<<<< HEAD from_091(longstr, V) when is_binary(V) andalso byte_size(V) =< ?LONGSTR_UTF8_LIMIT -> @@ -671,12 +699,19 @@ from_091(longstr, V) %% it _may_ still be valid utf8 but checking this for every longstr header %% value is going to be excessively slow case mc_util:is_utf8_no_null(V) of +======= +from_091(longstr, V) -> + case mc_util:is_utf8_no_null_limited(V) of +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) true -> {utf8, V}; false -> {binary, V} end; +<<<<<<< HEAD from_091(longstr, V) -> {binary, V}; +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) from_091(long, V) -> {long, V}; from_091(unsignedbyte, V) -> {ubyte, V}; from_091(short, V) -> {short, V}; @@ -707,7 +742,10 @@ supported_header_value_type(table) -> supported_header_value_type(_) -> true. +<<<<<<< HEAD +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) amqp10_map_get(_K, []) -> undefined; amqp10_map_get(K, Tuples) -> diff --git a/deps/rabbit/src/mc_compat.erl b/deps/rabbit/src/mc_compat.erl index 056905239d96..fa87d732d4a1 100644 --- a/deps/rabbit/src/mc_compat.erl +++ b/deps/rabbit/src/mc_compat.erl @@ -20,6 +20,10 @@ priority/1, set_ttl/2, x_header/2, +<<<<<<< HEAD +======= + x_headers/1, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) routing_headers/2, %%% convert_to/2, @@ -138,6 +142,12 @@ set_ttl(Value, #basic_message{content = Content0} = Msg) -> x_header(Key,#basic_message{content = Content}) -> mc_amqpl:x_header(Key, Content). +<<<<<<< HEAD +======= +x_headers(#basic_message{content = Content}) -> + mc_amqpl:x_headers(Content). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) routing_headers(#basic_message{content = Content}, Opts) -> mc_amqpl:routing_headers(Content, Opts). diff --git a/deps/rabbit/src/mc_util.erl b/deps/rabbit/src/mc_util.erl index 1f20d15699db..0a1af9d2b4db 100644 --- a/deps/rabbit/src/mc_util.erl +++ b/deps/rabbit/src/mc_util.erl @@ -3,6 +3,10 @@ -include("mc.hrl"). -export([is_valid_shortstr/1, +<<<<<<< HEAD +======= + is_utf8_no_null_limited/1, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) is_utf8_no_null/1, uuid_to_urn_string/1, urn_string_to_uuid/1, @@ -12,12 +16,30 @@ is_x_header/1 ]). +<<<<<<< HEAD +======= +-define(UTF8_SCAN_LIMIT, 4096). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec is_valid_shortstr(term()) -> boolean(). is_valid_shortstr(Bin) when ?IS_SHORTSTR_LEN(Bin) -> is_utf8_no_null(Bin); is_valid_shortstr(_) -> false. +<<<<<<< HEAD +======= +-spec is_utf8_no_null_limited(term()) -> boolean(). +is_utf8_no_null_limited(Bin) + when byte_size(Bin) =< ?UTF8_SCAN_LIMIT -> + is_utf8_no_null(Bin); +is_utf8_no_null_limited(_Term) -> + %% If longer than 4096 bytes, just assume it's not UTF-8. + %% It _may_ still be valid UTF-8 but checking this + %% on the hot path is going to be excessively slow. + false. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec is_utf8_no_null(term()) -> boolean(). is_utf8_no_null(Term) -> utf8_scan(Term, fun (C) -> C > 0 end). @@ -61,7 +83,11 @@ utf8_string_is_ascii(UTF8String) -> amqp_map_get(Key, {map, List}, Default) -> amqp_map_get(Key, List, Default); amqp_map_get(Key, List, Default) when is_list(List) -> +<<<<<<< HEAD case lists:search(fun ({{_, K}, _}) -> K == Key end, List) of +======= + case lists:search(fun ({{_, K}, _}) -> K =:= Key end, List) of +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {value, {_K, V}} -> V; false -> diff --git a/deps/rabbit/src/rabbit_access_control.erl b/deps/rabbit/src/rabbit_access_control.erl index cfc8b591eb3f..43d702fbaca1 100644 --- a/deps/rabbit/src/rabbit_access_control.erl +++ b/deps/rabbit/src/rabbit_access_control.erl @@ -249,7 +249,11 @@ check_user_id0(ClaimedUserName, #user{username = ActualUserName, end. -spec update_state(User :: rabbit_types:user(), NewState :: term()) -> +<<<<<<< HEAD {'ok', rabbit_types:auth_user()} | +======= + {'ok', rabbit_types:user()} | +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {'refused', string()} | {'error', any()}. diff --git a/deps/rabbit/src/rabbit_amqp_filtex.erl b/deps/rabbit/src/rabbit_amqp_filtex.erl new file mode 100644 index 000000000000..5687c26c7b76 --- /dev/null +++ b/deps/rabbit/src/rabbit_amqp_filtex.erl @@ -0,0 +1,200 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2023 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. + +%% AMQP Filter Expressions Version 1.0 Working Draft 09 +%% https://groups.oasis-open.org/higherlogic/ws/public/document?document_id=66227 +-module(rabbit_amqp_filtex). + +-include_lib("amqp10_common/include/amqp10_filtex.hrl"). + +-export([validate/1, + filter/2]). + +-type simple_type() :: number() | binary() | atom(). +-type affix() :: {suffix, non_neg_integer(), binary()} | + {prefix, non_neg_integer(), binary()}. +-type filter_expression_value() :: simple_type() | affix(). +-type filter_expression() :: {properties, [{FieldName :: atom(), filter_expression_value()}]} | + {application_properties, [{binary(), filter_expression_value()}]}. +-type filter_expressions() :: [filter_expression()]. +-export_type([filter_expressions/0]). + +-spec validate(tuple()) -> + {ok, filter_expression()} | error. +validate({described, Descriptor, {map, KVList}}) -> + try validate0(Descriptor, KVList) + catch throw:{?MODULE, _, _} -> + error + end; +validate(_) -> + error. + +-spec filter(filter_expressions(), mc:state()) -> + boolean(). +filter(Filters, Mc) -> + %% "A message will pass through a filter-set if and only if + %% it passes through each of the named filters." [3.5.8] + lists:all(fun(Filter) -> + filter0(Filter, Mc) + end, Filters). + +%%%%%%%%%%%%%%%% +%%% Internal %%% +%%%%%%%%%%%%%%%% + +filter0({properties, KVList}, Mc) -> + %% "The filter evaluates to true if all properties enclosed in the filter expression + %% match the respective properties in the message." + %% [filtex-v1.0-wd09 4.2.4] + lists:all(fun({FieldName, RefVal}) -> + TaggedVal = mc:property(FieldName, Mc), + Val = unwrap(TaggedVal), + match_simple_type(RefVal, Val) + end, KVList); +filter0({application_properties, KVList}, Mc) -> + AppProps = mc:routing_headers(Mc, []), + %% "The filter evaluates to true if all properties enclosed in the filter expression + %% match the respective entries in the application-properties section in the message." + %% [filtex-v1.0-wd09 4.2.5] + lists:all(fun({Key, RefVal}) -> + case AppProps of + #{Key := Val} -> + match_simple_type(RefVal, Val); + _ -> + false + end + end, KVList). + +%% [filtex-v1.0-wd09 4.1.1] +%% "A reference field value in a property filter expression matches +%% its corresponding message metadata field value if: +%% [...] +match_simple_type(null, _Val) -> + %% * The reference field value is NULL + true; +match_simple_type({suffix, SuffixSize, Suffix}, Val) -> + %% * Suffix. The message metadata field matches the expression if the ordinal values of the + %% characters of the suffix expression equal the ordinal values of the same number of + %% characters trailing the message metadata field value. + case is_binary(Val) of + true -> + case Val of + <<_:(size(Val) - SuffixSize)/binary, Suffix:SuffixSize/binary>> -> + true; + _ -> + false + end; + false -> + false + end; +match_simple_type({prefix, PrefixSize, Prefix}, Val) -> + %% * Prefix. The message metadata field matches the expression if the ordinal values of the + %% characters of the prefix expression equal the ordinal values of the same number of + %% characters leading the message metadata field value. + case Val of + <> -> + true; + _ -> + false + end; +match_simple_type(RefVal, Val) -> + %% * the reference field value is of a floating-point or integer number type + %% and the message metadata field is of a different floating-point or integer number type, + %% the reference value and the metadata field value are within the value range of both types, + %% and the values are equal when treated as a floating-point" + RefVal == Val. + +validate0(Descriptor, KVList) when + (Descriptor =:= {symbol, ?DESCRIPTOR_NAME_PROPERTIES_FILTER} orelse + Descriptor =:= {ulong, ?DESCRIPTOR_CODE_PROPERTIES_FILTER}) andalso + KVList =/= [] -> + validate_props(KVList, []); +validate0(Descriptor, KVList) when + (Descriptor =:= {symbol, ?DESCRIPTOR_NAME_APPLICATION_PROPERTIES_FILTER} orelse + Descriptor =:= {ulong, ?DESCRIPTOR_CODE_APPLICATION_PROPERTIES_FILTER}) andalso + KVList =/= [] -> + validate_app_props(KVList, []); +validate0(_, _) -> + error. + +validate_props([], Acc) -> + {ok, {properties, lists:reverse(Acc)}}; +validate_props([{{symbol, <<"message-id">>}, TaggedVal} | Rest], Acc) -> + case parse_message_id(TaggedVal) of + {ok, Val} -> + validate_props(Rest, [{message_id, Val} | Acc]); + error -> + error + end; +validate_props([{{symbol, <<"user-id">>}, {binary, Val}} | Rest], Acc) -> + validate_props(Rest, [{user_id, Val} | Acc]); +validate_props([{{symbol, <<"to">>}, {utf8, Val}} | Rest], Acc) -> + validate_props(Rest, [{to, parse_string_modifier_prefix(Val)} | Acc]); +validate_props([{{symbol, <<"subject">>}, {utf8, Val}} | Rest], Acc) -> + validate_props(Rest, [{subject, parse_string_modifier_prefix(Val)} | Acc]); +validate_props([{{symbol, <<"reply-to">>}, {utf8, Val}} | Rest], Acc) -> + validate_props(Rest, [{reply_to, parse_string_modifier_prefix(Val)} | Acc]); +validate_props([{{symbol, <<"correlation-id">>}, TaggedVal} | Rest], Acc) -> + case parse_message_id(TaggedVal) of + {ok, Val} -> + validate_props(Rest, [{correlation_id, Val} | Acc]); + error -> + error + end; +validate_props([{{symbol, <<"content-type">>}, {symbol, Val}} | Rest], Acc) -> + validate_props(Rest, [{content_type, Val} | Acc]); +validate_props([{{symbol, <<"content-encoding">>}, {symbol, Val}} | Rest], Acc) -> + validate_props(Rest, [{content_encoding, Val} | Acc]); +validate_props([{{symbol, <<"absolute-expiry-time">>}, {timestamp, Val}} | Rest], Acc) -> + validate_props(Rest, [{absolute_expiry_time, Val} | Acc]); +validate_props([{{symbol, <<"creation-time">>}, {timestamp, Val}} | Rest], Acc) -> + validate_props(Rest, [{creation_time, Val} | Acc]); +validate_props([{{symbol, <<"group-id">>}, {utf8, Val}} | Rest], Acc) -> + validate_props(Rest, [{group_id, parse_string_modifier_prefix(Val)} | Acc]); +validate_props([{{symbol, <<"group-sequence">>}, {uint, Val}} | Rest], Acc) -> + validate_props(Rest, [{group_sequence, Val} | Acc]); +validate_props([{{symbol, <<"reply-to-group-id">>}, {utf8, Val}} | Rest], Acc) -> + validate_props(Rest, [{reply_to_group_id, parse_string_modifier_prefix(Val)} | Acc]); +validate_props(_, _) -> + error. + +parse_message_id({ulong, Val}) -> + {ok, Val}; +parse_message_id({uuid, Val}) -> + {ok, Val}; +parse_message_id({binary, Val}) -> + {ok, Val}; +parse_message_id({utf8, Val}) -> + {ok, parse_string_modifier_prefix(Val)}; +parse_message_id(_) -> + error. + +validate_app_props([], Acc) -> + {ok, {application_properties, lists:reverse(Acc)}}; +validate_app_props([{{utf8, Key}, {utf8, String}} | Rest], Acc) -> + validate_app_props(Rest, [{Key, parse_string_modifier_prefix(String)} | Acc]); +validate_app_props([{{utf8, Key}, TaggedVal} | Rest], Acc) -> + validate_app_props(Rest, [{Key, unwrap(TaggedVal)} | Acc]); +validate_app_props(_, _) -> + error. + +%% [filtex-v1.0-wd09 4.1.1] +parse_string_modifier_prefix(<<"&s:", Suffix/binary>>) -> + {suffix, size(Suffix), Suffix}; +parse_string_modifier_prefix(<<"&p:", Prefix/binary>>) -> + {prefix, size(Prefix), Prefix}; +parse_string_modifier_prefix(<<"&&", _/binary>> = String) -> + %% "Escape prefix for case-sensitive matching of a string starting with ‘&’" + string:slice(String, 1); +parse_string_modifier_prefix(<<"&", _/binary>> = String) -> + throw({?MODULE, invalid_reference_field_value, String}); +parse_string_modifier_prefix(String) -> + String. + +unwrap({_Tag, V}) -> + V; +unwrap(V) -> + V. diff --git a/deps/rabbit/src/rabbit_amqp_management.erl b/deps/rabbit/src/rabbit_amqp_management.erl index e4555e806033..a2e54ee9250b 100644 --- a/deps/rabbit/src/rabbit_amqp_management.erl +++ b/deps/rabbit/src/rabbit_amqp_management.erl @@ -381,7 +381,23 @@ handle_http_req(<<"GET">>, Bindings0 = rabbit_binding:list_for_source_and_destination(SrcXName, DstName), Bindings = [B || B = #binding{key = K} <- Bindings0, K =:= Key], RespPayload = encode_bindings(Bindings), +<<<<<<< HEAD {<<"200">>, RespPayload, PermCaches}. +======= + {<<"200">>, RespPayload, PermCaches}; + +handle_http_req(<<"PUT">>, + [<<"auth">>, <<"tokens">>], + _Query, + ReqPayload, + _Vhost, + _User, + ConnPid, + PermCaches) -> + {binary, Token} = ReqPayload, + ok = rabbit_amqp_reader:set_credential(ConnPid, Token), + {<<"204">>, null, PermCaches}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) decode_queue({map, KVList}) -> M = lists:foldl( diff --git a/deps/rabbit/src/rabbit_amqp_reader.erl b/deps/rabbit/src/rabbit_amqp_reader.erl index 52e2ba2e8f9c..cd60660d6cf3 100644 --- a/deps/rabbit/src/rabbit_amqp_reader.erl +++ b/deps/rabbit/src/rabbit_amqp_reader.erl @@ -7,13 +7,25 @@ -module(rabbit_amqp_reader). +<<<<<<< HEAD -include_lib("rabbit_common/include/rabbit.hrl"). -include_lib("amqp10_common/include/amqp10_types.hrl"). +======= +-include_lib("kernel/include/logger.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). +-include_lib("amqp10_common/include/amqp10_types.hrl"). +-include("rabbit_amqp_reader.hrl"). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -include("rabbit_amqp.hrl"). -export([init/1, info/2, +<<<<<<< HEAD mainloop/2]). +======= + mainloop/2, + set_credential/2]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -export([system_continue/3, system_terminate/4, @@ -35,6 +47,10 @@ -record(v1_connection, {name :: binary(), +<<<<<<< HEAD +======= + container_id :: none | binary(), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) vhost :: none | rabbit_types:vhost(), %% server host host :: inet:ip_address() | inet:hostname(), @@ -52,6 +68,10 @@ channel_max :: non_neg_integer(), auth_mechanism :: sasl_init_unprocessed | {binary(), module()}, auth_state :: term(), +<<<<<<< HEAD +======= + credential_timer :: undefined | reference(), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) properties :: undefined | {map, list(tuple())} }). @@ -75,7 +95,12 @@ pending_recv :: boolean(), buf :: list(), buf_len :: non_neg_integer(), +<<<<<<< HEAD tracked_channels :: #{channel_number() => Session :: pid()} +======= + tracked_channels :: #{channel_number() => Session :: pid()}, + stats_timer :: rabbit_event:state() +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }). -type state() :: #v1{}. @@ -86,7 +111,11 @@ unpack_from_0_9_1( {Sock, PendingRecv, SupPid, Buf, BufLen, ProxySocket, +<<<<<<< HEAD ConnectionName, Host, PeerHost, Port, PeerPort, ConnectedAt}, +======= + ConnectionName, Host, PeerHost, Port, PeerPort, ConnectedAt, StatsTimer}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Parent) -> logger:update_process_metadata(#{connection => ConnectionName}), #v1{parent = Parent, @@ -102,8 +131,15 @@ unpack_from_0_9_1( tracked_channels = maps:new(), writer = none, connection_state = received_amqp3100, +<<<<<<< HEAD + connection = #v1_connection{ + name = ConnectionName, +======= + stats_timer = StatsTimer, connection = #v1_connection{ name = ConnectionName, + container_id = none, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) vhost = none, host = Host, peer_host = PeerHost, @@ -137,6 +173,14 @@ server_properties() -> Props = [{{symbol, <<"node">>}, {utf8, atom_to_binary(node())}} | Props1], {map, Props}. +<<<<<<< HEAD +======= +-spec set_credential(pid(), binary()) -> ok. +set_credential(Pid, Credential) -> + Pid ! {set_credential, Credential}, + ok. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %%-------------------------------------------------------------------------- inet_op(F) -> rabbit_misc:throw_on_error(inet_error, F). @@ -191,6 +235,13 @@ mainloop(Deb, State = #v1{sock = Sock, buf = Buf, buf_len = BufLen}) -> end end. +<<<<<<< HEAD +======= +handle_other(emit_stats, State) -> + emit_stats(State); +handle_other(ensure_stats_timer, State) -> + ensure_stats_timer(State); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) handle_other({'EXIT', Parent, Reason}, State = #v1{parent = Parent}) -> ReasonString = rabbit_misc:format("broker forced connection closure with reason '~w'", [Reason]), @@ -237,10 +288,27 @@ handle_other({'$gen_call', From, {info, Items}}, State) -> end, gen_server:reply(From, Reply), State; +<<<<<<< HEAD handle_other({'$gen_cast', {force_event_refresh, _Ref}}, State) -> State; handle_other(terminate_connection, _State) -> stop; +======= +handle_other({'$gen_cast', {force_event_refresh, Ref}}, State) -> + case ?IS_RUNNING(State) of + true -> + Infos = infos(?CONNECTION_EVENT_KEYS, State), + rabbit_event:notify(connection_created, Infos, Ref), + rabbit_event:init_stats_timer(State, #v1.stats_timer); + false -> + %% Ignore, we will emit a connection_created event once we start running. + State + end; +handle_other(terminate_connection, _State) -> + stop; +handle_other({set_credential, Cred}, State) -> + set_credential0(Cred, State); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) handle_other(credential_expired, State) -> Error = error_frame(?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, "credential expired", []), handle_exception(State, 0, Error); @@ -318,16 +386,26 @@ error_frame(Condition, Fmt, Args) -> handle_exception(State = #v1{connection_state = closed}, Channel, #'v1_0.error'{description = {utf8, Desc}}) -> +<<<<<<< HEAD rabbit_log_connection:error( "Error on AMQP 1.0 connection ~tp (~tp), channel number ~b:~n~tp", [self(), closed, Channel, Desc]), +======= + ?LOG_ERROR("Error on AMQP 1.0 connection ~tp (~tp), channel number ~b:~n~tp", + [self(), closed, Channel, Desc]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) State; handle_exception(State = #v1{connection_state = CS}, Channel, Error = #'v1_0.error'{description = {utf8, Desc}}) when ?IS_RUNNING(State) orelse CS =:= closing -> +<<<<<<< HEAD rabbit_log_connection:error( "Error on AMQP 1.0 connection ~tp (~tp), channel number ~b:~n~tp", [self(), CS, Channel, Desc]), +======= + ?LOG_ERROR("Error on AMQP 1.0 connection ~tp (~tp), channel number ~b:~n~tp", + [self(), CS, Channel, Desc]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) close(Error, State); handle_exception(State, _Channel, Error) -> silent_close_delay(), @@ -414,14 +492,22 @@ handle_connection_frame( }, helper_sup = HelperSupPid, sock = Sock} = State0) -> +<<<<<<< HEAD logger:update_process_metadata(#{amqp_container => ContainerId}), Vhost = vhost(Hostname), +======= + Vhost = vhost(Hostname), + logger:update_process_metadata(#{amqp_container => ContainerId, + vhost => Vhost, + user => Username}), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ok = check_user_loopback(State0), ok = check_vhost_exists(Vhost, State0), ok = check_vhost_alive(Vhost), ok = rabbit_access_control:check_vhost_access(User, Vhost, {socket, Sock}, #{}), ok = check_vhost_connection_limit(Vhost, Username), ok = check_user_connection_limit(Username), +<<<<<<< HEAD ok = ensure_credential_expiry_timer(User), rabbit_core_metrics:auth_attempt_succeeded(<<>>, Username, amqp10), notify_auth(user_authentication_success, Username, State0), @@ -429,6 +515,15 @@ handle_connection_frame( "Connection from AMQP 1.0 container '~ts': user '~ts' authenticated " "using SASL mechanism ~s and granted access to vhost '~ts'", [ContainerId, Username, Mechanism, Vhost]), +======= + Timer = maybe_start_credential_expiry_timer(User), + rabbit_core_metrics:auth_attempt_succeeded(<<>>, Username, amqp10), + notify_auth(user_authentication_success, Username, State0), + ?LOG_INFO( + "Connection from AMQP 1.0 container '~ts': user '~ts' authenticated " + "using SASL mechanism ~s and granted access to vhost '~ts'", + [ContainerId, Username, Mechanism, Vhost]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) OutgoingMaxFrameSize = case ClientMaxFrame of undefined -> @@ -491,12 +586,21 @@ handle_connection_frame( end, State1 = State0#v1{connection_state = running, connection = Connection#v1_connection{ +<<<<<<< HEAD +======= + container_id = ContainerId, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) vhost = Vhost, incoming_max_frame_size = IncomingMaxFrameSize, outgoing_max_frame_size = OutgoingMaxFrameSize, channel_max = EffectiveChannelMax, properties = Properties, +<<<<<<< HEAD timeout = ReceiveTimeoutMillis}, +======= + timeout = ReceiveTimeoutMillis, + credential_timer = Timer}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) heartbeater = Heartbeater}, State = start_writer(State1), HostnameVal = case Hostname of @@ -504,27 +608,46 @@ handle_connection_frame( null -> undefined; {utf8, Val} -> Val end, +<<<<<<< HEAD rabbit_log:debug( "AMQP 1.0 connection.open frame: hostname = ~ts, extracted vhost = ~ts, idle-time-out = ~p", [HostnameVal, Vhost, IdleTimeout]), +======= + ?LOG_DEBUG( + "AMQP 1.0 connection.open frame: hostname = ~ts, extracted vhost = ~ts, idle-time-out = ~p", + [HostnameVal, Vhost, IdleTimeout]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Infos = infos(?CONNECTION_EVENT_KEYS, State), ok = rabbit_core_metrics:connection_created( proplists:get_value(pid, Infos), Infos), ok = rabbit_event:notify(connection_created, Infos), +<<<<<<< HEAD ok = rabbit_amqp1_0:register_connection(self()), Caps = [%% https://docs.oasis-open.org/amqp/linkpair/v1.0/cs01/linkpair-v1.0-cs01.html#_Toc51331306 {symbol, <<"LINK_PAIR_V1_0">>}, %% https://docs.oasis-open.org/amqp/anonterm/v1.0/cs01/anonterm-v1.0-cs01.html#doc-anonymous-relay {symbol, <<"ANONYMOUS-RELAY">>}], +======= + ok = maybe_emit_stats(State), + ok = rabbit_amqp1_0:register_connection(self()), + Caps = [%% https://docs.oasis-open.org/amqp/linkpair/v1.0/cs01/linkpair-v1.0-cs01.html#_Toc51331306 + <<"LINK_PAIR_V1_0">>, + %% https://docs.oasis-open.org/amqp/anonterm/v1.0/cs01/anonterm-v1.0-cs01.html#doc-anonymous-relay + <<"ANONYMOUS-RELAY">>], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Open = #'v1_0.open'{ channel_max = {ushort, EffectiveChannelMax}, max_frame_size = {uint, IncomingMaxFrameSize}, %% "the value in idle-time-out SHOULD be half the peer's actual timeout threshold" [2.4.5] idle_time_out = {uint, ReceiveTimeoutMillis div 2}, container_id = {utf8, rabbit_nodes:cluster_name()}, +<<<<<<< HEAD offered_capabilities = {array, symbol, Caps}, +======= + offered_capabilities = rabbit_amqp_util:capabilities(Caps), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) properties = server_properties()}, ok = send_on_channel0(Sock, Open), State; @@ -615,6 +738,7 @@ handle_input(handshake, switch_callback(State, {frame_header, amqp}, 8); handle_input({frame_header, Mode}, Header = <>, +<<<<<<< HEAD State) when DOff >= 2 -> case {Mode, Type} of {amqp, 0} -> ok; @@ -634,6 +758,28 @@ handle_input({frame_header, Mode}, true -> switch_callback(State, {frame_body, Mode, DOff, Channel}, Size - 8) end; +======= + State0) when DOff >= 2 -> + case {Mode, Type} of + {amqp, 0} -> ok; + {sasl, 1} -> ok; + _ -> throw({bad_1_0_header_type, Header, Mode}) + end, + MaxFrameSize = State0#v1.connection#v1_connection.incoming_max_frame_size, + State = if Size =:= 8 -> + %% heartbeat + State0; + Size > MaxFrameSize -> + Err = error_frame( + ?V_1_0_CONNECTION_ERROR_FRAMING_ERROR, + "frame size (~b bytes) > maximum frame size (~b bytes)", + [Size, MaxFrameSize]), + handle_exception(State0, Channel, Err); + true -> + switch_callback(State0, {frame_body, Mode, DOff, Channel}, Size - 8) + end, + ensure_stats_timer(State); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) handle_input({frame_header, _Mode}, Malformed, _State) -> throw({bad_1_0_header, Malformed}); handle_input({frame_body, Mode, DOff, Channel}, @@ -765,16 +911,26 @@ notify_auth(EventType, Username, State) -> rabbit_event:notify(EventType, EventProps). track_channel(ChannelNum, SessionPid, #v1{tracked_channels = Channels} = State) -> +<<<<<<< HEAD rabbit_log:debug("AMQP 1.0 created session process ~p for channel number ~b", [SessionPid, ChannelNum]), +======= + ?LOG_DEBUG("AMQP 1.0 created session process ~p for channel number ~b", + [SessionPid, ChannelNum]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) _Ref = erlang:monitor(process, SessionPid, [{tag, {'DOWN', ChannelNum}}]), State#v1{tracked_channels = maps:put(ChannelNum, SessionPid, Channels)}. untrack_channel(ChannelNum, SessionPid, #v1{tracked_channels = Channels0} = State) -> case maps:take(ChannelNum, Channels0) of {SessionPid, Channels} -> +<<<<<<< HEAD rabbit_log:debug("AMQP 1.0 closed session process ~p with channel number ~b", [SessionPid, ChannelNum]), +======= + ?LOG_DEBUG("AMQP 1.0 closed session process ~p with channel number ~b", + [SessionPid, ChannelNum]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) State#v1{tracked_channels = Channels}; _ -> State @@ -868,6 +1024,7 @@ check_user_connection_limit(Username) -> end. +<<<<<<< HEAD %% TODO Provide a means for the client to refresh the credential. %% This could be either via: %% 1. SASL (if multiple authentications are allowed on the same AMQP 1.0 connection), see @@ -901,6 +1058,59 @@ ensure_credential_expiry_timer(User) -> false -> protocol_error(?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, "Credential expired ~b ms ago", [abs(Time)]) +======= +set_credential0(Cred, + State = #v1{connection = #v1_connection{ + user = User0, + vhost = Vhost, + credential_timer = OldTimer} = Conn, + tracked_channels = Chans, + sock = Sock}) -> + ?LOG_INFO("updating credential", []), + case rabbit_access_control:update_state(User0, Cred) of + {ok, User} -> + try rabbit_access_control:check_vhost_access(User, Vhost, {socket, Sock}, #{}) of + ok -> + maps:foreach(fun(_ChanNum, Pid) -> + rabbit_amqp_session:reset_authz(Pid, User) + end, Chans), + case OldTimer of + undefined -> ok; + Ref -> ok = erlang:cancel_timer(Ref, [{info, false}]) + end, + NewTimer = maybe_start_credential_expiry_timer(User), + State#v1{connection = Conn#v1_connection{ + user = User, + credential_timer = NewTimer}} + catch _:Reason -> + Error = error_frame(?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, + "access to vhost ~s failed for new credential: ~p", + [Vhost, Reason]), + handle_exception(State, 0, Error) + end; + Err -> + Error = error_frame(?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, + "credential update failed: ~p", + [Err]), + handle_exception(State, 0, Error) + end. + +maybe_start_credential_expiry_timer(User) -> + case rabbit_access_control:expiry_timestamp(User) of + never -> + undefined; + Ts when is_integer(Ts) -> + Time = (Ts - os:system_time(second)) * 1000, + ?LOG_DEBUG( + "credential expires in ~b ms frow now (absolute timestamp = ~b seconds since epoch)", + [Time, Ts]), + case Time > 0 of + true -> + erlang:send_after(Time, self(), credential_expired); + false -> + protocol_error(?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, + "credential expired ~b ms ago", [abs(Time)]) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end end. @@ -918,7 +1128,12 @@ silent_close_delay() -> -spec info(rabbit_types:connection(), rabbit_types:info_keys()) -> rabbit_types:infos(). info(Pid, InfoItems) -> +<<<<<<< HEAD case InfoItems -- ?INFO_ITEMS of +======= + KnownItems = [session_pids | ?INFO_ITEMS], + case InfoItems -- KnownItems of +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) [] -> case gen_server:call(Pid, {info, InfoItems}, infinity) of {ok, InfoList} -> @@ -969,6 +1184,11 @@ i(connected_at, #v1{connection = #v1_connection{connected_at = Val}}) -> Val; i(name, #v1{connection = #v1_connection{name = Val}}) -> Val; +<<<<<<< HEAD +======= +i(container_id, #v1{connection = #v1_connection{container_id = Val}}) -> + Val; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) i(vhost, #v1{connection = #v1_connection{vhost = Val}}) -> Val; i(host, #v1{connection = #v1_connection{host = Val}}) -> @@ -979,6 +1199,7 @@ i(peer_host, #v1{connection = #v1_connection{peer_host = Val}}) -> Val; i(peer_port, #v1{connection = #v1_connection{peer_port = Val}}) -> Val; +<<<<<<< HEAD i(SockStat, S) when SockStat =:= recv_oct; SockStat =:= recv_cnt; SockStat =:= send_oct; @@ -986,6 +1207,20 @@ i(SockStat, S) when SockStat =:= recv_oct; SockStat =:= send_pend -> socket_info(fun (Sock) -> rabbit_net:getstat(Sock, [SockStat]) end, fun ([{_, I}]) -> I end, S); +======= +i(SockStat, #v1{sock = Sock}) + when SockStat =:= recv_oct; + SockStat =:= recv_cnt; + SockStat =:= send_oct; + SockStat =:= send_cnt; + SockStat =:= send_pend -> + case rabbit_net:getstat(Sock, [SockStat]) of + {ok, [{SockStat, Val}]} -> + Val; + {error, _} -> + '' + end; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) i(ssl, #v1{sock = Sock}) -> rabbit_net:is_ssl(Sock); i(SSL, #v1{sock = Sock, proxy_socket = ProxySock}) when SSL =:= ssl_protocol; @@ -1009,6 +1244,7 @@ i(client_properties, #v1{connection = #v1_connection{properties = Props}}) -> end; i(channels, #v1{tracked_channels = Channels}) -> maps:size(Channels); +<<<<<<< HEAD i(channel_max, #v1{connection = #v1_connection{channel_max = Max}}) -> Max; i(Item, #v1{}) -> @@ -1020,6 +1256,43 @@ socket_info(Get, Select, #v1{sock = Sock}) -> {ok, T} -> Select(T); {error, _} -> '' end. +======= +i(session_pids, #v1{tracked_channels = Map}) -> + maps:values(Map); +i(channel_max, #v1{connection = #v1_connection{channel_max = Max}}) -> + Max; +i(reductions = Item, _State) -> + {Item, Reductions} = erlang:process_info(self(), Item), + Reductions; +i(garbage_collection, _State) -> + rabbit_misc:get_gc_info(self()); +i(Item, #v1{}) -> + throw({bad_argument, Item}). + +maybe_emit_stats(State) -> + ok = rabbit_event:if_enabled( + State, + #v1.stats_timer, + fun() -> emit_stats(State) end). + +emit_stats(State) -> + [{_, Pid}, + {_, RecvOct}, + {_, SendOct}, + {_, Reductions}] = infos(?SIMPLE_METRICS, State), + Infos = infos(?OTHER_METRICS, State), + rabbit_core_metrics:connection_stats(Pid, Infos), + rabbit_core_metrics:connection_stats(Pid, RecvOct, SendOct, Reductions), + %% NB: Don't call ensure_stats_timer because it becomes expensive + %% if all idle non-hibernating connections emit stats. + rabbit_event:reset_stats_timer(State, #v1.stats_timer). + +ensure_stats_timer(State) + when ?IS_RUNNING(State) -> + rabbit_event:ensure_stats_timer(State, #v1.stats_timer, emit_stats); +ensure_stats_timer(State) -> + State. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ignore_maintenance({map, Properties}) -> lists:member( diff --git a/deps/rabbit/src/rabbit_amqp_reader.hrl b/deps/rabbit/src/rabbit_amqp_reader.hrl new file mode 100644 index 000000000000..7c71b21dc90f --- /dev/null +++ b/deps/rabbit/src/rabbit_amqp_reader.hrl @@ -0,0 +1,17 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. + +-define(SIMPLE_METRICS, [pid, + recv_oct, + send_oct, + reductions]). + +-define(OTHER_METRICS, [recv_cnt, + send_cnt, + send_pend, + state, + channels, + garbage_collection]). diff --git a/deps/rabbit/src/rabbit_amqp_session.erl b/deps/rabbit/src/rabbit_amqp_session.erl index fdcc3de2be6b..4803b9989b40 100644 --- a/deps/rabbit/src/rabbit_amqp_session.erl +++ b/deps/rabbit/src/rabbit_amqp_session.erl @@ -11,6 +11,10 @@ -behaviour(gen_server). +<<<<<<< HEAD +======= +-include_lib("kernel/include/logger.hrl"). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -include_lib("rabbit_common/include/rabbit.hrl"). -include_lib("amqp10_common/include/amqp10_types.hrl"). -include("rabbit_amqp.hrl"). @@ -30,6 +34,15 @@ }} }). +<<<<<<< HEAD +======= +-rabbit_deprecated_feature( + {amqp_filter_set_bug, + #{deprecation_phase => permitted_by_default, + doc_url => "https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-filter-set" + }}). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% This is the link credit that we grant to sending clients. %% We are free to choose whatever we want, sending clients must obey. %% Default soft limits / credits in deps/rabbit/Makefile are: @@ -84,7 +97,13 @@ list_local/0, conserve_resources/3, check_resource_access/4, +<<<<<<< HEAD check_read_permitted_on_topic/4 +======= + check_read_permitted_on_topic/4, + reset_authz/2, + info/1 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]). -export([init/1, @@ -140,7 +159,13 @@ }). -record(incoming_link, { +<<<<<<< HEAD + snd_settle_mode :: snd_settle_mode(), +======= + name :: binary(), snd_settle_mode :: snd_settle_mode(), + target_address :: null | binary(), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% The exchange is either defined in the ATTACH frame and static for %% the life time of the link or dynamically provided in each message's %% "to" field (address v2). @@ -148,6 +173,10 @@ %% The routing key is either defined in the ATTACH frame and static for %% the life time of the link or dynamically provided in each message's %% "to" field (address v2) or "subject" field (address v1). +<<<<<<< HEAD +======= + %% (A publisher can set additional routing keys via the x-cc message annotation.) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) routing_key :: rabbit_types:routing_key() | to | subject, %% queue_name_bin is only set if the link target address refers to a queue. queue_name_bin :: undefined | rabbit_misc:resource_name(), @@ -188,6 +217,11 @@ }). -record(outgoing_link, { +<<<<<<< HEAD +======= + name :: binary(), + source_address :: binary(), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% Although the source address of a link might be an exchange name and binding key %% or a topic filter, an outgoing link will always consume from a queue. queue_name :: rabbit_amqqueue:name(), @@ -386,6 +420,13 @@ init({ReaderPid, WriterPid, ChannelNum, MaxFrameSize, User, Vhost, ConnName, handle_max = ClientHandleMax}}) -> process_flag(trap_exit, true), rabbit_process_flag:adjust_for_message_handling_proc(), +<<<<<<< HEAD +======= + logger:update_process_metadata(#{channel_number => ChannelNum, + connection => ConnName, + vhost => Vhost, + user => User#user.username}), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ok = pg:join(pg_scope(), self(), self()), Alarms0 = rabbit_alarm:register(self(), {?MODULE, conserve_resources, []}), @@ -473,6 +514,15 @@ list_local() -> conserve_resources(Pid, Source, {_, Conserve, _}) -> gen_server:cast(Pid, {conserve_resources, Source, Conserve}). +<<<<<<< HEAD +======= +-spec reset_authz(pid(), rabbit_types:user()) -> ok. +reset_authz(Pid, User) -> + gen_server:cast(Pid, {reset_authz, User}). + +handle_call(infos, _From, State) -> + reply(infos(State), State); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) handle_call(Msg, _From, State) -> Reply = {error, {not_understood, Msg}}, reply(Reply, State). @@ -567,15 +617,35 @@ handle_cast({conserve_resources, Alarm, Conserve}, noreply(State); handle_cast(refresh_config, #state{cfg = #cfg{vhost = Vhost} = Cfg} = State0) -> State = State0#state{cfg = Cfg#cfg{trace_state = rabbit_trace:init(Vhost)}}, +<<<<<<< HEAD noreply(State). +======= + noreply(State); +handle_cast({reset_authz, User}, #state{cfg = Cfg} = State0) -> + State1 = State0#state{ + permission_cache = [], + topic_permission_cache = [], + cfg = Cfg#cfg{user = User}}, + try recheck_authz(State1) of + State -> + noreply(State) + catch exit:#'v1_0.error'{} = Error -> + log_error_and_close_session(Error, State1) + end. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) log_error_and_close_session( Error, State = #state{cfg = #cfg{reader_pid = ReaderPid, writer_pid = WriterPid, channel_num = Ch}}) -> End = #'v1_0.end'{error = Error}, +<<<<<<< HEAD rabbit_log:warning("Closing session for connection ~p: ~tp", [ReaderPid, Error]), +======= + ?LOG_WARNING("Closing session for connection ~p: ~tp", + [ReaderPid, Error]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ok = rabbit_amqp_writer:send_command_sync(WriterPid, Ch, End), {stop, {shutdown, Error}, State}. @@ -862,8 +932,13 @@ destroy_outgoing_link(_, _, _, Acc) -> Acc. detach(Handle, Link, Error = #'v1_0.error'{}) -> +<<<<<<< HEAD rabbit_log:warning("Detaching link handle ~b due to error: ~tp", [Handle, Error]), +======= + ?LOG_WARNING("Detaching link handle ~b due to error: ~tp", + [Handle, Error]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) publisher_or_consumer_deleted(Link), #'v1_0.detach'{handle = ?UINT(Handle), closed = true, @@ -954,8 +1029,13 @@ handle_frame(#'v1_0.flow'{handle = Handle} = Flow, %% "If set to a handle that is not currently associated with %% an attached link, the recipient MUST respond by ending the %% session with an unattached-handle session error." [2.7.4] +<<<<<<< HEAD rabbit_log:warning( "Received Flow frame for unknown link handle: ~tp", [Flow]), +======= + ?LOG_WARNING("Received Flow frame for unknown link handle: ~tp", + [Flow]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) protocol_error( ?V_1_0_SESSION_ERROR_UNATTACHED_HANDLE, "Unattached link handle: ~b", [HandleInt]) @@ -1234,11 +1314,19 @@ handle_attach(#'v1_0.attach'{ reply_frames([Reply], State); handle_attach(#'v1_0.attach'{role = ?AMQP_ROLE_SENDER, +<<<<<<< HEAD name = LinkName, handle = Handle = ?UINT(HandleInt), source = Source, snd_settle_mode = MaybeSndSettleMode, target = Target, +======= + name = LinkName = {utf8, LinkName0}, + handle = Handle = ?UINT(HandleInt), + source = Source, + snd_settle_mode = MaybeSndSettleMode, + target = Target = #'v1_0.target'{address = TargetAddress}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) initial_delivery_count = DeliveryCount = ?UINT(DeliveryCountInt) }, State0 = #state{incoming_links = IncomingLinks0, @@ -1251,7 +1339,13 @@ handle_attach(#'v1_0.attach'{role = ?AMQP_ROLE_SENDER, SndSettleMode = snd_settle_mode(MaybeSndSettleMode), MaxMessageSize = persistent_term:get(max_message_size), IncomingLink = #incoming_link{ +<<<<<<< HEAD + snd_settle_mode = SndSettleMode, +======= + name = LinkName0, snd_settle_mode = SndSettleMode, + target_address = address(TargetAddress), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) exchange = Exchange, routing_key = RoutingKey, queue_name_bin = QNameBin, @@ -1288,12 +1382,23 @@ handle_attach(#'v1_0.attach'{role = ?AMQP_ROLE_SENDER, end; handle_attach(#'v1_0.attach'{role = ?AMQP_ROLE_RECEIVER, +<<<<<<< HEAD name = LinkName, handle = Handle = ?UINT(HandleInt), source = Source, snd_settle_mode = SndSettleMode, rcv_settle_mode = RcvSettleMode, max_message_size = MaybeMaxMessageSize} = Attach, +======= + name = LinkName = {utf8, LinkName0}, + handle = Handle = ?UINT(HandleInt), + source = Source = #'v1_0.source'{address = SourceAddress, + filter = DesiredFilter}, + snd_settle_mode = SndSettleMode, + rcv_settle_mode = RcvSettleMode, + max_message_size = MaybeMaxMessageSize, + properties = Properties}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) State0 = #state{queue_states = QStates0, outgoing_links = OutgoingLinks0, permission_cache = PermCache0, @@ -1363,6 +1468,13 @@ handle_attach(#'v1_0.attach'{role = ?AMQP_ROLE_RECEIVER, credit_api_v1, credit_api_v1} end, +<<<<<<< HEAD +======= + ConsumerArgs0 = parse_attach_properties(Properties), + {EffectiveFilter, ConsumerFilter, ConsumerArgs1} = + parse_filter(DesiredFilter), + ConsumerArgs = ConsumerArgs0 ++ ConsumerArgs1, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Spec = #{no_ack => SndSettled, channel_pid => self(), limiter_pid => none, @@ -1370,11 +1482,21 @@ handle_attach(#'v1_0.attach'{role = ?AMQP_ROLE_RECEIVER, mode => Mode, consumer_tag => handle_to_ctag(HandleInt), exclusive_consume => false, +<<<<<<< HEAD args => consumer_arguments(Attach), +======= + args => ConsumerArgs, + filter => ConsumerFilter, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ok_msg => undefined, acting_user => Username}, case rabbit_queue_type:consume(Q, Spec, QStates0) of {ok, QStates} -> +<<<<<<< HEAD +======= + OfferedCaps0 = rabbit_queue_type:amqp_capabilities(QType), + OfferedCaps = rabbit_amqp_util:capabilities(OfferedCaps0), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) A = #'v1_0.attach'{ name = LinkName, handle = Handle, @@ -1386,12 +1508,26 @@ handle_attach(#'v1_0.attach'{role = ?AMQP_ROLE_RECEIVER, %% will be requeued. That's why the we only support RELEASED as the default outcome. source = Source#'v1_0.source'{ default_outcome = #'v1_0.released'{}, +<<<<<<< HEAD outcomes = outcomes(Source)}, role = ?AMQP_ROLE_SENDER, %% Echo back that we will respect the client's requested max-message-size. max_message_size = MaybeMaxMessageSize}, MaxMessageSize = max_message_size(MaybeMaxMessageSize), Link = #outgoing_link{ +======= + outcomes = outcomes(Source), + %% "the sending endpoint sets the filter actually in place" [3.5.3] + filter = EffectiveFilter}, + role = ?AMQP_ROLE_SENDER, + %% Echo back that we will respect the client's requested max-message-size. + max_message_size = MaybeMaxMessageSize, + offered_capabilities = OfferedCaps}, + MaxMessageSize = max_message_size(MaybeMaxMessageSize), + Link = #outgoing_link{ + name = LinkName0, + source_address = address(SourceAddress), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) queue_name = queue_resource(Vhost, QNameBin), queue_type = QType, send_settled = SndSettled, @@ -2126,9 +2262,15 @@ handle_deliver(ConsumerTag, AckRequired, outgoing_links = OutgoingLinks}; _ -> %% TODO handle missing link -- why does the queue think it's there? +<<<<<<< HEAD rabbit_log:warning( "No link handle ~b exists for delivery with consumer tag ~p from queue ~tp", [Handle, ConsumerTag, QName]), +======= + ?LOG_WARNING( + "No link handle ~b exists for delivery with consumer tag ~p from queue ~tp", + [Handle, ConsumerTag, QName]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) State end. @@ -2348,16 +2490,30 @@ incoming_link_transfer( end, validate_transfer_snd_settle_mode(SndSettleMode, Settled), validate_transfer_rcv_settle_mode(RcvSettleMode, Settled), +<<<<<<< HEAD validate_message_size(PayloadBin, MaxMessageSize), +======= + PayloadSize = iolist_size(PayloadBin), + validate_message_size(PayloadSize, MaxMessageSize), + rabbit_msg_size_metrics:observe(?PROTOCOL, PayloadSize), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) messages_received(Settled), Mc0 = mc:init(mc_amqp, PayloadBin, #{}), case lookup_target(LinkExchange, LinkRKey, Mc0, Vhost, User, PermCache0) of +<<<<<<< HEAD {ok, X, RoutingKey, Mc1, PermCache} -> Mc2 = rabbit_message_interceptor:intercept(Mc1), check_user_id(Mc2, User), TopicPermCache = check_write_permitted_on_topic( X, User, RoutingKey, TopicPermCache0), +======= + {ok, X, RoutingKeys, Mc1, PermCache} -> + Mc2 = rabbit_message_interceptor:intercept(Mc1), + check_user_id(Mc2, User), + TopicPermCache = check_write_permitted_on_topics( + X, User, RoutingKeys, TopicPermCache0), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) QNames = rabbit_exchange:route(X, Mc2, #{return_binding_keys => true}), rabbit_trace:tap_in(Mc2, QNames, ConnName, ChannelNum, Username, Trace), Opts = #{correlation => {HandleInt, DeliveryId}}, @@ -2392,14 +2548,22 @@ incoming_link_transfer( "delivery_tag=~p, delivery_id=~p, reason=~p", [DeliveryTag, DeliveryId, Reason]) end; +<<<<<<< HEAD {error, #'v1_0.error'{} = Err} -> +======= + {error, {anonymous_terminus, false}, #'v1_0.error'{} = Err} -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Disposition = case Settled of true -> []; false -> [released(DeliveryId)] end, Detach = [detach(HandleInt, Link0, Err)], {error, Disposition ++ Detach}; +<<<<<<< HEAD {error, anonymous_terminus, #'v1_0.error'{} = Err} -> +======= + {error, {anonymous_terminus, true}, #'v1_0.error'{} = Err} -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% https://docs.oasis-open.org/amqp/anonterm/v1.0/cs01/anonterm-v1.0-cs01.html#doc-routingerrors case Settled of true -> @@ -2424,6 +2588,7 @@ incoming_link_transfer( end. lookup_target(#exchange{} = X, LinkRKey, Mc, _, _, PermCache) -> +<<<<<<< HEAD lookup_routing_key(X, LinkRKey, Mc, PermCache); lookup_target(#resource{} = XName, LinkRKey, Mc, _, _, PermCache) -> case rabbit_exchange:lookup(XName) of @@ -2431,6 +2596,15 @@ lookup_target(#resource{} = XName, LinkRKey, Mc, _, _, PermCache) -> lookup_routing_key(X, LinkRKey, Mc, PermCache); {error, not_found} -> {error, error_not_found(XName)} +======= + lookup_routing_key(X, LinkRKey, Mc, false, PermCache); +lookup_target(#resource{} = XName, LinkRKey, Mc, _, _, PermCache) -> + case rabbit_exchange:lookup(XName) of + {ok, X} -> + lookup_routing_key(X, LinkRKey, Mc, false, PermCache); + {error, not_found} -> + {error, {anonymous_terminus, false}, error_not_found(XName)} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end; lookup_target(to, to, Mc, Vhost, User, PermCache0) -> case mc:property(to, Mc) of @@ -2442,25 +2616,43 @@ lookup_target(to, to, Mc, Vhost, User, PermCache0) -> case rabbit_exchange:lookup(XName) of {ok, X} -> check_internal_exchange(X), +<<<<<<< HEAD lookup_routing_key(X, RKey, Mc, PermCache); {error, not_found} -> {error, anonymous_terminus, error_not_found(XName)} end; {error, bad_address} -> {error, anonymous_terminus, +======= + lookup_routing_key(X, RKey, Mc, true, PermCache); + {error, not_found} -> + {error, {anonymous_terminus, true}, error_not_found(XName)} + end; + {error, bad_address} -> + {error, {anonymous_terminus, true}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) #'v1_0.error'{ condition = ?V_1_0_AMQP_ERROR_PRECONDITION_FAILED, description = {utf8, <<"bad 'to' address string: ", String/binary>>}}} end; undefined -> +<<<<<<< HEAD {error, anonymous_terminus, +======= + {error, {anonymous_terminus, true}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) #'v1_0.error'{ condition = ?V_1_0_AMQP_ERROR_PRECONDITION_FAILED, description = {utf8, <<"anonymous terminus requires 'to' address to be set">>}}} end. lookup_routing_key(X = #exchange{name = #resource{name = XNameBin}}, +<<<<<<< HEAD RKey0, Mc0, PermCache) -> +======= + RKey0, Mc0, AnonTerm, PermCache) -> + Mc1 = mc:set_annotation(?ANN_EXCHANGE, XNameBin, Mc0), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) RKey = case RKey0 of subject -> case mc:property(subject, Mc0) of @@ -2472,9 +2664,37 @@ lookup_routing_key(X = #exchange{name = #resource{name = XNameBin}}, _ when is_binary(RKey0) -> RKey0 end, +<<<<<<< HEAD Mc1 = mc:set_annotation(?ANN_EXCHANGE, XNameBin, Mc0), Mc = mc:set_annotation(?ANN_ROUTING_KEYS, [RKey], Mc1), {ok, X, RKey, Mc, PermCache}. +======= + case mc:x_header(<<"x-cc">>, Mc0) of + undefined -> + RKeys = [RKey], + Mc = mc:set_annotation(?ANN_ROUTING_KEYS, RKeys, Mc1), + {ok, X, RKeys, Mc, PermCache}; + {list, CCs0} = L -> + try lists:map(fun({utf8, CC}) -> CC end, CCs0) of + CCs -> + RKeys = [RKey | CCs], + Mc = mc:set_annotation(?ANN_ROUTING_KEYS, RKeys, Mc1), + {ok, X, RKeys, Mc, PermCache} + catch error:function_clause -> + {error, {anonymous_terminus, AnonTerm}, bad_x_cc(L)} + end; + BadValue -> + {error, {anonymous_terminus, AnonTerm}, bad_x_cc(BadValue)} + end. + +bad_x_cc(Value) -> + Desc = unicode:characters_to_binary( + lists:flatten( + io_lib:format( + "bad value for 'x-cc' message-annotation: ~tp", [Value]))), + #'v1_0.error'{condition = ?V_1_0_AMQP_ERROR_INVALID_FIELD, + description = {utf8, Desc}}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) process_routing_confirm([], _SenderSettles = true, _, U) -> rabbit_global_counters:messages_unroutable_dropped(?PROTOCOL, 1), @@ -2611,6 +2831,14 @@ ensure_source_v1(Address, Err end. +<<<<<<< HEAD +======= +address(undefined) -> + null; +address({utf8, String}) -> + String. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec ensure_target(#'v1_0.target'{}, rabbit_types:vhost(), rabbit_types:user(), @@ -2710,11 +2938,18 @@ parse_target_v2_string(String) -> end. parse_target_v2_string0(<<"/exchanges/", Rest/binary>>) -> +<<<<<<< HEAD Key = cp_slash, Pattern = try persistent_term:get(Key) catch error:badarg -> Cp = binary:compile_pattern(<<"/">>), ok = persistent_term:put(Key, Cp), +======= + Pattern = try persistent_term:get(cp_slash) + catch error:badarg -> + Cp = binary:compile_pattern(<<"/">>), + ok = persistent_term:put(cp_slash, Cp), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Cp end, case binary:split(Rest, Pattern, [global]) of @@ -2949,7 +3184,11 @@ credit_reply_timeout(QType, QName) -> Fmt = "Timed out waiting for credit reply from ~s ~s. " "Hint: Enable feature flag rabbitmq_4.0.0", Args = [QType, rabbit_misc:rs(QName)], +<<<<<<< HEAD rabbit_log:error(Fmt, Args), +======= + ?LOG_ERROR(Fmt, Args), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) protocol_error(?V_1_0_AMQP_ERROR_INTERNAL_ERROR, Fmt, Args). default(undefined, Default) -> Default; @@ -2985,6 +3224,7 @@ encode_frames(T, Msg, MaxPayloadSize, Transfers) -> lists:reverse([[T, Msg] | Transfers]) end. +<<<<<<< HEAD consumer_arguments(#'v1_0.attach'{ source = #'v1_0.source'{filter = Filter}, properties = Properties}) -> @@ -2992,12 +3232,18 @@ consumer_arguments(#'v1_0.attach'{ filter_to_consumer_args(Filter). properties_to_consumer_args({map, KVList}) -> +======= +parse_attach_properties(undefined) -> + []; +parse_attach_properties({map, KVList}) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Key = {symbol, <<"rabbitmq:priority">>}, case proplists:lookup(Key, KVList) of {Key, Val = {int, _Prio}} -> [mc_amqpl:to_091(<<"x-priority">>, Val)]; _ -> [] +<<<<<<< HEAD end; properties_to_consumer_args(_) -> []. @@ -3066,6 +3312,82 @@ keyfind_unpack_described(Key, KvList) -> Kv; false -> false +======= + end. + +parse_filter(undefined) -> + {undefined, [], []}; +parse_filter({map, DesiredKVList}) -> + {EffectiveKVList, ConsusumerFilter, ConsumerArgs} = + lists:foldr(fun parse_filters/2, {[], [], []}, DesiredKVList), + {{map, EffectiveKVList}, ConsusumerFilter, ConsumerArgs}. + +parse_filters(Filter = {{symbol, _Key}, {described, {symbol, <<"rabbitmq:stream-offset-spec">>}, Value}}, + Acc = {EffectiveFilters, ConsumerFilter, ConsumerArgs}) -> + case Value of + {timestamp, Ts} -> + %% 0.9.1 uses second based timestamps + Arg = {<<"x-stream-offset">>, timestamp, Ts div 1000}, + {[Filter | EffectiveFilters], ConsumerFilter, [Arg | ConsumerArgs]}; + {utf8, Spec} -> + %% next, last, first and "10m" etc + Arg = {<<"x-stream-offset">>, longstr, Spec}, + {[Filter | EffectiveFilters], ConsumerFilter, [Arg | ConsumerArgs]}; + {_Type, Offset} + when is_integer(Offset) andalso Offset >= 0 -> + Arg = {<<"x-stream-offset">>, long, Offset}, + {[Filter | EffectiveFilters], ConsumerFilter, [Arg | ConsumerArgs]}; + _ -> + Acc + end; +parse_filters(Filter = {{symbol, _Key}, {described, {symbol, <<"rabbitmq:stream-filter">>}, Value}}, + Acc = {EffectiveFilters, ConsumerFilter, ConsumerArgs}) -> + case Value of + {list, Filters0} -> + Filters = lists:filtermap(fun({utf8, Filter0}) -> + {true, {longstr, Filter0}}; + (_) -> + false + end, Filters0), + Arg = {<<"x-stream-filter">>, array, Filters}, + {[Filter | EffectiveFilters], ConsumerFilter, [Arg | ConsumerArgs]}; + + {utf8, Filter0} -> + Arg = {<<"x-stream-filter">>, longstr, Filter0}, + {[Filter | EffectiveFilters], ConsumerFilter, [Arg | ConsumerArgs]}; + _ -> + Acc + end; +parse_filters(Filter = {{symbol, _Key}, {described, {symbol, <<"rabbitmq:stream-match-unfiltered">>}, Match}}, + {EffectiveFilters, ConsumerFilter, ConsumerArgs}) + when is_boolean(Match) -> + Arg = {<<"x-stream-match-unfiltered">>, bool, Match}, + {[Filter | EffectiveFilters], ConsumerFilter, [Arg | ConsumerArgs]}; +parse_filters({Symbol = {symbol, <<"rabbitmq:stream-", _/binary>>}, Value}, Acc) + when element(1, Value) =/= described -> + case rabbit_deprecated_features:is_permitted(amqp_filter_set_bug) of + true -> + parse_filters({Symbol, {described, Symbol, Value}}, Acc); + false -> + Acc + end; +parse_filters(Filter = {{symbol, _Key}, Value}, + Acc = {EffectiveFilters, ConsumerFilter, ConsumerArgs}) -> + case rabbit_amqp_filtex:validate(Value) of + {ok, FilterExpression = {FilterType, _}} -> + case proplists:is_defined(FilterType, ConsumerFilter) of + true -> + %% For now, let's prohibit multiple top level filters of the same type + %% (properties or application-properties). There should be no use case. + %% In future, we can allow multiple times the same top level grouping + %% filter expression type (all/any/not). + Acc; + false -> + {[Filter | EffectiveFilters], [FilterExpression | ConsumerFilter], ConsumerArgs} + end; + error -> + Acc +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end. validate_attach(#'v1_0.attach'{target = #'v1_0.coordinator'{}}) -> @@ -3134,9 +3456,14 @@ validate_transfer_rcv_settle_mode(_, _) -> validate_message_size(_, unlimited) -> ok; +<<<<<<< HEAD validate_message_size(Message, MaxMsgSize) when is_integer(MaxMsgSize) -> MsgSize = iolist_size(Message), +======= +validate_message_size(MsgSize, MaxMsgSize) + when is_integer(MsgSize) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case MsgSize =< MaxMsgSize of true -> ok; @@ -3150,7 +3477,13 @@ validate_message_size(Message, MaxMsgSize) ?V_1_0_LINK_ERROR_MESSAGE_SIZE_EXCEEDED, "message size (~b bytes) > maximum message size (~b bytes)", [MsgSize, MaxMsgSize]) +<<<<<<< HEAD end. +======= + end; +validate_message_size(Msg, MaxMsgSize) -> + validate_message_size(iolist_size(Msg), MaxMsgSize). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec ensure_terminus(source | target, term(), @@ -3427,6 +3760,7 @@ check_resource_access(Resource, Perm, User, Cache) -> end end. +<<<<<<< HEAD -spec check_write_permitted_on_topic( rabbit_types:exchange(), rabbit_types:user(), @@ -3435,6 +3769,22 @@ check_resource_access(Resource, Perm, User, Cache) -> topic_permission_cache(). check_write_permitted_on_topic(Resource, User, RoutingKey, TopicPermCache) -> check_topic_authorisation(Resource, User, RoutingKey, write, TopicPermCache). +======= +-spec check_write_permitted_on_topics( + rabbit_types:exchange(), + rabbit_types:user(), + [rabbit_types:routing_key(),...], + topic_permission_cache()) -> + topic_permission_cache(). +check_write_permitted_on_topics(#exchange{type = topic} = Resource, + User, RoutingKeys, TopicPermCache) -> + lists:foldl( + fun(RoutingKey, Cache) -> + check_topic_authorisation(Resource, User, RoutingKey, write, Cache) + end, TopicPermCache, RoutingKeys); +check_write_permitted_on_topics(_, _, _, TopicPermCache) -> + TopicPermCache. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec check_read_permitted_on_topic( rabbit_types:exchange(), @@ -3474,6 +3824,32 @@ check_topic_authorisation(#exchange{type = topic, check_topic_authorisation(_, _, _, _, Cache) -> Cache. +<<<<<<< HEAD +======= +recheck_authz(#state{incoming_links = IncomingLinks, + outgoing_links = OutgoingLinks, + permission_cache = Cache0, + cfg = #cfg{user = User} + } = State) -> + ?LOG_DEBUG("rechecking link authorizations", []), + Cache1 = maps:fold( + fun(_Handle, #incoming_link{exchange = X}, Cache) -> + case X of + #exchange{name = XName} -> + check_resource_access(XName, write, User, Cache); + #resource{} = XName -> + check_resource_access(XName, write, User, Cache); + to -> + Cache + end + end, Cache0, IncomingLinks), + Cache2 = maps:fold( + fun(_Handle, #outgoing_link{queue_name = QName}, Cache) -> + check_resource_access(QName, read, User, Cache) + end, Cache1, OutgoingLinks), + State#state{permission_cache = Cache2}. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) check_user_id(Mc, User) -> case rabbit_access_control:check_user_id(Mc, User) of ok -> @@ -3610,6 +3986,121 @@ format_status( topic_permission_cache => TopicPermissionCache}, maps:update(state, State, Status). +<<<<<<< HEAD +======= +-spec info(pid()) -> + {ok, rabbit_types:infos()} | {error, term()}. +info(Pid) -> + try gen_server:call(Pid, infos) of + Infos -> + {ok, Infos} + catch _:Reason -> + {error, Reason} + end. + +infos(#state{cfg = #cfg{channel_num = ChannelNum, + max_handle = MaxHandle}, + next_incoming_id = NextIncomingId, + incoming_window = IncomingWindow, + next_outgoing_id = NextOutgoingId, + remote_incoming_window = RemoteIncomingWindow, + remote_outgoing_window = RemoteOutgoingWindow, + outgoing_unsettled_map = OutgoingUnsettledMap, + incoming_links = IncomingLinks, + outgoing_links = OutgoingLinks, + incoming_management_links = IncomingManagementLinks, + outgoing_management_links = OutgoingManagementLinks + }) -> + [ + {channel_number, ChannelNum}, + {handle_max, MaxHandle}, + {next_incoming_id, NextIncomingId}, + {incoming_window, IncomingWindow}, + {next_outgoing_id, NextOutgoingId}, + {remote_incoming_window, RemoteIncomingWindow}, + {remote_outgoing_window, RemoteOutgoingWindow}, + {outgoing_unsettled_deliveries, maps:size(OutgoingUnsettledMap)}, + {incoming_links, + info_incoming_management_links(IncomingManagementLinks) ++ + info_incoming_links(IncomingLinks)}, + {outgoing_links, + info_outgoing_management_links(OutgoingManagementLinks) ++ + info_outgoing_links(OutgoingLinks)} + ]. + +info_incoming_management_links(Links) -> + [info_incoming_link(Handle, Name, settled, ?MANAGEMENT_NODE_ADDRESS, + MaxMessageSize, DeliveryCount, Credit, 0) + || Handle := #management_link{ + name = Name, + max_message_size = MaxMessageSize, + delivery_count = DeliveryCount, + credit = Credit} <- Links]. + +info_incoming_links(Links) -> + [info_incoming_link(Handle, Name, SndSettleMode, TargetAddress, MaxMessageSize, + DeliveryCount, Credit, maps:size(IncomingUnconfirmedMap)) + || Handle := #incoming_link{ + name = Name, + snd_settle_mode = SndSettleMode, + target_address = TargetAddress, + max_message_size = MaxMessageSize, + delivery_count = DeliveryCount, + credit = Credit, + incoming_unconfirmed_map = IncomingUnconfirmedMap} <- Links]. + +info_incoming_link(Handle, LinkName, SndSettleMode, TargetAddress, + MaxMessageSize, DeliveryCount, Credit, UnconfirmedMessages) -> + [{handle, Handle}, + {link_name, LinkName}, + {snd_settle_mode, SndSettleMode}, + {target_address, TargetAddress}, + {max_message_size, MaxMessageSize}, + {delivery_count, DeliveryCount}, + {credit, Credit}, + {unconfirmed_messages, UnconfirmedMessages}]. + +info_outgoing_management_links(Links) -> + [info_outgoing_link(Handle, Name, ?MANAGEMENT_NODE_ADDRESS, <<>>, + true, MaxMessageSize, DeliveryCount, Credit) + || Handle := #management_link{ + name = Name, + max_message_size = MaxMessageSize, + delivery_count = DeliveryCount, + credit = Credit} <- Links]. + +info_outgoing_links(Links) -> + [begin + {DeliveryCount, Credit} = case ClientFlowCtl of + #client_flow_ctl{delivery_count = DC, + credit = C} -> + {DC, C}; + credit_api_v1 -> + {'', ''} + end, + info_outgoing_link(Handle, Name, SourceAddress, QueueName#resource.name, + SendSettled, MaxMessageSize, DeliveryCount, Credit) + + end + || Handle := #outgoing_link{ + name = Name, + source_address = SourceAddress, + queue_name = QueueName, + max_message_size = MaxMessageSize, + send_settled = SendSettled, + client_flow_ctl = ClientFlowCtl} <- Links]. + +info_outgoing_link(Handle, LinkName, SourceAddress, QueueNameBin, SendSettled, + MaxMessageSize, DeliveryCount, Credit) -> + [{handle, Handle}, + {link_name, LinkName}, + {source_address, SourceAddress}, + {queue_name, QueueNameBin}, + {send_settled, SendSettled}, + {max_message_size, MaxMessageSize}, + {delivery_count, DeliveryCount}, + {credit, Credit}]. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) unwrap_simple_type(V = {list, _}) -> V; diff --git a/deps/rabbit/src/rabbit_amqp_util.erl b/deps/rabbit/src/rabbit_amqp_util.erl index 3257cef93704..c8f5f881dfd3 100644 --- a/deps/rabbit/src/rabbit_amqp_util.erl +++ b/deps/rabbit/src/rabbit_amqp_util.erl @@ -8,7 +8,12 @@ -module(rabbit_amqp_util). -include("rabbit_amqp.hrl"). +<<<<<<< HEAD -export([protocol_error/3]). +======= +-export([protocol_error/3, + capabilities/1]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec protocol_error(term(), io:format(), [term()]) -> no_return(). @@ -17,3 +22,14 @@ protocol_error(Condition, Msg, Args) -> Reason = #'v1_0.error'{condition = Condition, description = {utf8, Description}}, exit(Reason). +<<<<<<< HEAD +======= + +-spec capabilities([binary()]) -> + undefined | {array, symbol, [{symbol, binary()}]}. +capabilities([]) -> + undefined; +capabilities(Capabilities) -> + Caps = [{symbol, C} || C <- Capabilities], + {array, symbol, Caps}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbit/src/rabbit_amqp_writer.erl b/deps/rabbit/src/rabbit_amqp_writer.erl index 7b239a10a107..282edc52653d 100644 --- a/deps/rabbit/src/rabbit_amqp_writer.erl +++ b/deps/rabbit/src/rabbit_amqp_writer.erl @@ -31,7 +31,12 @@ pending :: iolist(), %% This field is just an optimisation to minimize the cost of erlang:iolist_size/1 pending_size :: non_neg_integer(), +<<<<<<< HEAD monitored_sessions :: #{pid() => true} +======= + monitored_sessions :: #{pid() => true}, + stats_timer :: rabbit_event:state() +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }). -define(HIBERNATE_AFTER, 6_000). @@ -100,7 +105,12 @@ init({Sock, ReaderPid}) -> reader = ReaderPid, pending = [], pending_size = 0, +<<<<<<< HEAD monitored_sessions = #{}}, +======= + monitored_sessions = #{}, + stats_timer = rabbit_event:init_stats_timer()}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) process_flag(message_queue_data, off_heap), {ok, State}. @@ -123,6 +133,13 @@ handle_call({send_command, ChannelNum, Performative}, _From, State0) -> State = flush(State1), {reply, ok, State}. +<<<<<<< HEAD +======= +handle_info(emit_stats, State0 = #state{reader = ReaderPid}) -> + ReaderPid ! ensure_stats_timer, + State = rabbit_event:reset_stats_timer(State0, #state.stats_timer), + no_reply(State); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) handle_info(timeout, State0) -> State = flush(State0), {noreply, State}; @@ -223,18 +240,32 @@ tcp_send(Sock, Data) -> maybe_flush(State = #state{pending_size = PendingSize}) -> case PendingSize > ?FLUSH_THRESHOLD of +<<<<<<< HEAD true -> flush(State); +======= + true -> flush(State); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) false -> State end. flush(State = #state{pending = []}) -> State; +<<<<<<< HEAD flush(State = #state{sock = Sock, pending = Pending}) -> case rabbit_net:send(Sock, lists:reverse(Pending)) of ok -> State#state{pending = [], pending_size = 0}; +======= +flush(State0 = #state{sock = Sock, + pending = Pending}) -> + case rabbit_net:send(Sock, lists:reverse(Pending)) of + ok -> + State = State0#state{pending = [], + pending_size = 0}, + rabbit_event:ensure_stats_timer(State, #state.stats_timer, emit_stats); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {error, Reason} -> exit({writer, send_failed, Reason}) end. diff --git a/deps/rabbit/src/rabbit_amqqueue.erl b/deps/rabbit/src/rabbit_amqqueue.erl index 721fbc3f9efd..cd7ecc037c96 100644 --- a/deps/rabbit/src/rabbit_amqqueue.erl +++ b/deps/rabbit/src/rabbit_amqqueue.erl @@ -762,6 +762,13 @@ augment_declare_args(VHost, Durable, Exclusive, AutoDelete, Args0) -> end end. +<<<<<<< HEAD +======= +-spec update_args_table_with_queue_type( + rabbit_queue_type:queue_type() | binary(), + boolean(), boolean(), boolean(), + rabbit_framing:amqp_table()) -> rabbit_framing:amqp_table(). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) update_args_table_with_queue_type(DefaultQueueType, Durable, Exclusive, AutoDelete, Args) -> Type = rabbit_queue_type:discover(DefaultQueueType), IsPermitted = is_queue_args_combination_permitted( @@ -1834,8 +1841,13 @@ internal_delete(Queue, ActingUser, Reason) -> {error, timeout} = Err -> Err; Deletions -> +<<<<<<< HEAD _ = rabbit_binding:process_deletions(Deletions), rabbit_binding:notify_deletions(Deletions, ?INTERNAL_USER), +======= + ok = rabbit_binding:process_deletions(Deletions), + ok = rabbit_binding:notify_deletions(Deletions, ?INTERNAL_USER), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_core_metrics:queue_deleted(QueueName), ok = rabbit_event:notify(queue_deleted, [{name, QueueName}, @@ -1958,6 +1970,7 @@ filter_transient_queues_to_delete(Node) -> end. notify_queue_binding_deletions(QueueDeletions) when is_list(QueueDeletions) -> +<<<<<<< HEAD Deletions = rabbit_binding:process_deletions( lists:foldl(fun rabbit_binding:combine_deletions/2, rabbit_binding:new_deletions(), @@ -1966,6 +1979,16 @@ notify_queue_binding_deletions(QueueDeletions) when is_list(QueueDeletions) -> notify_queue_binding_deletions(QueueDeletions) -> Deletions = rabbit_binding:process_deletions(QueueDeletions), rabbit_binding:notify_deletions(Deletions, ?INTERNAL_USER). +======= + Deletions = lists:foldl( + fun rabbit_binding:combine_deletions/2, + rabbit_binding:new_deletions(), QueueDeletions), + ok = rabbit_binding:process_deletions(Deletions), + rabbit_binding:notify_deletions(Deletions, ?INTERNAL_USER); +notify_queue_binding_deletions(QueueDeletions) -> + ok = rabbit_binding:process_deletions(QueueDeletions), + rabbit_binding:notify_deletions(QueueDeletions, ?INTERNAL_USER). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) notify_transient_queues_deleted(QueueDeletions) -> lists:foreach( diff --git a/deps/rabbit/src/rabbit_amqqueue_process.erl b/deps/rabbit/src/rabbit_amqqueue_process.erl index 63f886bd3763..576415116089 100644 --- a/deps/rabbit/src/rabbit_amqqueue_process.erl +++ b/deps/rabbit/src/rabbit_amqqueue_process.erl @@ -725,6 +725,7 @@ maybe_deliver_or_enqueue(Delivery = #delivery{message = Message}, {IsDuplicate, BQS1} = BQ:is_duplicate(Message, BQS), State1 = State#q{backing_queue_state = BQS1}, case IsDuplicate of +<<<<<<< HEAD true -> State1; {true, drop} -> State1; %% Drop publish and nack to publisher @@ -732,6 +733,28 @@ maybe_deliver_or_enqueue(Delivery = #delivery{message = Message}, send_reject_publish(Delivery, State1); %% Enqueue and maybe drop head later false -> +======= + true -> + %% Publish to DLX + _ = with_dlx( + DLX, + fun (X) -> + rabbit_global_counters:messages_dead_lettered(maxlen, + rabbit_classic_queue, + at_most_once, 1), + QName = qname(State1), + rabbit_dead_letter:publish(Message, maxlen, X, RK, QName) + end, + fun () -> + rabbit_global_counters:messages_dead_lettered(maxlen, + rabbit_classic_queue, + disabled, 1) + end), + %% Drop publish and nack to publisher + send_reject_publish(Delivery, State1); + false -> + %% Enqueue and maybe drop head later +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) deliver_or_enqueue(Delivery, Delivered, State1) end end. diff --git a/deps/rabbit/src/rabbit_backing_queue.erl b/deps/rabbit/src/rabbit_backing_queue.erl index ffa0a791f1b5..2ad985de3713 100644 --- a/deps/rabbit/src/rabbit_backing_queue.erl +++ b/deps/rabbit/src/rabbit_backing_queue.erl @@ -220,9 +220,14 @@ %% Called prior to a publish or publish_delivered call. Allows the BQ %% to signal that it's already seen this message, (e.g. it was published +<<<<<<< HEAD %% or discarded previously) specifying whether to drop the message or reject it. -callback is_duplicate(mc:state(), state()) -> {{true, drop} | {true, reject} | boolean(), state()}. +======= +%% or discarded previously). +-callback is_duplicate(mc:state(), state()) -> {boolean(), state()}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -callback set_queue_mode(queue_mode(), state()) -> state(). diff --git a/deps/rabbit/src/rabbit_binding.erl b/deps/rabbit/src/rabbit_binding.erl index cf7f79b51e6a..cd689bbfb447 100644 --- a/deps/rabbit/src/rabbit_binding.erl +++ b/deps/rabbit/src/rabbit_binding.erl @@ -13,7 +13,11 @@ -export([list/1, list_for_source/1, list_for_destination/1, list_for_source_and_destination/2, list_for_source_and_destination/3, list_explicit/0]). +<<<<<<< HEAD -export([new_deletions/0, combine_deletions/2, add_deletion/3, +======= +-export([new_deletions/0, combine_deletions/2, add_deletion/5, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) process_deletions/1, notify_deletions/2, group_bindings_fold/3]). -export([info_keys/0, info/1, info/2, info_all/1, info_all/2, info_all/4]). @@ -22,6 +26,12 @@ -export([reverse_route/1, index_route/1]). -export([binding_type/2]). +<<<<<<< HEAD +======= +%% For testing only +-export([fetch_deletion/2]). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -define(DEFAULT_EXCHANGE(VHostPath), #resource{virtual_host = VHostPath, kind = exchange, name = <<>>}). @@ -50,9 +60,18 @@ rabbit_types:ok_or_error(rabbit_types:amqp_error())). -type bindings() :: [rabbit_types:binding()]. +<<<<<<< HEAD %% TODO this should really be opaque but that seems to confuse 17.1's %% dialyzer into objecting to everything that uses it. -type deletions() :: dict:dict(). +======= +-record(deletion, {exchange :: rabbit_types:exchange(), + %% Whether the exchange was deleted. + deleted :: boolean(), + bindings :: sets:set(rabbit_types:binding())}). + +-opaque deletions() :: #{XName :: rabbit_exchange:name() => #deletion{}}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %%---------------------------------------------------------------------------- @@ -159,6 +178,22 @@ binding_type0(false, true) -> binding_type0(_, _) -> transient. +<<<<<<< HEAD +======= +binding_checks(Binding, InnerFun) -> + fun(Src, Dst) -> + case rabbit_exchange:validate_binding(Src, Binding) of + ok -> + %% this argument is used to check queue exclusivity; + %% in general, we want to fail on that in preference to + %% anything else + InnerFun(Src, Dst); + Err -> + Err + end + end. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec remove(rabbit_types:binding(), rabbit_types:username()) -> bind_res(). remove(Binding, ActingUser) -> remove(Binding, fun (_Src, _Dst) -> ok end, ActingUser). @@ -360,6 +395,7 @@ index_route(#route{binding = #binding{source = Source, %% ---------------------------------------------------------------------------- %% Binding / exchange deletion abstraction API %% ---------------------------------------------------------------------------- +<<<<<<< HEAD anything_but( NotThis, NotThis, NotThis) -> NotThis; anything_but( NotThis, NotThis, This) -> This; @@ -411,6 +447,98 @@ notify_deletions(Deletions, ActingUser) -> (_XName, {_X, not_deleted, Bs}, ok) -> notify_bindings_deletion(Bs, ActingUser) end, ok, Deletions). +======= +%% +%% `deletions()' describe a set of removals of bindings and/or exchanges from +%% the metadata store. +%% +%% This deletion collection is used for two purposes: +%% +%%
    +%%
  • "Processing" of deletions. Processing here means that the +%% exchanges and bindings are passed into the {@link rabbit_exchange} +%% callbacks. When an exchange is deleted the `rabbit_exchange:delete/1' +%% callback is invoked and when the exchange is not deleted but some bindings +%% are deleted the `rabbit_exchange:remove_bindings/2' is invoked.
  • +%%
  • Notification of metadata deletion. Like other internal +%% notifications, {@link rabbit_binding:notify_deletions()} uses {@link +%% rabbit_event} to notify any interested consumers of a resource deletion. +%% An example consumer of {@link rabbit_event} is the `rabbitmq_event_exchange' +%% plugin which publishes these notifications as messages.
  • +%%
+%% +%% The point of collecting deletions into this opaque type is to be able to +%% collect all bindings deleted for a given exchange into a list. This allows +%% us to invoke the `rabbit_exchange:remove_bindings/2' callback with all +%% deleted bindings at once rather than passing each deleted binding +%% individually. + +-spec new_deletions() -> deletions(). + +new_deletions() -> #{}. + +-spec add_deletion(XName, X, XDeleted, Bindings, Deletions) -> Deletions1 + when + XName :: rabbit_exchange:name(), + X :: rabbit_types:exchange(), + XDeleted :: deleted | not_deleted, + Bindings :: bindings(), + Deletions :: deletions(), + Deletions1 :: deletions(). + +add_deletion(XName, X, WasDeleted, Bindings, Deletions) + when (WasDeleted =:= deleted orelse WasDeleted =:= not_deleted) andalso + is_list(Bindings) andalso is_map(Deletions) -> + WasDeleted1 = case WasDeleted of + deleted -> true; + not_deleted -> false + end, + Bindings1 = sets:from_list(Bindings, [{version, 2}]), + Deletion = #deletion{exchange = X, + deleted = WasDeleted1, + bindings = Bindings1}, + maps:update_with( + XName, + fun(Deletion1) -> + merge_deletion(Deletion1, Deletion) + end, Deletion, Deletions). + +-spec combine_deletions(deletions(), deletions()) -> deletions(). + +combine_deletions(Deletions1, Deletions2) + when is_map(Deletions1) andalso is_map(Deletions2) -> + maps:merge_with( + fun (_XName, Deletion1, Deletion2) -> + merge_deletion(Deletion1, Deletion2) + end, Deletions1, Deletions2). + +merge_deletion( + #deletion{deleted = Deleted1, bindings = Bindings1}, + #deletion{exchange = X2, deleted = Deleted2, bindings = Bindings2}) -> + %% Assume that X2 is more up to date than X1. + X = X2, + Deleted = Deleted1 orelse Deleted2, + Bindings = sets:union(Bindings1, Bindings2), + #deletion{exchange = X, + deleted = Deleted, + bindings = Bindings}. + +-spec notify_deletions(Deletions, ActingUser) -> ok when + Deletions :: rabbit_binding:deletions(), + ActingUser :: rabbit_types:username(). + +notify_deletions(Deletions, ActingUser) when is_map(Deletions) -> + maps:foreach( + fun (XName, #deletion{deleted = XDeleted, bindings = Bindings}) -> + case XDeleted of + true -> + notify_exchange_deletion(XName, ActingUser), + notify_bindings_deletion(Bindings, ActingUser); + false -> + notify_bindings_deletion(Bindings, ActingUser) + end + end, Deletions). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) notify_exchange_deletion(XName, ActingUser) -> ok = rabbit_event:notify( @@ -418,6 +546,7 @@ notify_exchange_deletion(XName, ActingUser) -> [{name, XName}, {user_who_performed_action, ActingUser}]). +<<<<<<< HEAD notify_bindings_deletion(Bs, ActingUser) -> [rabbit_event:notify(binding_deleted, info(B) ++ [{user_who_performed_action, ActingUser}]) @@ -449,4 +578,60 @@ binding_checks(Binding, InnerFun) -> Err -> Err end +======= +notify_bindings_deletion(Bindings, ActingUser) -> + sets:fold( + fun(Binding, ok) -> + rabbit_event:notify( + binding_deleted, + info(Binding) ++ [{user_who_performed_action, ActingUser}]), + ok + end, ok, Bindings). + +-spec process_deletions(deletions()) -> ok. +process_deletions(Deletions) -> + maps:foreach( + fun (_XName, #deletion{exchange = X, + deleted = XDeleted, + bindings = Bindings}) -> + Serial = rabbit_exchange:serial(X), + case XDeleted of + true -> + rabbit_exchange:callback(X, delete, Serial, [X]); + false -> + Bindings1 = sets:to_list(Bindings), + rabbit_exchange:callback( + X, remove_bindings, Serial, [X, Bindings1]) + end + end, Deletions). + +-spec fetch_deletion(XName, Deletions) -> Ret when + XName :: rabbit_exchange:name(), + Deletions :: deletions(), + Ret :: {X, WasDeleted, Bindings}, + X :: rabbit_types:exchange(), + WasDeleted :: deleted | not_deleted, + Bindings :: bindings(). +%% @doc Fetches the deletions for the given exchange name. +%% +%% This function is only intended for use in tests. +%% +%% @private + +fetch_deletion(XName, Deletions) -> + case maps:find(XName, Deletions) of + {ok, #deletion{exchange = X, + deleted = Deleted, + bindings = Bindings}} -> + WasDeleted = case Deleted of + true -> + deleted; + false -> + not_deleted + end, + Bindings1 = sets:to_list(Bindings), + {X, WasDeleted, Bindings1}; + error -> + error +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end. diff --git a/deps/rabbit/src/rabbit_channel.erl b/deps/rabbit/src/rabbit_channel.erl index 303776471396..8693ef667137 100644 --- a/deps/rabbit/src/rabbit_channel.erl +++ b/deps/rabbit/src/rabbit_channel.erl @@ -471,7 +471,11 @@ force_event_refresh(Ref) -> list_queue_states(Pid) -> gen_server2:call(Pid, list_queue_states). +<<<<<<< HEAD -spec update_user_state(pid(), rabbit_types:auth_user()) -> 'ok' | {error, channel_terminated}. +======= +-spec update_user_state(pid(), rabbit_types:user()) -> 'ok' | {error, channel_terminated}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) update_user_state(Pid, UserState) when is_pid(Pid) -> case erlang:is_process_alive(Pid) of @@ -997,7 +1001,11 @@ check_msg_size(Content, GCThreshold) -> Size = rabbit_basic:maybe_gc_large_msg(Content, GCThreshold), case Size =< MaxMessageSize of true -> +<<<<<<< HEAD ok; +======= + rabbit_msg_size_metrics:observe(amqp091, Size); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) false -> Fmt = case MaxMessageSize of ?MAX_MSG_SIZE -> diff --git a/deps/rabbit/src/rabbit_core_ff.erl b/deps/rabbit/src/rabbit_core_ff.erl index 5475909eec54..ae00106080dd 100644 --- a/deps/rabbit/src/rabbit_core_ff.erl +++ b/deps/rabbit/src/rabbit_core_ff.erl @@ -10,14 +10,24 @@ -rabbit_feature_flag( {classic_mirrored_queue_version, #{desc => "Support setting version for classic mirrored queues", +<<<<<<< HEAD stability => required +======= + stability => required, + require_level => hard +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }}). -rabbit_feature_flag( {quorum_queue, #{desc => "Support queues of type `quorum`", doc_url => "https://www.rabbitmq.com/docs/quorum-queues", +<<<<<<< HEAD stability => required +======= + stability => required, + require_level => hard +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }}). -rabbit_feature_flag( @@ -25,6 +35,10 @@ #{desc => "Support queues of type `stream`", doc_url => "https://www.rabbitmq.com/docs/stream", stability => required, +<<<<<<< HEAD +======= + require_level => hard, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) depends_on => [quorum_queue] }}). @@ -32,18 +46,29 @@ {implicit_default_bindings, #{desc => "Default bindings are now implicit, instead of " "being stored in the database", +<<<<<<< HEAD stability => required +======= + stability => required, + require_level => hard +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }}). -rabbit_feature_flag( {virtual_host_metadata, #{desc => "Virtual host metadata (description, tags, etc)", +<<<<<<< HEAD stability => required +======= + stability => required, + require_level => hard +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }}). -rabbit_feature_flag( {maintenance_mode_status, #{desc => "Maintenance mode status", +<<<<<<< HEAD stability => required }}). @@ -51,6 +76,17 @@ {user_limits, #{desc => "Configure connection and channel limits for a user", stability => required +======= + stability => required, + require_level => hard + }}). + +-rabbit_feature_flag( + {user_limits, + #{desc => "Configure connection and channel limits for a user", + stability => required, + require_level => hard +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }}). -rabbit_feature_flag( @@ -58,33 +94,62 @@ #{desc => "Single active consumer for streams", doc_url => "https://www.rabbitmq.com/docs/stream", stability => required, +<<<<<<< HEAD +======= + require_level => hard, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) depends_on => [stream_queue] }}). -rabbit_feature_flag( +<<<<<<< HEAD {feature_flags_v2, #{desc => "Feature flags subsystem V2", stability => required +======= + {feature_flags_v2, + #{desc => "Feature flags subsystem V2", + stability => required, + require_level => hard +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }}). -rabbit_feature_flag( {direct_exchange_routing_v2, +<<<<<<< HEAD #{desc => "v2 direct exchange routing implementation", stability => required, depends_on => [feature_flags_v2, implicit_default_bindings] +======= + #{desc => "v2 direct exchange routing implementation", + stability => required, + require_level => hard, + depends_on => [feature_flags_v2, implicit_default_bindings] +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }}). -rabbit_feature_flag( {listener_records_in_ets, +<<<<<<< HEAD #{desc => "Store listener records in ETS instead of Mnesia", stability => required, depends_on => [feature_flags_v2] +======= + #{desc => "Store listener records in ETS instead of Mnesia", + stability => required, + require_level => hard, + depends_on => [feature_flags_v2] +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }}). -rabbit_feature_flag( {tracking_records_in_ets, #{desc => "Store tracking records in ETS instead of Mnesia", stability => required, +<<<<<<< HEAD +======= + require_level => hard, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) depends_on => [feature_flags_v2] }}). @@ -94,6 +159,10 @@ doc_url => "https://github.com/rabbitmq/rabbitmq-server/issues/5931", %%TODO remove compatibility code stability => required, +<<<<<<< HEAD +======= + require_level => hard, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) depends_on => [stream_queue] }}). @@ -102,6 +171,10 @@ #{desc => "Support for restarting streams with optional preferred next leader argument." "Used to implement stream leader rebalancing", stability => required, +<<<<<<< HEAD +======= + require_level => hard, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) depends_on => [stream_queue] }}). @@ -110,6 +183,10 @@ #{desc => "Bug fix to unblock a group of consumers in a super stream partition", doc_url => "https://github.com/rabbitmq/rabbitmq-server/issues/7743", stability => required, +<<<<<<< HEAD +======= + require_level => hard, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) depends_on => [stream_single_active_consumer] }}). @@ -117,6 +194,10 @@ {stream_filtering, #{desc => "Support for stream filtering.", stability => required, +<<<<<<< HEAD +======= + require_level => hard, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) depends_on => [stream_queue] }}). @@ -124,14 +205,25 @@ {message_containers, #{desc => "Message containers.", stability => required, +<<<<<<< HEAD +======= + require_level => hard, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) depends_on => [feature_flags_v2] }}). -rabbit_feature_flag( {khepri_db, +<<<<<<< HEAD #{desc => "New Raft-based metadata store. Fully supported as of RabbitMQ 4.0", doc_url => "https://www.rabbitmq.com/docs/next/metadata-store", stability => experimental, +======= + #{desc => "New Raft-based metadata store.", + doc_url => "https://www.rabbitmq.com/docs/next/metadata-store", + stability => experimental, + experiment_level => supported, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) depends_on => [feature_flags_v2, direct_exchange_routing_v2, maintenance_mode_status, @@ -154,6 +246,10 @@ #{desc => "A new internal command that is used to update streams as " "part of a policy.", stability => required, +<<<<<<< HEAD +======= + require_level => hard, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) depends_on => [stream_queue] }}). diff --git a/deps/rabbit/src/rabbit_db_binding.erl b/deps/rabbit/src/rabbit_db_binding.erl index 37bc82ba246c..fbd6d60cb04f 100644 --- a/deps/rabbit/src/rabbit_db_binding.erl +++ b/deps/rabbit/src/rabbit_db_binding.erl @@ -304,7 +304,14 @@ delete_in_mnesia(Src, Dst, B) -> should_index_table(Src), fun delete/3), Deletions0 = maybe_auto_delete_exchange_in_mnesia( B#binding.source, [B], rabbit_binding:new_deletions(), false), +<<<<<<< HEAD fun() -> {ok, rabbit_binding:process_deletions(Deletions0)} end. +======= + fun() -> + ok = rabbit_binding:process_deletions(Deletions0), + {ok, Deletions0} + end. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) absent_errs_only_in_mnesia(Names) -> Errs = [E || Name <- Names, @@ -354,7 +361,12 @@ delete_in_khepri(#binding{source = SrcName, {error, _} = Err -> Err; Deletions -> +<<<<<<< HEAD {ok, rabbit_binding:process_deletions(Deletions)} +======= + ok = rabbit_binding:process_deletions(Deletions), + {ok, Deletions} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end. exists_in_khepri(Path, Binding) -> @@ -381,6 +393,7 @@ delete_in_khepri(Binding) -> end. maybe_auto_delete_exchange_in_khepri(XName, Bindings, Deletions, OnlyDurable) -> +<<<<<<< HEAD {Entry, Deletions1} = case rabbit_db_exchange:maybe_auto_delete_in_khepri(XName, OnlyDurable) of {not_deleted, X} -> @@ -390,6 +403,20 @@ maybe_auto_delete_exchange_in_khepri(XName, Bindings, Deletions, OnlyDurable) -> rabbit_binding:combine_deletions(Deletions, Deletions2)} end, rabbit_binding:add_deletion(XName, Entry, Deletions1). +======= + case rabbit_db_exchange:maybe_auto_delete_in_khepri(XName, OnlyDurable) of + {not_deleted, undefined} -> + Deletions; + {not_deleted, X} -> + rabbit_binding:add_deletion( + XName, X, not_deleted, Bindings, Deletions); + {deleted, X, Deletions1} -> + Deletions2 = rabbit_binding:combine_deletions( + Deletions, Deletions1), + rabbit_binding:add_deletion( + XName, X, deleted, Bindings, Deletions2) + end. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% ------------------------------------------------------------------- %% get_all(). @@ -1153,6 +1180,7 @@ sync_index_route(_, _, _) -> OnlyDurable :: boolean(), Ret :: rabbit_binding:deletions(). maybe_auto_delete_exchange_in_mnesia(XName, Bindings, Deletions, OnlyDurable) -> +<<<<<<< HEAD {Entry, Deletions1} = case rabbit_db_exchange:maybe_auto_delete_in_mnesia(XName, OnlyDurable) of {not_deleted, X} -> @@ -1162,6 +1190,20 @@ maybe_auto_delete_exchange_in_mnesia(XName, Bindings, Deletions, OnlyDurable) -> rabbit_binding:combine_deletions(Deletions, Deletions2)} end, rabbit_binding:add_deletion(XName, Entry, Deletions1). +======= + case rabbit_db_exchange:maybe_auto_delete_in_mnesia(XName, OnlyDurable) of + {not_deleted, undefined} -> + Deletions; + {not_deleted, X} -> + rabbit_binding:add_deletion( + XName, X, not_deleted, Bindings, Deletions); + {deleted, X, Deletions1} -> + Deletions2 = rabbit_binding:combine_deletions( + Deletions, Deletions1), + rabbit_binding:add_deletion( + XName, X, deleted, Bindings, Deletions2) + end. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% Instead of locking entire table on remove operations we can lock the %% affected resource only. diff --git a/deps/rabbit/src/rabbit_db_cluster.erl b/deps/rabbit/src/rabbit_db_cluster.erl index 1df145ccb117..1aa241829b94 100644 --- a/deps/rabbit/src/rabbit_db_cluster.erl +++ b/deps/rabbit/src/rabbit_db_cluster.erl @@ -57,7 +57,11 @@ can_join(RemoteNode) -> "DB: checking if `~ts` can join cluster using remote node `~ts`", [node(), RemoteNode], #{domain => ?RMQLOG_DOMAIN_DB}), +<<<<<<< HEAD case rabbit_feature_flags:check_node_compatibility(RemoteNode) of +======= + case rabbit_feature_flags:check_node_compatibility(RemoteNode, true) of +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ok -> case rabbit_khepri:is_enabled(RemoteNode) of true -> can_join_using_khepri(RemoteNode); diff --git a/deps/rabbit/src/rabbit_db_exchange.erl b/deps/rabbit/src/rabbit_db_exchange.erl index 5d434563f7e3..d40e5827e6b0 100644 --- a/deps/rabbit/src/rabbit_db_exchange.erl +++ b/deps/rabbit/src/rabbit_db_exchange.erl @@ -573,7 +573,11 @@ next_serial_in_khepri_tx(#exchange{name = XName}) -> IfUnused :: boolean(), Exchange :: rabbit_types:exchange(), Binding :: rabbit_types:binding(), +<<<<<<< HEAD Deletions :: dict:dict(), +======= + Deletions :: rabbit_binding:deletions(), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Ret :: {deleted, Exchange, [Binding], Deletions} | {error, not_found} | {error, in_use} | @@ -624,7 +628,11 @@ unconditional_delete_in_mnesia(X, OnlyDurable) -> RemoveBindingsForSource :: boolean(), Exchange :: rabbit_types:exchange(), Binding :: rabbit_types:binding(), +<<<<<<< HEAD Deletions :: dict:dict(), +======= + Deletions :: rabbit_binding:deletions(), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Ret :: {error, not_found} | {error, in_use} | {deleted, Exchange, [Binding], Deletions}. delete_in_mnesia(X = #exchange{name = XName}, OnlyDurable, RemoveBindingsForSource) -> ok = mnesia:delete({?MNESIA_TABLE, XName}), @@ -695,7 +703,11 @@ delete_all_in_mnesia_tx(VHostName) -> {deleted, #exchange{name = XName}, Bindings, XDeletions} = unconditional_delete_in_mnesia( X, false), XDeletions1 = rabbit_binding:add_deletion( +<<<<<<< HEAD XName, {X, deleted, Bindings}, XDeletions), +======= + XName, X, deleted, Bindings, XDeletions), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_binding:combine_deletions(Acc, XDeletions1) end, rabbit_binding:new_deletions(), Xs), {ok, Deletions}. @@ -716,7 +728,11 @@ delete_all_in_khepri_tx(VHostName) -> rabbit_db_binding:delete_all_for_exchange_in_khepri( X, false, true), Deletions1 = rabbit_binding:add_deletion( +<<<<<<< HEAD XName, {X, deleted, Bindings}, XDeletions), +======= + XName, X, deleted, Bindings, XDeletions), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_binding:combine_deletions(Deletions, Deletions1) end, rabbit_binding:new_deletions(), NodeProps), {ok, Deletions}. diff --git a/deps/rabbit/src/rabbit_depr_ff_extra.erl b/deps/rabbit/src/rabbit_depr_ff_extra.erl index 5267c3efbfb6..7c9c07e1902e 100644 --- a/deps/rabbit/src/rabbit_depr_ff_extra.erl +++ b/deps/rabbit/src/rabbit_depr_ff_extra.erl @@ -2,7 +2,11 @@ %% License, v. 2.0. If a copy of the MPL was not distributed with this %% file, You can obtain one at https://mozilla.org/MPL/2.0/. %% +<<<<<<< HEAD %% Copyright (c) 2023 Broadcom. All Rights Reserved. The term “Broadcom” +======= +%% Copyright (c) 2023-2024 Broadcom. All Rights Reserved. The term “Broadcom” +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. %% %% @doc diff --git a/deps/rabbit/src/rabbit_deprecated_features.erl b/deps/rabbit/src/rabbit_deprecated_features.erl index 93289be033eb..c7342b143de3 100644 --- a/deps/rabbit/src/rabbit_deprecated_features.erl +++ b/deps/rabbit/src/rabbit_deprecated_features.erl @@ -2,11 +2,21 @@ %% License, v. 2.0. If a copy of the MPL was not distributed with this %% file, You can obtain one at https://mozilla.org/MPL/2.0/. %% +<<<<<<< HEAD %% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. %% %% @author The RabbitMQ team %% @copyright 2007-2024 Broadcom. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +======= +%% Copyright (c) 2023-2024 Broadcom. All Rights Reserved. The term “Broadcom” +%% refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +%% @author The RabbitMQ team +%% @copyright 2023-2024 Broadcom. The term “Broadcom” refers to Broadcom Inc. +%% and/or its subsidiaries. All rights reserved. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% %% @doc %% This module provides an API to manage deprecated features in RabbitMQ. It diff --git a/deps/rabbit/src/rabbit_exchange.erl b/deps/rabbit/src/rabbit_exchange.erl index b4037f9a8078..73b4053df373 100644 --- a/deps/rabbit/src/rabbit_exchange.erl +++ b/deps/rabbit/src/rabbit_exchange.erl @@ -470,6 +470,7 @@ delete(XName, IfUnused, Username) -> _ = rabbit_runtime_parameters:set(XName#resource.virtual_host, ?EXCHANGE_DELETE_IN_PROGRESS_COMPONENT, XName#resource.name, true, Username), +<<<<<<< HEAD Deletions = process_deletions(rabbit_db_exchange:delete(XName, IfUnused)), case Deletions of {error, _} -> @@ -477,6 +478,17 @@ delete(XName, IfUnused, Username) -> _ -> rabbit_binding:notify_deletions(Deletions, Username), ok +======= + case rabbit_db_exchange:delete(XName, IfUnused) of + {deleted, #exchange{name = XName} = X, Bs, Deletions} -> + Deletions1 = rabbit_binding:add_deletion( + XName, X, deleted, Bs, Deletions), + ok = rabbit_binding:process_deletions(Deletions1), + ok = rabbit_binding:notify_deletions(Deletions1, Username), + ok; + {error, _} = Err -> + Err +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end after rabbit_runtime_parameters:clear(XName#resource.virtual_host, @@ -491,6 +503,7 @@ delete(XName, IfUnused, Username) -> delete_all(VHostName, ActingUser) -> {ok, Deletions} = rabbit_db_exchange:delete_all(VHostName), +<<<<<<< HEAD Deletions1 = rabbit_binding:process_deletions(Deletions), rabbit_binding:notify_deletions(Deletions1, ActingUser), ok. @@ -502,6 +515,12 @@ process_deletions({deleted, #exchange{name = XName} = X, Bs, Deletions}) -> rabbit_binding:add_deletion( XName, {X, deleted, Bs}, Deletions)). +======= + ok = rabbit_binding:process_deletions(Deletions), + ok = rabbit_binding:notify_deletions(Deletions, ActingUser), + ok. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec ensure_deleted(ExchangeName, IfUnused, Username) -> Ret when ExchangeName :: name(), IfUnused :: boolean(), diff --git a/deps/rabbit/src/rabbit_feature_flags.erl b/deps/rabbit/src/rabbit_feature_flags.erl index f635e50d2b5f..682830ea277e 100644 --- a/deps/rabbit/src/rabbit_feature_flags.erl +++ b/deps/rabbit/src/rabbit_feature_flags.erl @@ -2,11 +2,21 @@ %% License, v. 2.0. If a copy of the MPL was not distributed with this %% file, You can obtain one at https://mozilla.org/MPL/2.0/. %% +<<<<<<< HEAD %% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. %% %% @author The RabbitMQ team %% @copyright 2007-2024 Broadcom. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +======= +%% Copyright (c) 2019-2024 Broadcom. All Rights Reserved. The term “Broadcom” +%% refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +%% @author The RabbitMQ team +%% @copyright 2019-2024 Broadcom. The term “Broadcom” refers to Broadcom Inc. +%% and/or its subsidiaries. All rights reserved. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% %% @doc %% This module offers a framework to declare capabilities a RabbitMQ node @@ -103,7 +113,13 @@ init/0, get_state/1, get_stability/1, +<<<<<<< HEAD check_node_compatibility/1, +======= + get_require_level/1, + get_experiment_level/1, + check_node_compatibility/1, check_node_compatibility/2, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) sync_feature_flags_with_cluster/2, refresh_feature_flags_after_app_load/0, enabled_feature_flags_list_file/0 @@ -145,6 +161,11 @@ -type feature_props() :: #{desc => string(), doc_url => string(), stability => stability(), +<<<<<<< HEAD +======= + require_level => require_level(), + experiment_level => experiment_level(), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) depends_on => [feature_name()], callbacks => #{callback_name() => callback_fun_name()}}. @@ -181,6 +202,11 @@ desc => string(), doc_url => string(), stability => stability(), +<<<<<<< HEAD +======= + require_level => require_level(), + experiment_level => experiment_level(), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) depends_on => [feature_name()], callbacks => #{callback_name() => callback_fun_name()}, @@ -205,6 +231,36 @@ %% Experimental feature flags are not enabled by default on a fresh RabbitMQ %% node. They must be enabled by the user. +<<<<<<< HEAD +======= +-type require_level() :: hard | soft. +%% The level of requirement of a feature flag. +%% +%% A hard required feature flags must be enabled before a RabbitMQ node is +%% upgraded to a version where it is required. +%% +%% A soft required feature flag will be automatically enabled when a RabbitMQ +%% node is upgraded to a version where it is required. + +-type experiment_level() :: unsupported | supported. +%% The level of support of an experimental feature flag. +%% +%% At first, an experimental feature flag is offered to give a chance to users +%% to try it and give feedback as part of the design and development of the +%% feature. At this stage, it is unsupported: it must not be enabled in a +%% production environment and upgrade to a later version of RabbitMQ while +%% this experimental feature flag is enabled is not supported. +%% +%% Then, the experimental feature flag becomes supported. At this point, it is +%% stable enough that upgrading is guarantied and help will be provided. +%% However it is not mature enough to be marked as stable (which would make it +%% enabled by default in a new deployment or when running `rabbitmqctl +%% enable_feature_flag all'. +%% +%% The next step is to change its stability to `stable'. Once done, the +%% `experiment_level()' field is irrelevant. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -type callback_fun_name() :: {Module :: module(), Function :: atom()}. %% The name of the module and function to call when changing the state of %% the feature flag. @@ -313,6 +369,11 @@ feature_state/0, feature_states/0, stability/0, +<<<<<<< HEAD +======= + require_level/0, + experiment_level/0, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) callback_fun_name/0, callbacks/0, callback_name/0, @@ -682,13 +743,24 @@ info() -> info(Options) when is_map(Options) -> rabbit_ff_extra:info(Options). +<<<<<<< HEAD -spec get_state(feature_name()) -> enabled | disabled | unavailable. +======= +-spec get_state(feature_name()) -> enabled | + state_changing | + disabled | + unavailable. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% @doc %% Returns the state of a feature flag. %% %% The possible states are: %%
    %%
  • `enabled': the feature flag is enabled.
  • +<<<<<<< HEAD +======= +%%
  • `state_changing': the feature flag is being enabled.
  • +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %%
  • `disabled': the feature flag is supported by all nodes in the %% cluster but currently disabled.
  • %%
  • `unavailable': the feature flag is unsupported by at least one @@ -696,6 +768,7 @@ info(Options) when is_map(Options) -> %%
%% %% @param FeatureName The name of the feature flag to check. +<<<<<<< HEAD %% @returns `enabled', `disabled' or `unavailable'. get_state(FeatureName) when is_atom(FeatureName) -> @@ -706,6 +779,22 @@ get_state(FeatureName) when is_atom(FeatureName) -> true -> disabled; false -> unavailable end +======= +%% @returns `enabled', `state_changing', `disabled' or `unavailable'. + +get_state(FeatureName) when is_atom(FeatureName) -> + IsEnabled = is_enabled(FeatureName, non_blocking), + case IsEnabled of + true -> + enabled; + state_changing -> + state_changing; + false -> + case is_supported(FeatureName) of + true -> disabled; + false -> unavailable + end +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end. -spec get_stability @@ -742,7 +831,11 @@ get_stability(FeatureName) when is_atom(FeatureName) -> undefined -> undefined; FeatureProps -> get_stability(FeatureProps) end; +<<<<<<< HEAD get_stability(FeatureProps) when ?IS_FEATURE_FLAG(FeatureProps) -> +======= +get_stability(FeatureProps) when ?IS_FEATURE_FLAG(FeatureProps) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) maps:get(stability, FeatureProps, stable); get_stability(FeatureProps) when ?IS_DEPRECATION(FeatureProps) -> Phase = rabbit_deprecated_features:get_phase(FeatureProps), @@ -753,6 +846,90 @@ get_stability(FeatureProps) when ?IS_DEPRECATION(FeatureProps) -> permitted_by_default -> experimental end. +<<<<<<< HEAD +======= +-spec get_require_level +(FeatureName) -> RequireLevel | undefined when + FeatureName :: feature_name(), + RequireLevel :: require_level() | none; +(FeatureProps) -> RequireLevel when + FeatureProps :: + feature_props_extended() | + rabbit_deprecated_features:feature_props_extended(), + RequireLevel :: require_level() | none. +%% @doc +%% Returns the requirement level of a feature flag. +%% +%% The possible requirement levels are: +%%
    +%%
  • `hard': the feature flag must be enabled before the RabbitMQ node is +%% upgraded to a version where it is hard required.
  • +%%
  • `soft': the feature flag will be automatically enabled wher a RabbitMQ +%% node is upgraded to a version where it is soft required.
  • +%%
  • `none': the feature flag is not required.
  • +%%
+%% +%% @param FeatureName The name of the feature flag to check. +%% @param FeatureProps A feature flag properties map. +%% @returns `hard', `soft' or `none', or `undefined' if the given feature flag +%% name doesn't correspond to a known feature flag. + +get_require_level(FeatureName) when is_atom(FeatureName) -> + case rabbit_ff_registry_wrapper:get(FeatureName) of + undefined -> undefined; + FeatureProps -> get_require_level(FeatureProps) + end; +get_require_level(FeatureProps) when ?IS_FEATURE_FLAG(FeatureProps) -> + case get_stability(FeatureProps) of + required -> maps:get(require_level, FeatureProps, soft); + _ -> none + end; +get_require_level(FeatureProps) when ?IS_DEPRECATION(FeatureProps) -> + case get_stability(FeatureProps) of + required -> hard; + _ -> none + end. + +-spec get_experiment_level +(FeatureName) -> ExperimentLevel | undefined when + FeatureName :: feature_name(), + ExperimentLevel :: experiment_level() | none; +(FeatureProps) -> ExperimentLevel when + FeatureProps :: + feature_props_extended() | + rabbit_deprecated_features:feature_props_extended(), + ExperimentLevel :: experiment_level() | none. +%% @doc +%% Returns the experimental level of an experimental feature flag. +%% +%% The possible experiment levels are: +%%
    +%%
  • `unsupported': the experimental feature flag must not be enabled in +%% production and upgrades with it enabled is unsupported.
  • +%%
  • `supported': the experimental feature flag is not yet stable enough but +%% upgrades are guarantied to be possible. This is returned too if the +%% feature flag is stable or required.
  • +%%
+%% +%% @param FeatureName The name of the feature flag to check. +%% @param FeatureProps A feature flag properties map. +%% @returns `unsupported', `supported', or `undefined' if the given feature +%% flag name doesn't correspond to a known feature flag. + +get_experiment_level(FeatureName) when is_atom(FeatureName) -> + case rabbit_ff_registry_wrapper:get(FeatureName) of + undefined -> undefined; + FeatureProps -> get_experiment_level(FeatureProps) + end; +get_experiment_level(FeatureProps) when ?IS_FEATURE_FLAG(FeatureProps) -> + case get_stability(FeatureProps) of + experimental -> maps:get(experiment_level, FeatureProps, unsupported); + _ -> supported + end; +get_experiment_level(FeatureProps) when ?IS_DEPRECATION(FeatureProps) -> + supported. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% ------------------------------------------------------------------- %% Feature flags registry. %% ------------------------------------------------------------------- @@ -911,6 +1088,11 @@ assert_feature_flag_is_valid(FeatureName, FeatureProps) -> ValidProps = [desc, doc_url, stability, +<<<<<<< HEAD +======= + require_level, + experiment_level, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) depends_on, callbacks], ?assertEqual([], maps:keys(FeatureProps) -- ValidProps), @@ -922,6 +1104,20 @@ assert_feature_flag_is_valid(FeatureName, FeatureProps) -> ?assert(Stability =:= stable orelse Stability =:= experimental orelse Stability =:= required), +<<<<<<< HEAD +======= + ?assert(Stability =:= experimental orelse + not maps:is_key(experiment_level, FeatureProps)), + ?assert(Stability =:= required orelse + not maps:is_key(require_level, FeatureProps)), + RequireLevel = maps:get(require_level, FeatureProps, soft), + ?assert(RequireLevel =:= hard orelse RequireLevel =:= soft), + ExperimentLevel = maps:get( + experiment_level, FeatureProps, + unsupported), + ?assert(ExperimentLevel =:= unsupported orelse + ExperimentLevel =:= supported), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?assertNot(maps:is_key(migration_fun, FeatureProps)), ?assertNot(maps:is_key(warning, FeatureProps)), case FeatureProps of @@ -1302,7 +1498,13 @@ does_node_support(Node, FeatureNames, Timeout) -> false end. +<<<<<<< HEAD -spec check_node_compatibility(node()) -> ok | {error, any()}. +======= +-spec check_node_compatibility(RemoteNode) -> Ret when + RemoteNode :: node(), + Ret :: ok | {error, any()}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% @doc %% Checks if a node is compatible with the local node. %% @@ -1314,11 +1516,48 @@ does_node_support(Node, FeatureNames, Timeout) -> %% local node %% %% +<<<<<<< HEAD %% @param Node the name of the remote node to test. %% @returns `ok' if they are compatible, `{error, Reason}' if they are not. check_node_compatibility(Node) -> rabbit_ff_controller:check_node_compatibility(Node). +======= +%% @param RemoteNode the name of the remote node to test. +%% @returns `ok' if they are compatible, `{error, Reason}' if they are not. + +check_node_compatibility(RemoteNode) -> + check_node_compatibility(RemoteNode, false). + +-spec check_node_compatibility(RemoteNode, LocalNodeAsVirgin) -> Ret when + RemoteNode :: node(), + LocalNodeAsVirgin :: boolean(), + Ret :: ok | {error, any()}. +%% @doc +%% Checks if a node is compatible with the local node. +%% +%% To be compatible, the following two conditions must be met: +%%
    +%%
  1. feature flags enabled on the local node must be supported by the +%% remote node
  2. +%%
  3. feature flags enabled on the remote node must be supported by the +%% local node
  4. +%%
+%% +%% Unlike {@link check_node_compatibility/1}, the local node's feature flags +%% inventory is evaluated as if the node was virgin if `LocalNodeAsVirgin' is +%% true. This is useful if the local node will be reset as part of joining a +%% remote cluster for instance. +%% +%% @param RemoteNode the name of the remote node to test. +%% @param LocalNodeAsVirgin flag to indicate if the local node should be +%% evaluated as if it was virgin. +%% @returns `ok' if they are compatible, `{error, Reason}' if they are not. + +check_node_compatibility(RemoteNode, LocalNodeAsVirgin) -> + rabbit_ff_controller:check_node_compatibility( + RemoteNode, LocalNodeAsVirgin). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) run_feature_flags_mod_on_remote_node(Node, Function, Args, Timeout) -> rabbit_ff_controller:rpc_call(Node, ?MODULE, Function, Args, Timeout). @@ -1330,7 +1569,11 @@ run_feature_flags_mod_on_remote_node(Node, Function, Args, Timeout) -> sync_feature_flags_with_cluster([] = _Nodes, true = _NodeIsVirgin) -> rabbit_ff_controller:enable_default(); sync_feature_flags_with_cluster([] = _Nodes, false = _NodeIsVirgin) -> +<<<<<<< HEAD ok; +======= + rabbit_ff_controller:enable_required(); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) sync_feature_flags_with_cluster(Nodes, _NodeIsVirgin) -> %% We don't use `rabbit_nodes:filter_running()' here because the given %% `Nodes' list may contain nodes which are not members yet (the cluster diff --git a/deps/rabbit/src/rabbit_ff_controller.erl b/deps/rabbit/src/rabbit_ff_controller.erl index f82ed6000e16..032915888c34 100644 --- a/deps/rabbit/src/rabbit_ff_controller.erl +++ b/deps/rabbit/src/rabbit_ff_controller.erl @@ -2,11 +2,21 @@ %% License, v. 2.0. If a copy of the MPL was not distributed with this %% file, You can obtain one at https://mozilla.org/MPL/2.0/. %% +<<<<<<< HEAD %% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. %% %% @author The RabbitMQ team %% @copyright 2007-2024 Broadcom. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +======= +%% Copyright (c) 2019-2024 Broadcom. All Rights Reserved. The term “Broadcom” +%% refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +%% @author The RabbitMQ team +%% @copyright 2019-2024 Broadcom. The term “Broadcom” refers to Broadcom Inc. +%% and/or its subsidiaries. All rights reserved. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% %% @doc %% The feature flag controller is responsible for synchronization and managing @@ -36,7 +46,12 @@ -export([is_supported/1, is_supported/2, enable/1, enable_default/0, +<<<<<<< HEAD check_node_compatibility/1, +======= + enable_required/0, + check_node_compatibility/2, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) sync_cluster/1, refresh_after_app_load/0, get_forced_feature_flag_names/0]). @@ -134,12 +149,49 @@ enable_default() -> Ret end. +<<<<<<< HEAD check_node_compatibility(RemoteNode) -> ThisNode = node(), ?LOG_DEBUG( "Feature flags: CHECKING COMPATIBILITY between nodes `~ts` and `~ts`", [ThisNode, RemoteNode], #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}), +======= +enable_required() -> + ?LOG_DEBUG( + "Feature flags: enable required feature flags", + #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}), + case erlang:whereis(?LOCAL_NAME) of + Pid when is_pid(Pid) -> + %% The function is called while `rabbit' is running. + gen_statem:call(?LOCAL_NAME, enable_required); + undefined -> + %% The function is called while `rabbit' is stopped. We need to + %% start a one-off controller, again to make sure concurrent + %% changes are blocked. + {ok, Pid} = start_link(), + Ret = gen_statem:call(Pid, enable_required), + gen_statem:stop(Pid), + Ret + end. + +check_node_compatibility(RemoteNode, LocalNodeAsVirgin) -> + ThisNode = node(), + case LocalNodeAsVirgin of + true -> + ?LOG_DEBUG( + "Feature flags: CHECKING COMPATIBILITY between nodes `~ts` " + "and `~ts`; consider node `~ts` as virgin", + [ThisNode, RemoteNode, ThisNode], + #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}); + false -> + ?LOG_DEBUG( + "Feature flags: CHECKING COMPATIBILITY between nodes `~ts` " + "and `~ts`", + [ThisNode, RemoteNode], + #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}) + end, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% We don't go through the controller process to check nodes compatibility %% because this function is used while `rabbit' is stopped usually. %% @@ -147,7 +199,11 @@ check_node_compatibility(RemoteNode) -> %% because it would not guaranty that the compatibility remains true after %% this function finishes and before the node starts and synchronizes %% feature flags. +<<<<<<< HEAD check_node_compatibility_task(ThisNode, RemoteNode). +======= + check_node_compatibility_task(ThisNode, RemoteNode, LocalNodeAsVirgin). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) sync_cluster(Nodes) -> ?LOG_DEBUG( @@ -194,7 +250,11 @@ standing_by( when EventContent =/= notify_when_done -> ?LOG_DEBUG( "Feature flags: registering controller globally before " +<<<<<<< HEAD "proceeding with task: ~tp", +======= + "proceeding with task: ~0tp", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) [EventContent], #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}), @@ -292,6 +352,11 @@ proceed_with_task({enable, FeatureNames}) -> enable_task(FeatureNames); proceed_with_task(enable_default) -> enable_default_task(); +<<<<<<< HEAD +======= +proceed_with_task(enable_required) -> + enable_required_task(); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) proceed_with_task({sync_cluster, Nodes}) -> sync_cluster_task(Nodes); proceed_with_task(refresh_after_app_load) -> @@ -382,12 +447,23 @@ notify_waiting_controller({ControlerPid, _} = From) -> %% Code to check compatibility between nodes. %% -------------------------------------------------------------------- +<<<<<<< HEAD -spec check_node_compatibility_task(Node, Node) -> Ret when Node :: node(), Ret :: ok | {error, Reason}, Reason :: incompatible_feature_flags. check_node_compatibility_task(NodeA, NodeB) -> +======= +-spec check_node_compatibility_task(NodeA, NodeB, NodeAAsVirigin) -> Ret when + NodeA :: node(), + NodeB :: node(), + NodeAAsVirigin :: boolean(), + Ret :: ok | {error, Reason}, + Reason :: incompatible_feature_flags. + +check_node_compatibility_task(NodeA, NodeB, NodeAAsVirigin) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?LOG_NOTICE( "Feature flags: checking nodes `~ts` and `~ts` compatibility...", [NodeA, NodeB], @@ -400,7 +476,12 @@ check_node_compatibility_task(NodeA, NodeB) -> _ when is_list(NodesB) -> check_node_compatibility_task1( NodeA, NodesA, +<<<<<<< HEAD NodeB, NodesB); +======= + NodeB, NodesB, + NodeAAsVirigin); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Error -> ?LOG_WARNING( "Feature flags: " @@ -419,6 +500,7 @@ check_node_compatibility_task(NodeA, NodeB) -> {error, {aborted_feature_flags_compat_check, Error}} end. +<<<<<<< HEAD check_node_compatibility_task1(NodeA, NodesA, NodeB, NodesB) when is_list(NodesA) andalso is_list(NodesB) -> case collect_inventory_on_nodes(NodesA) of @@ -434,6 +516,18 @@ check_node_compatibility_task1(NodeA, NodesA, NodeB, NodesB) "`~ts`:~n~tp", [NodeB, InventoryB], #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}), +======= +check_node_compatibility_task1(NodeA, NodesA, NodeB, NodesB, NodeAAsVirigin) + when is_list(NodesA) andalso is_list(NodesB) -> + case collect_inventory_on_nodes(NodesA) of + {ok, InventoryA0} -> + InventoryA = virtually_reset_inventory( + InventoryA0, NodeAAsVirigin), + log_inventory(NodeA, InventoryA), + case collect_inventory_on_nodes(NodesB) of + {ok, InventoryB} -> + log_inventory(NodeB, InventoryB), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case are_compatible(InventoryA, InventoryB) of true -> ?LOG_NOTICE( @@ -468,6 +562,62 @@ check_node_compatibility_task1(NodeA, NodesA, NodeB, NodesB) {error, {aborted_feature_flags_compat_check, Error}} end. +<<<<<<< HEAD +======= +log_inventory( + FromNode, + #{feature_flags := FeatureFlags, states_per_node := StatesPerNode}) -> + ?LOG_DEBUG( + begin + AllFeatureNames = lists:sort(maps:keys(FeatureFlags)), + Nodes = lists:sort(maps:keys(StatesPerNode)), + LongestFeatureName = lists:foldl( + fun(FeatureName, MaxLength) -> + Length = length( + atom_to_list( + FeatureName)), + if + Length > MaxLength -> Length; + true -> MaxLength + end + end, 0, AllFeatureNames), + NodeInitialPrefix = lists:duplicate(LongestFeatureName + 1, $\s), + {Header, + HeaderTail} = lists:foldl( + fun(Node, {String, Prefix}) -> + String1 = io_lib:format( + "~ts~ts ,-- ~ts~n", + [String, Prefix, Node]), + NextPrefix = Prefix ++ " |", + {String1, NextPrefix} + end, {"", NodeInitialPrefix}, Nodes), + lists:flatten( + io_lib:format( + "Feature flags: inventory queried from node `~ts`:~n", + [FromNode]) ++ + Header ++ + HeaderTail ++ + [io_lib:format("~n~*ts:", [LongestFeatureName, FeatureName]) ++ + [io_lib:format( + " ~s", + [begin + State = maps:get( + FeatureName, + maps:get(Node, StatesPerNode), + false), + case State of + true -> "x"; + state_changing -> "~"; + false -> " " + end + end]) + || Node <- Nodes] + || FeatureName <- AllFeatureNames] ++ + []) + end, + #{domain_ => ?RMQLOG_DOMAIN_FEAT_FLAGS}). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec list_nodes_clustered_with(Node) -> Ret when Node :: node(), Ret :: Members | Error, @@ -488,6 +638,45 @@ list_nodes_clustered_with(Node) -> ListOrError -> ListOrError end. +<<<<<<< HEAD +======= +virtually_reset_inventory( + #{feature_flags := FeatureFlags, + states_per_node := StatesPerNode} = Inventory, + true = _NodeAsVirgin) -> + [Node | _] = maps:keys(StatesPerNode), + FeatureStates0 = maps:get(Node, StatesPerNode), + FeatureStates1 = maps:map( + fun(FeatureName, _FeatureState) -> + FeatureProps = maps:get( + FeatureName, FeatureFlags), + state_after_virtual_state( + FeatureName, FeatureProps) + end, FeatureStates0), + StatesPerNode1 = maps:map( + fun(_Node, _FeatureStates) -> + FeatureStates1 + end, StatesPerNode), + Inventory1 = Inventory#{states_per_node => StatesPerNode1}, + Inventory1; +virtually_reset_inventory( + Inventory, + false = _NodeAsVirgin) -> + Inventory. + +state_after_virtual_state(_FeatureName, FeatureProps) + when ?IS_FEATURE_FLAG(FeatureProps) -> + Stability = rabbit_feature_flags:get_stability(FeatureProps), + case Stability of + required -> true; + _ -> false + end; +state_after_virtual_state(FeatureName, FeatureProps) + when ?IS_DEPRECATION(FeatureProps) -> + not rabbit_deprecated_features:should_be_permitted( + FeatureName, FeatureProps). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec are_compatible(Inventory, Inventory) -> AreCompatible when Inventory :: rabbit_feature_flags:cluster_inventory(), AreCompatible :: boolean(). @@ -576,14 +765,20 @@ enable_task(FeatureNames) -> end. enable_default_task() -> +<<<<<<< HEAD FeatureNames = get_forced_feature_flag_names(), case FeatureNames of undefined -> +======= + case get_forced_feature_flag_names() of + {ok, undefined} -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?LOG_DEBUG( "Feature flags: starting an unclustered node for the first " "time: all stable feature flags will be enabled by default", #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}), {ok, Inventory} = collect_inventory_on_nodes([node()]), +<<<<<<< HEAD #{feature_flags := FeatureFlags} = Inventory, StableFeatureNames = maps:fold( @@ -597,17 +792,27 @@ enable_default_task() -> end, [], FeatureFlags), enable_many(Inventory, StableFeatureNames); [] -> +======= + StableFeatureNames = get_stable_feature_flags(Inventory), + enable_many(Inventory, StableFeatureNames); + {ok, []} -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?LOG_DEBUG( "Feature flags: starting an unclustered node for the first " "time: all feature flags are forcibly left disabled from " "the $RABBITMQ_FEATURE_FLAGS environment variable", #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}), ok; +<<<<<<< HEAD _ -> +======= + {ok, FeatureNames} when is_list(FeatureNames) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?LOG_DEBUG( "Feature flags: starting an unclustered node for the first " "time: only the following feature flags specified in the " "$RABBITMQ_FEATURE_FLAGS environment variable will be enabled: " +<<<<<<< HEAD "~tp", [FeatureNames], #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}), @@ -618,6 +823,64 @@ enable_default_task() -> -spec get_forced_feature_flag_names() -> Ret when Ret :: FeatureNames | undefined, FeatureNames :: [rabbit_feature_flags:feature_name()]. +======= + "~0tp", + [FeatureNames], + #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}), + {ok, Inventory} = collect_inventory_on_nodes([node()]), + enable_many(Inventory, FeatureNames); + {ok, {rel, Plus, Minus}} -> + ?LOG_DEBUG( + "Feature flags: starting an unclustered node for the first " + "time: all stable feature flags will be enabled, after " + "applying changes from $RABBITMQ_FEATURE_FLAGS: adding ~0tp, " + "skipping ~0tp", + [Plus, Minus], + #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}), + {ok, Inventory} = collect_inventory_on_nodes([node()]), + StableFeatureNames = get_stable_feature_flags(Inventory), + Unsupported = lists:filter( + fun(FeatureName) -> + not is_known_and_supported( + Inventory, FeatureName) + end, Minus), + case Unsupported of + [] -> + FeatureNames = (StableFeatureNames -- Minus) ++ Plus, + enable_many(Inventory, FeatureNames); + _ -> + ?LOG_ERROR( + "Feature flags: unsupported feature flags to skip in " + "$RABBITMQ_FEATURE_FLAGS: ~0tp", + [Unsupported], + #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}), + {error, unsupported} + end; + {error, syntax_error_in_envvar} = Error -> + ?LOG_DEBUG( + "Feature flags: invalid mix of `feature_flag` and " + "`+/-feature_flag` in $RABBITMQ_FEATURE_FLAGS", + #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}), + Error + end. + +get_stable_feature_flags(#{feature_flags := FeatureFlags}) -> + maps:fold( + fun(FeatureName, FeatureProps, Acc) -> + Stability = rabbit_feature_flags:get_stability(FeatureProps), + case Stability of + stable -> [FeatureName | Acc]; + _ -> Acc + end + end, [], FeatureFlags). + +-spec get_forced_feature_flag_names() -> Ret when + Ret :: {ok, Abs | Rel | undefined} | {error, syntax_error_in_envvar}, + Abs :: [rabbit_feature_flags:feature_name()], + Rel :: {rel, + [rabbit_feature_flags:feature_name()], + [rabbit_feature_flags:feature_name()]}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% @doc Returns the (possibly empty) list of feature flags the user wants to %% enable out-of-the-box when starting a node for the first time. %% @@ -638,6 +901,7 @@ enable_default_task() -> %% @private get_forced_feature_flag_names() -> +<<<<<<< HEAD Ret = case get_forced_feature_flag_names_from_env() of undefined -> get_forced_feature_flag_names_from_config(); List -> List @@ -676,6 +940,70 @@ get_forced_feature_flag_names_from_env() -> -spec get_forced_feature_flag_names_from_config() -> Ret when Ret :: FeatureNames | undefined, FeatureNames :: [rabbit_feature_flags:feature_name()]. +======= + case get_forced_feature_flag_names_from_env() of + {ok, undefined} -> get_forced_feature_flag_names_from_config(); + {ok, _} = Ret -> Ret; + {error, _} = Error -> Error + end. + +-spec get_forced_feature_flag_names_from_env() -> Ret when + Ret :: {ok, Abs | Rel | undefined} | {error, syntax_error_in_envvar}, + Abs :: [rabbit_feature_flags:feature_name()], + Rel :: {rel, + [rabbit_feature_flags:feature_name()], + [rabbit_feature_flags:feature_name()]}. +%% @private + +get_forced_feature_flag_names_from_env() -> + Value = case rabbit_prelaunch:get_context() of + #{forced_feature_flags_on_init := ForcedFFs} -> ForcedFFs; + _ -> undefined + end, + case Value of + undefined -> + {ok, Value}; + [] -> + {ok, Value}; + [[Op | _] | _] when Op =:= $+ orelse Op =:= $- -> + lists:foldr( + fun + ([$+ | NameS], {ok, {rel, Plus, Minus}}) -> + Name = list_to_atom(NameS), + Plus1 = [Name | Plus], + {ok, {rel, Plus1, Minus}}; + ([$- | NameS], {ok, {rel, Plus, Minus}}) -> + Name = list_to_atom(NameS), + Minus1 = [Name | Minus], + {ok, {rel, Plus, Minus1}}; + (_, {error, _} = Error) -> + Error; + (_, _) -> + {error, syntax_error_in_envvar} + end, {ok, {rel, [], []}}, Value); + _ when is_list(Value) -> + lists:foldr( + fun + (Name, {ok, Abs}) when is_atom(Name) -> + {ok, [Name | Abs]}; + ([C | _] = NameS, {ok, Abs}) + when C =/= $+ andalso C =/= $- -> + Name = list_to_atom(NameS), + {ok, [Name | Abs]}; + (_, {error, _} = Error) -> + Error; + (_, _) -> + {error, syntax_error_in_envvar} + end, {ok, []}, Value) + end. + +-spec get_forced_feature_flag_names_from_config() -> Ret when + Ret :: {ok, Abs | Rel | undefined}, + Abs :: [rabbit_feature_flags:feature_name()], + Rel :: {rel, + [rabbit_feature_flags:feature_name()], + [rabbit_feature_flags:feature_name()]}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% @private get_forced_feature_flag_names_from_config() -> @@ -683,6 +1011,7 @@ get_forced_feature_flag_names_from_config() -> rabbit, forced_feature_flags_on_init, undefined), case Value of undefined -> +<<<<<<< HEAD Value; _ when is_list(Value) -> case lists:all(fun is_atom/1, Value) of @@ -693,6 +1022,33 @@ get_forced_feature_flag_names_from_config() -> undefined end. +======= + {ok, Value}; + _ when is_list(Value) -> + {ok, Value}; + {rel, Plus, Minus} when is_list(Plus) andalso is_list(Minus) -> + {ok, Value} + end. + +-spec enable_required_task() -> Ret when + Ret :: ok | {error, Reason}, + Reason :: term(). + +enable_required_task() -> + {ok, Inventory} = collect_inventory_on_nodes([node()]), + RequiredFeatureNames = list_soft_required_feature_flags(Inventory), + case RequiredFeatureNames of + [] -> + ok; + _ -> + ?LOG_DEBUG( + "Feature flags: enabling required feature flags: ~0p", + [RequiredFeatureNames], + #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}) + end, + enable_many(Inventory, RequiredFeatureNames). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec sync_cluster_task() -> Ret when Ret :: ok | {error, Reason}, Reason :: term(). @@ -707,6 +1063,7 @@ sync_cluster_task() -> Reason :: term(). sync_cluster_task(Nodes) -> +<<<<<<< HEAD %% We assume that a feature flag can only be enabled, not disabled. %% Therefore this synchronization searches for feature flags enabled on %% some nodes but not all, and make sure they are enabled everywhere. @@ -724,6 +1081,8 @@ sync_cluster_task(Nodes) -> [Nodes], #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}), +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case collect_inventory_on_nodes(Nodes) of {ok, Inventory} -> CantEnable = list_deprecated_features_that_cant_be_denied( @@ -732,7 +1091,31 @@ sync_cluster_task(Nodes) -> [] -> FeatureNames = list_feature_flags_enabled_somewhere( Inventory, false), +<<<<<<< HEAD enable_many(Inventory, FeatureNames); +======= + + %% In addition to feature flags enabled somewhere, we also + %% ensure required feature flags are enabled accross the + %% board. + RequiredFeatureNames = list_soft_required_feature_flags( + Inventory), + case RequiredFeatureNames of + [] -> + ok; + _ -> + ?LOG_DEBUG( + "Feature flags: enabling required feature " + "flags as part of cluster sync: ~0p", + [RequiredFeatureNames], + #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}) + end, + + FeatureNamesToEnable = lists:usort( + FeatureNames ++ + RequiredFeatureNames), + enable_many(Inventory, FeatureNamesToEnable); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) _ -> ?LOG_ERROR( "Feature flags: the following deprecated features " @@ -772,12 +1155,40 @@ refresh_after_app_load_task() -> Ret :: ok | {error, Reason}, Reason :: term(). +<<<<<<< HEAD enable_many(#{states_per_node := _} = Inventory, [FeatureName | Rest]) -> case enable_if_supported(Inventory, FeatureName) of {ok, Inventory1} -> enable_many(Inventory1, Rest); Error -> Error end; enable_many(_Inventory, []) -> +======= +enable_many(#{states_per_node := _} = Inventory, FeatureNames) -> + %% We acquire a lock before making any change to the registry. This is not + %% used by the controller (because it is already using a globally + %% registered name to prevent concurrent runs). But this is used in + %% `rabbit_feature_flags:is_enabled()' to block while the state is + %% `state_changing'. + rabbit_ff_registry_factory:acquire_state_change_lock(), + Ret = enable_many_locked(Inventory, FeatureNames), + rabbit_ff_registry_factory:release_state_change_lock(), + Ret. + +-spec enable_many_locked(Inventory, FeatureNames) -> Ret when + Inventory :: rabbit_feature_flags:cluster_inventory(), + FeatureNames :: [rabbit_feature_flags:feature_name()], + Ret :: ok | {error, Reason}, + Reason :: term(). + +enable_many_locked( + #{states_per_node := _} = Inventory, [FeatureName | Rest]) -> + case enable_if_supported(Inventory, FeatureName) of + {ok, Inventory1} -> enable_many_locked(Inventory1, Rest); + Error -> Error + end; +enable_many_locked( + _Inventory, []) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ok. -spec enable_if_supported(Inventory, FeatureName) -> Ret when @@ -794,15 +1205,22 @@ enable_if_supported(#{states_per_node := _} = Inventory, FeatureName) -> "Feature flags: `~ts`: supported; continuing", [FeatureName], #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}), +<<<<<<< HEAD lock_registry_and_enable(Inventory, FeatureName); false -> ?LOG_DEBUG( +======= + enable_with_registry_locked(Inventory, FeatureName); + false -> + ?LOG_ERROR( +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "Feature flags: `~ts`: unsupported; aborting", [FeatureName], #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}), {error, unsupported} end. +<<<<<<< HEAD -spec lock_registry_and_enable(Inventory, FeatureName) -> Ret when Inventory :: rabbit_feature_flags:cluster_inventory(), FeatureName :: rabbit_feature_flags:feature_name(), @@ -820,6 +1238,8 @@ lock_registry_and_enable(#{states_per_node := _} = Inventory, FeatureName) -> rabbit_ff_registry_factory:release_state_change_lock(), Ret. +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec enable_with_registry_locked(Inventory, FeatureName) -> Ret when Inventory :: rabbit_feature_flags:cluster_inventory(), FeatureName :: rabbit_feature_flags:feature_name(), @@ -876,13 +1296,22 @@ check_required_and_enable( FeatureName) -> %% Required feature flags vs. virgin nodes. FeatureProps = maps:get(FeatureName, FeatureFlags), +<<<<<<< HEAD Stability = rabbit_feature_flags:get_stability(FeatureProps), +======= + RequireLevel = rabbit_feature_flags:get_require_level(FeatureProps), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ProvidedBy = maps:get(provided_by, FeatureProps), NodesWhereDisabled = list_nodes_where_feature_flag_is_disabled( Inventory, FeatureName), +<<<<<<< HEAD MarkDirectly = case Stability of required when ProvidedBy =:= rabbit -> +======= + MarkDirectly = case RequireLevel of + hard when ProvidedBy =:= rabbit -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?LOG_DEBUG( "Feature flags: `~s`: the feature flag is " "required on some nodes; list virgin nodes " @@ -901,7 +1330,11 @@ check_required_and_enable( end end, NodesWhereDisabled), VirginNodesWhereDisabled =:= NodesWhereDisabled; +<<<<<<< HEAD required when ProvidedBy =/= rabbit -> +======= + hard when ProvidedBy =/= rabbit -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% A plugin can be enabled/disabled at runtime and %% between restarts. Thus we have no way to %% distinguish a newly enabled plugin from a plugin @@ -926,8 +1359,13 @@ check_required_and_enable( case MarkDirectly of false -> +<<<<<<< HEAD case Stability of required -> +======= + case RequireLevel of + hard -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?LOG_DEBUG( "Feature flags: `~s`: some nodes where the feature " "flag is disabled are not virgin, we need to perform " @@ -1295,6 +1733,29 @@ list_feature_flags_enabled_somewhere( end, #{}, StatesPerNode), lists:sort(maps:keys(MergedStates)). +<<<<<<< HEAD +======= +list_soft_required_feature_flags( + #{feature_flags := FeatureFlags, states_per_node := StatesPerNode}) -> + FeatureStates = maps:get(node(), StatesPerNode), + RequiredFeatureNames = maps:fold( + fun(FeatureName, FeatureProps, Acc) -> + RequireLevel = ( + rabbit_feature_flags:get_require_level( + FeatureProps)), + IsEnabled = maps:get( + FeatureName, FeatureStates, + false), + case RequireLevel of + soft when IsEnabled =:= false -> + [FeatureName | Acc]; + _ -> + Acc + end + end, [], FeatureFlags), + lists:sort(RequiredFeatureNames). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec list_deprecated_features_that_cant_be_denied(Inventory) -> Ret when Inventory :: rabbit_feature_flags:cluster_inventory(), @@ -1367,7 +1828,11 @@ list_nodes_where_feature_flag_is_disabled( %% disabled. not Enabled; _ -> +<<<<<<< HEAD %% The feature flags is unknown on this +======= + %% The feature flag is unknown on this +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% node, don't run the migration function. false end diff --git a/deps/rabbit/src/rabbit_ff_extra.erl b/deps/rabbit/src/rabbit_ff_extra.erl index 9eba72185936..4f88716e08d9 100644 --- a/deps/rabbit/src/rabbit_ff_extra.erl +++ b/deps/rabbit/src/rabbit_ff_extra.erl @@ -2,7 +2,12 @@ %% License, v. 2.0. If a copy of the MPL was not distributed with this %% file, You can obtain one at https://mozilla.org/MPL/2.0/. %% +<<<<<<< HEAD %% @copyright 2007-2024 Broadcom. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +======= +%% @copyright 2019-2024 Broadcom. The term “Broadcom” refers to Broadcom Inc. +%% and/or its subsidiaries. All rights reserved. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% %% @doc %% This module provides extra functions unused by the feature flags @@ -23,6 +28,15 @@ -type cli_info_entry() :: [{name, rabbit_feature_flags:feature_name()} | {state, enabled | disabled | unavailable} | {stability, rabbit_feature_flags:stability()} | +<<<<<<< HEAD +======= + {require_level, + rabbit_feature_flags:require_level()} | + {experiment_level, + rabbit_feature_flags:experiment_level()} | + {callbacks, + [rabbit_feature_flags:callback_name()]} | +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {provided_by, atom()} | {desc, string()} | {doc_url, string()}]. @@ -60,6 +74,14 @@ cli_info(FeatureFlags) -> FeatureProps = maps:get(FeatureName, FeatureFlags), State = rabbit_feature_flags:get_state(FeatureName), Stability = rabbit_feature_flags:get_stability(FeatureProps), +<<<<<<< HEAD +======= + RequireLevel = rabbit_feature_flags:get_require_level( + FeatureProps), + ExperimentLevel = rabbit_feature_flags:get_experiment_level( + FeatureProps), + Callbacks = maps:keys(maps:get(callbacks, FeatureProps, #{})), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) App = maps:get(provided_by, FeatureProps), Desc = maps:get(desc, FeatureProps, ""), DocUrl = maps:get(doc_url, FeatureProps, ""), @@ -68,6 +90,12 @@ cli_info(FeatureFlags) -> {doc_url, unicode:characters_to_binary(DocUrl)}, {state, State}, {stability, Stability}, +<<<<<<< HEAD +======= + {require_level, RequireLevel}, + {experiment_level, ExperimentLevel}, + {callbacks, Callbacks}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {provided_by, App}], [FFInfo | Acc] end, [], lists:sort(maps:keys(FeatureFlags))). @@ -159,6 +187,11 @@ info(FeatureFlags, Options) -> {State, Color} = case State0 of enabled -> {"Enabled", Green}; +<<<<<<< HEAD +======= + state_changing -> + {"(Changing)", Yellow}; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) disabled -> {"Disabled", Yellow}; unavailable -> diff --git a/deps/rabbit/src/rabbit_ff_registry.erl b/deps/rabbit/src/rabbit_ff_registry.erl index 864ff564dc64..291fbdc1ac9b 100644 --- a/deps/rabbit/src/rabbit_ff_registry.erl +++ b/deps/rabbit/src/rabbit_ff_registry.erl @@ -2,11 +2,21 @@ %% License, v. 2.0. If a copy of the MPL was not distributed with this %% file, You can obtain one at https://mozilla.org/MPL/2.0/. %% +<<<<<<< HEAD %% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. %% %% @author The RabbitMQ team %% @copyright 2007-2024 Broadcom. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +======= +%% Copyright (c) 2019-2024 Broadcom. All Rights Reserved. The term “Broadcom” +%% refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +%% @author The RabbitMQ team +%% @copyright 2019-2024 Broadcom. The term “Broadcom” refers to Broadcom Inc. +%% and/or its subsidiaries. All rights reserved. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% %% @doc %% This module exposes the API of the {@link rabbit_feature_flags} diff --git a/deps/rabbit/src/rabbit_ff_registry_factory.erl b/deps/rabbit/src/rabbit_ff_registry_factory.erl index 0d91a7b64955..8abcfcb3ba91 100644 --- a/deps/rabbit/src/rabbit_ff_registry_factory.erl +++ b/deps/rabbit/src/rabbit_ff_registry_factory.erl @@ -2,7 +2,12 @@ %% License, v. 2.0. If a copy of the MPL was not distributed with this %% file, You can obtain one at https://mozilla.org/MPL/2.0/. %% +<<<<<<< HEAD %% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +======= +%% Copyright (c) 2019-2024 Broadcom. All Rights Reserved. The term “Broadcom” +%% refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% -module(rabbit_ff_registry_factory). @@ -260,26 +265,47 @@ maybe_initialize_registry(NewSupportedFeatureFlags, maps:map( fun (FeatureName, FeatureProps) when ?IS_FEATURE_FLAG(FeatureProps) -> +<<<<<<< HEAD Stability = rabbit_feature_flags:get_stability(FeatureProps), +======= + RequireLevel = ( + rabbit_feature_flags:get_require_level(FeatureProps)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ProvidedBy = maps:get(provided_by, FeatureProps), State = case FeatureStates0 of #{FeatureName := FeatureState} -> FeatureState; _ -> false end, +<<<<<<< HEAD case Stability of required when State =:= true -> %% The required feature flag is already enabled, we keep %% it this way. State; required when NewNode -> +======= + case RequireLevel of + hard when State =:= true -> + %% The required feature flag is already enabled, we keep + %% it this way. + State; + hard when NewNode -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% This is the very first time the node starts, we %% already mark the required feature flag as enabled. ?assertNotEqual(state_changing, State), true; +<<<<<<< HEAD required when ProvidedBy =/= rabbit -> ?assertNotEqual(state_changing, State), true; required -> +======= + hard when ProvidedBy =/= rabbit -> + ?assertNotEqual(state_changing, State), + true; + hard -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% This is not a new node and the required feature flag %% is disabled. This is an error and RabbitMQ must be %% downgraded to enable the feature flag. @@ -442,6 +468,7 @@ do_initialize_registry(#{feature_flags := AllFeatureFlags, written_to_disk := WrittenToDisk} = Inventory) -> %% We log the state of those feature flags. ?LOG_DEBUG( +<<<<<<< HEAD lists:flatten( "Feature flags: list of feature flags found:\n" ++ [io_lib:format( @@ -473,6 +500,68 @@ do_initialize_registry(#{feature_flags := AllFeatureFlags, true -> "yes"; false -> "no" end])]), +======= + begin + AllFeatureNames = lists:sort(maps:keys(AllFeatureFlags)), + {FeatureNames, + DeprFeatureNames} = lists:partition( + fun(FeatureName) -> + FeatureProps = maps:get( + FeatureName, + AllFeatureFlags), + ?IS_FEATURE_FLAG(FeatureProps) + end, AllFeatureNames), + + IsRequired = fun(FeatureName) -> + FeatureProps = maps:get( + FeatureName, + AllFeatureFlags), + required =:= + rabbit_feature_flags:get_stability( + FeatureProps) + end, + {ReqFeatureNames, + NonReqFeatureNames} = lists:partition(IsRequired, FeatureNames), + {ReqDeprFeatureNames, + NonReqDeprFeatureNames} = lists:partition( + IsRequired, DeprFeatureNames), + + lists:flatten( + "Feature flags: list of feature flags found:\n" ++ + [io_lib:format( + "Feature flags: [~ts] ~ts~n", + [case maps:get(FeatureName, FeatureStates, false) of + true -> "x"; + state_changing -> "~"; + false -> " " + end, + FeatureName]) + || FeatureName <- NonReqFeatureNames] ++ + "Feature flags: list of deprecated features found:\n" ++ + [io_lib:format( + "Feature flags: [~ts] ~ts~n", + [case maps:get(FeatureName, FeatureStates, false) of + true -> "x"; + state_changing -> "~"; + false -> " " + end, + FeatureName]) + || FeatureName <- NonReqDeprFeatureNames] ++ + [io_lib:format( + "Feature flags: required feature flags not listed above: ~b~n" + "Feature flags: removed deprecated features not listed " + "above: ~b~n" + "Feature flags: scanned applications: ~0tp~n" + "Feature flags: feature flag states written to disk: ~ts", + [length(ReqFeatureNames), + length(ReqDeprFeatureNames), + ScannedApps, + case WrittenToDisk of + true -> "yes"; + false -> "no" + end])]) + end, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS} ), diff --git a/deps/rabbit/src/rabbit_ff_registry_wrapper.erl b/deps/rabbit/src/rabbit_ff_registry_wrapper.erl index beef32f657cf..af622db3fbfd 100644 --- a/deps/rabbit/src/rabbit_ff_registry_wrapper.erl +++ b/deps/rabbit/src/rabbit_ff_registry_wrapper.erl @@ -2,11 +2,21 @@ %% License, v. 2.0. If a copy of the MPL was not distributed with this %% file, You can obtain one at https://mozilla.org/MPL/2.0/. %% +<<<<<<< HEAD %% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. %% %% @author The RabbitMQ team %% @copyright 2007-2024 Broadcom. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +======= +%% Copyright (c) 2019-2024 Broadcom. All Rights Reserved. The term “Broadcom” +%% refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +%% @author The RabbitMQ team +%% @copyright 2019-2024 Broadcom. The term “Broadcom” refers to Broadcom Inc. +%% and/or its subsidiaries. All rights reserved. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% %% @doc %% This module sits in front of {@link rabbit_ff_registry}. diff --git a/deps/rabbit/src/rabbit_global_counters.erl b/deps/rabbit/src/rabbit_global_counters.erl index b5cdc5b627e1..fbd21b3e3c8a 100644 --- a/deps/rabbit/src/rabbit_global_counters.erl +++ b/deps/rabbit/src/rabbit_global_counters.erl @@ -132,12 +132,23 @@ boot_step() -> [begin %% Protocol counters +<<<<<<< HEAD init([{protocol, Proto}]), %% Protocol & Queue Type counters init([{protocol, Proto}, {queue_type, rabbit_classic_queue}]), init([{protocol, Proto}, {queue_type, rabbit_quorum_queue}]), init([{protocol, Proto}, {queue_type, rabbit_stream_queue}]) +======= + Protocol = {protocol, Proto}, + init([Protocol]), + rabbit_msg_size_metrics:init(Proto), + + %% Protocol & Queue Type counters + init([Protocol, {queue_type, rabbit_classic_queue}]), + init([Protocol, {queue_type, rabbit_quorum_queue}]), + init([Protocol, {queue_type, rabbit_stream_queue}]) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end || Proto <- [amqp091, amqp10]], %% Dead Letter counters @@ -247,13 +258,21 @@ publisher_created(Protocol) -> counters:add(fetch(Protocol), ?PUBLISHERS, 1). publisher_deleted(Protocol) -> +<<<<<<< HEAD counters:add(fetch(Protocol), ?PUBLISHERS, -1). +======= + counters:sub(fetch(Protocol), ?PUBLISHERS, 1). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) consumer_created(Protocol) -> counters:add(fetch(Protocol), ?CONSUMERS, 1). consumer_deleted(Protocol) -> +<<<<<<< HEAD counters:add(fetch(Protocol), ?CONSUMERS, -1). +======= + counters:sub(fetch(Protocol), ?CONSUMERS, 1). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) messages_dead_lettered(Reason, QueueType, DeadLetterStrategy, Num) -> Index = case Reason of diff --git a/deps/rabbit/src/rabbit_khepri.erl b/deps/rabbit/src/rabbit_khepri.erl index 2c853d97495a..c9a3c16ba585 100644 --- a/deps/rabbit/src/rabbit_khepri.erl +++ b/deps/rabbit/src/rabbit_khepri.erl @@ -892,10 +892,14 @@ check_cluster_consistency(Node, CheckNodesConsistency) -> Error end; {_OTP, _Rabbit, {ok, Status}} -> +<<<<<<< HEAD case rabbit_db_cluster:check_compatibility(Node) of ok -> {ok, Status}; Error -> Error end +======= + {ok, Status} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end. remote_node_info(Node) -> diff --git a/deps/rabbit/src/rabbit_mnesia.erl b/deps/rabbit/src/rabbit_mnesia.erl index 0aa4ae5360b5..c49c704c9c8c 100644 --- a/deps/rabbit/src/rabbit_mnesia.erl +++ b/deps/rabbit/src/rabbit_mnesia.erl @@ -407,7 +407,28 @@ cluster_nodes(WhichNodes) -> cluster_status(WhichNodes). cluster_status_from_mnesia() -> case is_running() of false -> +<<<<<<< HEAD {error, mnesia_not_running}; +======= + case rabbit_khepri:get_feature_state() of + enabled -> + %% To keep this API compatible with older remote nodes who + %% don't know about Khepri, we take the cluster status + %% from `rabbit_khepri' and reformat the return value to + %% ressemble the node from this module. + %% + %% Both nodes won't be compatible, but let's leave that + %% decision to the Feature flags subsystem. + case rabbit_khepri:cluster_status_from_khepri() of + {ok, {All, Running}} -> + {ok, {All, All, Running}}; + {error, _} = Error -> + Error + end; + _ -> + {error, mnesia_not_running} + end; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) true -> %% If the tables are not present, it means that %% `init_db/3' hasn't been run yet. In other words, either @@ -475,8 +496,28 @@ members() -> end. node_info() -> +<<<<<<< HEAD {rabbit_misc:otp_release(), rabbit_misc:version(), mnesia:system_info(protocol_version), +======= + %% Once Khepri is enabled, the Mnesia protocol is irrelevant obviously. + %% + %% That said, older remote nodes who don't known about Khepri will request + %% this information anyway as part of calling `node_info/0'. Here, we + %% simply return `unsupported' as the Mnesia protocol. Older versions of + %% RabbitMQ will skip the protocol negotiation and use other ways. + %% + %% The goal is mostly to let older nodes which check Mnesia before feature + %% flags to reach the feature flags check. This one will correctly + %% indicate that they are incompatible. That's why we return `unsupported' + %% here, even if we could return the actual Mnesia protocol. + MnesiaProtocol = case rabbit_khepri:get_feature_state() of + enabled -> unsupported; + _ -> mnesia:system_info(protocol_version) + end, + {rabbit_misc:otp_release(), rabbit_misc:version(), + MnesiaProtocol, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) cluster_status_from_mnesia()}. -spec node_type() -> rabbit_db_cluster:node_type(). @@ -694,10 +735,14 @@ check_cluster_consistency(Node, CheckNodesConsistency) -> Error end; {_OTP, _Rabbit, _Protocol, {ok, Status}} -> +<<<<<<< HEAD case rabbit_db_cluster:check_compatibility(Node) of ok -> {ok, Status}; Error -> Error end +======= + {ok, Status} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end. remote_node_info(Node) -> diff --git a/deps/rabbit/src/rabbit_msg_size_metrics.erl b/deps/rabbit/src/rabbit_msg_size_metrics.erl new file mode 100644 index 000000000000..1faaa311a515 --- /dev/null +++ b/deps/rabbit/src/rabbit_msg_size_metrics.erl @@ -0,0 +1,143 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +%% This module tracks received message size distribution as histogram. +%% (A histogram is represented by a set of counters, one for each bucket.) +-module(rabbit_msg_size_metrics). + +-export([init/1, + observe/2, + prometheus_format/0]). + +%% Integration tests. +-export([raw_buckets/1, + diff_raw_buckets/2]). + +-ifdef(TEST). +-export([cleanup/1]). +-endif. + +-define(BUCKET_1, 100). +-define(BUCKET_2, 1_000). +-define(BUCKET_3, 10_000). +-define(BUCKET_4, 100_000). +-define(BUCKET_5, 1_000_000). +-define(BUCKET_6, 10_000_000). +%% rabbit.max_message_size up to RabbitMQ 3.13 was 128 MiB. +%% rabbit.max_message_size since RabbitMQ 4.0 is 16 MiB. +%% To help finding an appropriate rabbit.max_message_size we also add a bucket for 50 MB. +-define(BUCKET_7, 50_000_000). +-define(BUCKET_8, 100_000_000). +%% 'infinity' means practically 512 MiB as hard limited in +%% https://github.com/rabbitmq/rabbitmq-server/blob/v4.0.2/deps/rabbit_common/include/rabbit.hrl#L254-L257 +-define(BUCKET_9, 'infinity'). + +-define(MSG_SIZE_BUCKETS, + [{1, ?BUCKET_1}, + {2, ?BUCKET_2}, + {3, ?BUCKET_3}, + {4, ?BUCKET_4}, + {5, ?BUCKET_5}, + {6, ?BUCKET_6}, + {7, ?BUCKET_7}, + {8, ?BUCKET_8}, + {9, ?BUCKET_9}]). + +-define(POS_MSG_SIZE_SUM, 10). + +-type raw_buckets() :: [{BucketUpperBound :: non_neg_integer(), + NumObservations :: non_neg_integer()}]. + +-spec init(atom()) -> ok. +init(Protocol) -> + Size = ?POS_MSG_SIZE_SUM, + Counters = counters:new(Size, [write_concurrency]), + put_counters(Protocol, Counters). + +-spec observe(atom(), non_neg_integer()) -> ok. +observe(Protocol, MessageSize) -> + BucketPos = find_bucket_pos(MessageSize), + Counters = get_counters(Protocol), + counters:add(Counters, BucketPos, 1), + counters:add(Counters, ?POS_MSG_SIZE_SUM, MessageSize). + +-spec prometheus_format() -> #{atom() => map()}. +prometheus_format() -> + Values = [prometheus_values(Counters) || Counters <- get_labels_counters()], + #{message_size_bytes => #{type => histogram, + help => "Size of messages received from publishers", + values => Values}}. + +find_bucket_pos(Size) when Size =< ?BUCKET_1 -> 1; +find_bucket_pos(Size) when Size =< ?BUCKET_2 -> 2; +find_bucket_pos(Size) when Size =< ?BUCKET_3 -> 3; +find_bucket_pos(Size) when Size =< ?BUCKET_4 -> 4; +find_bucket_pos(Size) when Size =< ?BUCKET_5 -> 5; +find_bucket_pos(Size) when Size =< ?BUCKET_6 -> 6; +find_bucket_pos(Size) when Size =< ?BUCKET_7 -> 7; +find_bucket_pos(Size) when Size =< ?BUCKET_8 -> 8; +find_bucket_pos(_Size) -> 9. + +raw_buckets(Protocol) + when is_atom(Protocol) -> + Counters = get_counters(Protocol), + raw_buckets(Counters); +raw_buckets(Counters) -> + [{UpperBound, counters:get(Counters, Pos)} + || {Pos, UpperBound} <- ?MSG_SIZE_BUCKETS]. + +-spec diff_raw_buckets(raw_buckets(), raw_buckets()) -> raw_buckets(). +diff_raw_buckets(After, Before) -> + diff_raw_buckets(After, Before, []). + +diff_raw_buckets([], [], Acc) -> + lists:reverse(Acc); +diff_raw_buckets([{UpperBound, CounterAfter} | After], + [{UpperBound, CounterBefore} | Before], + Acc) -> + case CounterAfter - CounterBefore of + 0 -> + diff_raw_buckets(After, Before, Acc); + Diff -> + diff_raw_buckets(After, Before, [{UpperBound, Diff} | Acc]) + end. + +%% "If you have looked at a /metrics for a histogram, you probably noticed that the buckets +%% aren’t just a count of events that fall into them. The buckets also include a count of +%% events in all the smaller buckets, all the way up to the +Inf, bucket which is the total +%% number of events. This is known as a cumulative histogram, and why the bucket label +%% is called le, standing for less than or equal to. +%% This is in addition to buckets being counters, so Prometheus histograms are cumula‐ +%% tive in two different ways." +%% [Prometheus: Up & Running] +prometheus_values({Labels, Counters}) -> + {Buckets, Count} = lists:mapfoldl( + fun({UpperBound, NumObservations}, Acc0) -> + Acc = Acc0 + NumObservations, + {{UpperBound, Acc}, Acc} + end, 0, raw_buckets(Counters)), + Sum = counters:get(Counters, ?POS_MSG_SIZE_SUM), + {Labels, Buckets, Count, Sum}. + +put_counters(Protocol, Counters) -> + persistent_term:put({?MODULE, Protocol}, Counters). + +get_counters(Protocol) -> + persistent_term:get({?MODULE, Protocol}). + +get_labels_counters() -> + [{[{protocol, Protocol}], Counters} + || {{?MODULE, Protocol}, Counters} <- persistent_term:get()]. + +-ifdef(TEST). +%% "Counters are not tied to the current process and are automatically +%% garbage collected when they are no longer referenced." +-spec cleanup(atom()) -> ok. +cleanup(Protocol) -> + persistent_term:erase({?MODULE, Protocol}), + ok. +-endif. diff --git a/deps/rabbit/src/rabbit_networking.erl b/deps/rabbit/src/rabbit_networking.erl index 6788336df0e1..481df739bb31 100644 --- a/deps/rabbit/src/rabbit_networking.erl +++ b/deps/rabbit/src/rabbit_networking.erl @@ -25,9 +25,15 @@ node_listeners/1, node_client_listeners/1, register_connection/1, unregister_connection/1, register_non_amqp_connection/1, unregister_non_amqp_connection/1, +<<<<<<< HEAD connections/0, non_amqp_connections/0, connection_info_keys/0, connection_info/1, connection_info/2, connection_info_all/0, connection_info_all/1, +======= + connections/0, non_amqp_connections/0, + connection_info/2, + connection_info_all/1, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) emit_connection_info_all/4, emit_connection_info_local/3, close_connection/2, close_connections/2, close_all_connections/1, close_all_user_connections/2, @@ -482,6 +488,7 @@ non_amqp_connections() -> local_non_amqp_connections() -> pg_local:get_members(rabbit_non_amqp_connections). +<<<<<<< HEAD -spec connection_info_keys() -> rabbit_types:info_keys(). connection_info_keys() -> rabbit_reader:info_keys(). @@ -490,15 +497,20 @@ connection_info_keys() -> rabbit_reader:info_keys(). connection_info(Pid) -> rabbit_reader:info(Pid). +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec connection_info(rabbit_types:connection(), rabbit_types:info_keys()) -> rabbit_types:infos(). connection_info(Pid, Items) -> rabbit_reader:info(Pid, Items). +<<<<<<< HEAD -spec connection_info_all() -> [rabbit_types:infos()]. connection_info_all() -> cmap(fun (Q) -> connection_info(Q) end). +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec connection_info_all(rabbit_types:info_keys()) -> [rabbit_types:infos()]. diff --git a/deps/rabbit/src/rabbit_prelaunch_feature_flags.erl b/deps/rabbit/src/rabbit_prelaunch_feature_flags.erl index cc8918a6b085..2504bd1eb4ff 100644 --- a/deps/rabbit/src/rabbit_prelaunch_feature_flags.erl +++ b/deps/rabbit/src/rabbit_prelaunch_feature_flags.erl @@ -37,7 +37,13 @@ setup(#{feature_flags_file := FFFile}) -> "Failed to initialize feature flags registry: ~tp", [Reason], #{domain => ?RMQLOG_DOMAIN_PRELAUNCH}), +<<<<<<< HEAD throw({error, failed_to_initialize_feature_flags_registry}) +======= + throw({error, + {failed_to_initialize_feature_flags_registry, + Reason}}) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end; {error, Reason} -> ?LOG_ERROR( diff --git a/deps/rabbit/src/rabbit_queue_type.erl b/deps/rabbit/src/rabbit_queue_type.erl index 207b1c6a5634..1a8bebc57376 100644 --- a/deps/rabbit/src/rabbit_queue_type.erl +++ b/deps/rabbit/src/rabbit_queue_type.erl @@ -58,6 +58,10 @@ fold_state/3, is_policy_applicable/2, is_server_named_allowed/1, +<<<<<<< HEAD +======= + amqp_capabilities/1, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) arguments/1, arguments/2, notify_decorators/1, @@ -129,6 +133,10 @@ consumer_tag := rabbit_types:ctag(), exclusive_consume => boolean(), args => rabbit_framing:amqp_table(), +<<<<<<< HEAD +======= + filter => rabbit_amqp_filtex:filter_expressions(), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ok_msg := term(), acting_user := rabbit_types:username()}. -type cancel_reason() :: cancel | remove. @@ -493,6 +501,15 @@ is_server_named_allowed(Type) -> Capabilities = Type:capabilities(), maps:get(server_named, Capabilities, false). +<<<<<<< HEAD +======= +-spec amqp_capabilities(queue_type()) -> + [binary()]. +amqp_capabilities(Type) -> + Capabilities = Type:capabilities(), + maps:get(?FUNCTION_NAME, Capabilities, []). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec arguments(arguments()) -> [binary()]. arguments(ArgumentType) -> Args0 = lists:map(fun(T) -> diff --git a/deps/rabbit/src/rabbit_quorum_queue.erl b/deps/rabbit/src/rabbit_quorum_queue.erl index c59c8d8be09c..781e74ec01b2 100644 --- a/deps/rabbit/src/rabbit_quorum_queue.erl +++ b/deps/rabbit/src/rabbit_quorum_queue.erl @@ -965,7 +965,11 @@ consume(Q, Spec, QState0) when ?amqqueue_is_quorum(Q) -> exclusive_consume := ExclusiveConsume, args := Args, ok_msg := OkMsg, +<<<<<<< HEAD acting_user := ActingUser} = Spec, +======= + acting_user := ActingUser} = Spec, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% TODO: validate consumer arguments %% currently quorum queues do not support any arguments QName = amqqueue:get_name(Q), diff --git a/deps/rabbit/src/rabbit_reader.erl b/deps/rabbit/src/rabbit_reader.erl index da5eda69f057..66c8dbb59049 100644 --- a/deps/rabbit/src/rabbit_reader.erl +++ b/deps/rabbit/src/rabbit_reader.erl @@ -42,8 +42,14 @@ -include_lib("rabbit_common/include/rabbit_framing.hrl"). -include_lib("rabbit_common/include/rabbit.hrl"). +<<<<<<< HEAD -export([start_link/2, info_keys/0, info/1, info/2, force_event_refresh/2, +======= +-include("rabbit_amqp_reader.hrl"). + +-export([start_link/2, info/2, force_event_refresh/2, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) shutdown/2]). -export([system_continue/3, system_terminate/4, system_code_change/4]). @@ -116,6 +122,7 @@ connection_blocked_message_sent }). +<<<<<<< HEAD -define(STATISTICS_KEYS, [pid, recv_oct, recv_cnt, send_oct, send_cnt, send_pend, state, channels, reductions, garbage_collection]). @@ -124,6 +131,8 @@ -define(OTHER_METRICS, [recv_cnt, send_cnt, send_pend, state, channels, garbage_collection]). +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -define(CREATION_EVENT_KEYS, [pid, name, port, peer_port, host, peer_host, ssl, peer_cert_subject, peer_cert_issuer, @@ -132,8 +141,11 @@ timeout, frame_max, channel_max, client_properties, connected_at, node, user_who_performed_action]). +<<<<<<< HEAD -define(INFO_KEYS, ?CREATION_EVENT_KEYS ++ ?STATISTICS_KEYS -- [pid]). +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -define(AUTH_NOTIFICATION_INFO_KEYS, [host, name, peer_host, peer_port, protocol, auth_mechanism, ssl, ssl_protocol, ssl_cipher, peer_cert_issuer, peer_cert_subject, @@ -184,6 +196,7 @@ system_terminate(Reason, _Parent, _Deb, _State) -> system_code_change(Misc, _Module, _OldVsn, _Extra) -> {ok, Misc}. +<<<<<<< HEAD -spec info_keys() -> rabbit_types:info_keys(). info_keys() -> ?INFO_KEYS. @@ -193,6 +206,8 @@ info_keys() -> ?INFO_KEYS. info(Pid) -> gen_server:call(Pid, info, infinity). +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec info(pid(), rabbit_types:info_keys()) -> rabbit_types:infos(). info(Pid, Items) -> @@ -629,9 +644,12 @@ handle_other({'$gen_call', From, {shutdown, Explanation}}, State) -> force -> stop; normal -> NewState end; +<<<<<<< HEAD handle_other({'$gen_call', From, info}, State) -> gen_server:reply(From, infos(?INFO_KEYS, State)), State; +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) handle_other({'$gen_call', From, {info, Items}}, State) -> gen_server:reply(From, try {ok, infos(Items, State)} catch Error -> {error, Error} @@ -1600,8 +1618,13 @@ i(state, #v1{connection_state = ConnectionState, end; i(garbage_collection, _State) -> rabbit_misc:get_gc_info(self()); +<<<<<<< HEAD i(reductions, _State) -> {reductions, Reductions} = erlang:process_info(self(), reductions), +======= +i(reductions = Item, _State) -> + {Item, Reductions} = erlang:process_info(self(), Item), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Reductions; i(Item, #v1{connection = Conn}) -> ic(Item, Conn). @@ -1623,6 +1646,10 @@ ic(client_properties, #connection{client_properties = CP}) -> CP; ic(auth_mechanism, #connection{auth_mechanism = none}) -> none; ic(auth_mechanism, #connection{auth_mechanism = {Name, _Mod}}) -> Name; ic(connected_at, #connection{connected_at = T}) -> T; +<<<<<<< HEAD +======= +ic(container_id, _) -> ''; % AMQP 1.0 specific field +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ic(Item, #connection{}) -> throw({bad_argument, Item}). socket_info(Get, Select, #v1{sock = Sock}) -> @@ -1640,12 +1667,21 @@ maybe_emit_stats(State) -> emit_stats(State) -> [{_, Pid}, +<<<<<<< HEAD {_, Recv_oct}, {_, Send_oct}, {_, Reductions}] = infos(?SIMPLE_METRICS, State), Infos = infos(?OTHER_METRICS, State), rabbit_core_metrics:connection_stats(Pid, Infos), rabbit_core_metrics:connection_stats(Pid, Recv_oct, Send_oct, Reductions), +======= + {_, RecvOct}, + {_, SendOct}, + {_, Reductions}] = infos(?SIMPLE_METRICS, State), + Infos = infos(?OTHER_METRICS, State), + rabbit_core_metrics:connection_stats(Pid, Infos), + rabbit_core_metrics:connection_stats(Pid, RecvOct, SendOct, Reductions), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) State1 = rabbit_event:reset_stats_timer(State, #v1.stats_timer), ensure_stats_timer(State1). @@ -1660,6 +1696,10 @@ pack_for_1_0(Buf, BufLen, #v1{sock = Sock, pending_recv = PendingRecv, helper_sup = {_HelperSup091, HelperSup10}, proxy_socket = ProxySocket, +<<<<<<< HEAD +======= + stats_timer = StatsTimer, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) connection = #connection{ name = Name, host = Host, @@ -1668,7 +1708,11 @@ pack_for_1_0(Buf, BufLen, #v1{sock = Sock, peer_port = PeerPort, connected_at = ConnectedAt}}) -> {Sock, PendingRecv, HelperSup10, Buf, BufLen, ProxySocket, +<<<<<<< HEAD Name, Host, PeerHost, Port, PeerPort, ConnectedAt}. +======= + Name, Host, PeerHost, Port, PeerPort, ConnectedAt, StatsTimer}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) respond_and_close(State, Channel, Protocol, Reason, LogErr) -> log_hard_error(State, Channel, LogErr), diff --git a/deps/rabbit/src/rabbit_stream_queue.erl b/deps/rabbit/src/rabbit_stream_queue.erl index a7aa3a5a18cc..ddb0e81d6ca7 100644 --- a/deps/rabbit/src/rabbit_stream_queue.erl +++ b/deps/rabbit/src/rabbit_stream_queue.erl @@ -78,13 +78,21 @@ ack :: boolean(), start_offset = 0 :: non_neg_integer(), listening_offset = 0 :: non_neg_integer(), +<<<<<<< HEAD last_consumed_offset = 0 :: non_neg_integer(), +======= + last_consumed_offset :: non_neg_integer(), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) log :: undefined | osiris_log:state(), chunk_iterator :: undefined | osiris_log:chunk_iterator(), %% These messages were already read ahead from the Osiris log, %% were part of an uncompressed sub batch, and are buffered in %% reversed order until the consumer has more credits to consume them. buffer_msgs_rev = [] :: [rabbit_amqqueue:qmsg()], +<<<<<<< HEAD +======= + filter :: rabbit_amqp_filtex:filter_expressions(), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) reader_options :: map()}). -record(stream_client, {stream_id :: string(), @@ -333,7 +341,12 @@ consume(Q, Spec, #stream_client{} = QState0) %% begins sending maybe_send_reply(ChPid, OkMsg), _ = rabbit_stream_coordinator:register_local_member_listener(Q), +<<<<<<< HEAD begin_stream(QState, ConsumerTag, OffsetSpec, Mode, AckRequired, filter_spec(Args)) +======= + Filter = maps:get(filter, Spec, []), + begin_stream(QState, ConsumerTag, OffsetSpec, Mode, AckRequired, Filter, filter_spec(Args)) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end; {undefined, _} -> {protocol_error, precondition_failed, @@ -424,7 +437,11 @@ query_local_pid(#stream_client{stream_id = StreamId} = State) -> begin_stream(#stream_client{name = QName, readers = Readers0, local_pid = LocalPid} = State, +<<<<<<< HEAD Tag, Offset, Mode, AckRequired, Options) +======= + Tag, Offset, Mode, AckRequired, Filter, Options) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) when is_pid(LocalPid) -> CounterSpec = {{?MODULE, QName, Tag, self()}, []}, {ok, Seg0} = osiris:init_reader(LocalPid, Offset, CounterSpec, Options), @@ -451,6 +468,10 @@ begin_stream(#stream_client{name = QName, listening_offset = NextOffset, last_consumed_offset = StartOffset, log = Seg0, +<<<<<<< HEAD +======= + filter = Filter, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) reader_options = Options}, {ok, State#stream_client{readers = Readers0#{Tag => Str0}}}. @@ -1158,7 +1179,12 @@ stream_entries(QName, Name, LocalPid, #stream{chunk_iterator = Iter0, delivery_count = DC, credit = Credit, +<<<<<<< HEAD start_offset = StartOffset} = Str0, Acc0) -> +======= + start_offset = StartOffset, + filter = Filter} = Str0, Acc0) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case osiris_log:iterator_next(Iter0) of end_of_chunk -> case chunk_iterator(Str0, LocalPid) of @@ -1172,7 +1198,11 @@ stream_entries(QName, Name, LocalPid, {batch, _NumRecords, 0, _Len, BatchedEntries} -> {MsgsRev, NumMsgs} = parse_uncompressed_subbatch( BatchedEntries, Offset, StartOffset, +<<<<<<< HEAD QName, Name, LocalPid, {[], 0}), +======= + QName, Name, LocalPid, Filter, {[], 0}), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case Credit >= NumMsgs of true -> {Str0#stream{chunk_iterator = Iter, @@ -1199,12 +1229,28 @@ stream_entries(QName, Name, LocalPid, _SimpleEntry -> case Offset >= StartOffset of true -> +<<<<<<< HEAD Msg = entry_to_msg(Entry, Offset, QName, Name, LocalPid), {Str0#stream{chunk_iterator = Iter, delivery_count = delivery_count_add(DC, 1), credit = Credit - 1, last_consumed_offset = Offset}, [Msg | Acc0]}; +======= + case entry_to_msg(Entry, Offset, QName, + Name, LocalPid, Filter) of + none -> + {Str0#stream{chunk_iterator = Iter, + last_consumed_offset = Offset}, + Acc0}; + Msg -> + {Str0#stream{chunk_iterator = Iter, + delivery_count = delivery_count_add(DC, 1), + credit = Credit - 1, + last_consumed_offset = Offset}, + [Msg | Acc0]} + end; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) false -> {Str0#stream{chunk_iterator = Iter}, Acc0} end @@ -1236,13 +1282,19 @@ chunk_iterator(#stream{credit = Credit, end. %% Deliver each record of an uncompressed sub batch individually. +<<<<<<< HEAD parse_uncompressed_subbatch(<<>>, _Offset, _StartOffset, _QName, _Name, _LocalPid, Acc) -> +======= +parse_uncompressed_subbatch( + <<>>, _Offset, _StartOffset, _QName, _Name, _LocalPid, _Filter, Acc) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Acc; parse_uncompressed_subbatch( <<0:1, %% simple entry Len:31/unsigned, Entry:Len/binary, Rem/binary>>, +<<<<<<< HEAD Offset, StartOffset, QName, Name, LocalPid, Acc0 = {AccList, AccCount}) -> Acc = case Offset >= StartOffset of true -> @@ -1269,6 +1321,62 @@ entry_to_msg(Entry, Offset, #resource{kind = queue, end, Mc = mc:set_annotation(<<"x-stream-offset">>, Offset, Mc2), {Name, LocalPid, Offset, false, Mc}. +======= + Offset, StartOffset, QName, Name, LocalPid, Filter, Acc0 = {AccList, AccCount}) -> + Acc = case Offset >= StartOffset of + true -> + case entry_to_msg(Entry, Offset, QName, Name, LocalPid, Filter) of + none -> + Acc0; + Msg -> + {[Msg | AccList], AccCount + 1} + end; + false -> + Acc0 + end, + parse_uncompressed_subbatch(Rem, Offset + 1, StartOffset, QName, + Name, LocalPid, Filter, Acc). + +entry_to_msg(Entry, Offset, #resource{kind = queue, name = QName}, Name, LocalPid, Filter) -> + Mc0 = mc:init(mc_amqp, Entry, #{}), + %% If exchange or routing keys annotation isn't present the entry most likely came + %% from the rabbitmq-stream plugin so we'll choose defaults that simulate use + %% of the direct exchange. + XHeaders = mc:x_headers(Mc0), + Exchange = case XHeaders of + #{<<"x-exchange">> := {utf8, X}} -> + X; + _ -> + <<>> + end, + RKeys0 = case XHeaders of + #{<<"x-cc">> := {list, CCs}} -> + [CC || {utf8, CC} <- CCs]; + _ -> + [] + end, + RKeys1 = case XHeaders of + #{<<"x-routing-key">> := {utf8, RK}} -> + [RK | RKeys0]; + _ -> + RKeys0 + end, + RKeys = case RKeys1 of + [] -> + [QName]; + _ -> + RKeys1 + end, + Mc1 = mc:set_annotation(?ANN_EXCHANGE, Exchange, Mc0), + Mc2 = mc:set_annotation(?ANN_ROUTING_KEYS, RKeys, Mc1), + Mc = mc:set_annotation(<<"x-stream-offset">>, Offset, Mc2), + case rabbit_amqp_filtex:filter(Filter, Mc) of + true -> + {Name, LocalPid, Offset, false, Mc}; + false -> + none + end. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) capabilities() -> #{unsupported_policies => [%% Classic policies @@ -1288,6 +1396,12 @@ capabilities() -> consumer_arguments => [<<"x-stream-offset">>, <<"x-stream-filter">>, <<"x-stream-match-unfiltered">>], +<<<<<<< HEAD +======= + %% AMQP property filter expressions + %% https://groups.oasis-open.org/higherlogic/ws/public/document?document_id=66227 + amqp_capabilities => [<<"AMQP_FILTEX_PROP_V1_0">>], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) server_named => false}. notify_decorators(Q) when ?is_amqqueue(Q) -> diff --git a/deps/rabbit/test/amqp_address_SUITE.erl b/deps/rabbit/test/amqp_address_SUITE.erl index 910e1068eeed..71d8cc274c24 100644 --- a/deps/rabbit/test/amqp_address_SUITE.erl +++ b/deps/rabbit/test/amqp_address_SUITE.erl @@ -18,6 +18,12 @@ [rpc/4]). -import(rabbit_ct_helpers, [eventually/1]). +<<<<<<< HEAD +======= +-import(amqp_utils, + [flush/1, + wait_for_credit/1]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) all() -> [ @@ -301,10 +307,16 @@ target_per_message_exchange_routing_key(Config) -> Tag1 = Body1 = <<1>>, Tag2 = Body2 = <<2>>, +<<<<<<< HEAD %% Although mc_amqp:essential_properties/1 parses these annotations, they should be ignored. Msg1 = amqp10_msg:set_message_annotations( #{<<"x-exchange">> => <<"ignored">>, <<"x-routing-key">> => <<"ignored">>}, +======= + %% Although mc_amqp:essential_properties/1 parses the x-exchange annotation, it should be ignored. + Msg1 = amqp10_msg:set_message_annotations( + #{<<"x-exchange">> => <<"ignored">>}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) amqp10_msg:set_properties(#{to => To1}, amqp10_msg:new(Tag1, Body1))), Msg2 = amqp10_msg:set_properties(#{to => To2}, amqp10_msg:new(Tag2, Body2)), ok = amqp10_client:send_msg(Sender, Msg1), @@ -651,6 +663,7 @@ connection_config(Config) -> container_id => <<"my container">>, sasl => {plain, <<"guest">>, <<"guest">>}}. +<<<<<<< HEAD % before we can send messages we have to wait for credit from the server wait_for_credit(Sender) -> receive @@ -662,6 +675,8 @@ wait_for_credit(Sender) -> ct:fail(?FUNCTION_NAME) end. +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) wait_for_settled(State, Tag) -> receive {amqp10_disposition, {State, Tag}} -> @@ -671,6 +686,7 @@ wait_for_settled(State, Tag) -> flush(Reason), ct:fail(Reason) end. +<<<<<<< HEAD flush(Prefix) -> receive Msg -> @@ -679,3 +695,5 @@ flush(Prefix) -> after 1 -> ok end. +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbit/test/amqp_auth_SUITE.erl b/deps/rabbit/test/amqp_auth_SUITE.erl index 920f779172d4..461c110b89d2 100644 --- a/deps/rabbit/test/amqp_auth_SUITE.erl +++ b/deps/rabbit/test/amqp_auth_SUITE.erl @@ -21,6 +21,13 @@ -import(event_recorder, [assert_event_type/2, assert_event_prop/2]). +<<<<<<< HEAD +======= +-import(amqp_utils, + [flush/1, + wait_for_credit/1, + close_connection_sync/1]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) all() -> [ @@ -1077,6 +1084,7 @@ amqp_error(Condition, Description) condition = Condition, description = {utf8, Description}}. +<<<<<<< HEAD % before we can send messages we have to wait for credit from the server wait_for_credit(Sender) -> receive @@ -1096,10 +1104,13 @@ flush(Prefix) -> ok end. +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) delete_all_queues(Config) -> Qs = rpc(Config, rabbit_amqqueue, list, []), [{ok, _QLen} = rpc(Config, rabbit_amqqueue, delete, [Q, false, false, <<"fake-user">>]) || Q <- Qs]. +<<<<<<< HEAD close_connection_sync(Connection) when is_pid(Connection) -> @@ -1108,3 +1119,5 @@ close_connection_sync(Connection) after 5000 -> flush(missing_closed), ct:fail("missing CLOSE from server") end. +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbit/test/amqp_client_SUITE.erl b/deps/rabbit/test/amqp_client_SUITE.erl index 09916c55c356..1fb87e805f0d 100644 --- a/deps/rabbit/test/amqp_client_SUITE.erl +++ b/deps/rabbit/test/amqp_client_SUITE.erl @@ -27,6 +27,20 @@ -import(event_recorder, [assert_event_type/2, assert_event_prop/2]). +<<<<<<< HEAD +======= +-import(amqp_utils, + [init/1, init/2, + connection_config/1, connection_config/2, + flush/1, + wait_for_credit/1, + wait_for_accepts/1, + send_messages/3, send_messages/4, + detach_link_sync/1, + end_session_sync/1, + wait_for_session_end/1, + close_connection_sync/1]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) all() -> [ @@ -101,12 +115,21 @@ groups() -> max_message_size_client_to_server, max_message_size_server_to_client, global_counters, +<<<<<<< HEAD stream_filtering, +======= + stream_bloom_filter, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) available_messages_classic_queue, available_messages_quorum_queue, available_messages_stream, incoming_message_interceptors, +<<<<<<< HEAD trace, +======= + trace_classic_queue, + trace_stream, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) user_id, message_ttl, plugin, @@ -146,7 +169,16 @@ groups() -> tcp_back_pressure_rabbitmq_internal_flow_quorum_queue, session_max_per_connection, link_max_per_session, +<<<<<<< HEAD reserved_annotation +======= + reserved_annotation, + x_cc_annotation_exchange, + x_cc_annotation_exchange_routing_key_empty, + x_cc_annotation_queue, + x_cc_annotation_null, + bad_x_cc_annotation_exchange +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]}, {cluster_size_3, [shuffle], @@ -287,12 +319,24 @@ init_per_testcase(T, Config) when T =:= detach_requeues_one_session_quorum_queue orelse T =:= single_active_consumer_quorum_queue orelse T =:= detach_requeues_two_connections_quorum_queue -> +<<<<<<< HEAD case rabbit_ct_broker_helpers:enable_feature_flag(Config, 'rabbitmq_4.0.0') of ok -> rabbit_ct_helpers:testcase_started(Config, T); {skip, _} -> {skip, "Feature flag rabbitmq_4.0.0 enables the consumer removal API"} end; +======= + %% Feature flag rabbitmq_4.0.0 enables the consumer removal API. + ok = rabbit_ct_broker_helpers:enable_feature_flag(Config, 'rabbitmq_4.0.0'), + rabbit_ct_helpers:testcase_started(Config, T); +init_per_testcase(T, Config) + when T =:= leader_transfer_quorum_queue_credit_single orelse + T =:= leader_transfer_quorum_queue_credit_batches -> + %% These test cases flake with feature flag 'rabbitmq_4.0.0' disabled. + ok = rabbit_ct_broker_helpers:enable_feature_flag(Config, 'rabbitmq_4.0.0'), + rabbit_ct_helpers:testcase_started(Config, T); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) init_per_testcase(T = immutable_bare_message, Config) -> case rpc(Config, rabbit_feature_flags, is_enabled, ['rabbitmq_4.0.0']) of true -> @@ -317,6 +361,7 @@ init_per_testcase(T = dead_letter_reject, Config) -> {skip, "This test is known to fail with feature flag message_containers_deaths_v2 disabled " "due bug https://github.com/rabbitmq/rabbitmq-server/issues/11159"} end; +<<<<<<< HEAD init_per_testcase(T, Config) when T =:= leader_transfer_quorum_queue_credit_single orelse T =:= leader_transfer_quorum_queue_credit_batches orelse @@ -337,6 +382,8 @@ init_per_testcase(T, Config) %% If node 1 runs 3.x, this is the old real plugin. ok = rabbit_ct_broker_helpers:enable_plugin(Config, 1, rabbitmq_amqp1_0), rabbit_ct_helpers:testcase_started(Config, T); +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) init_per_testcase(Testcase, Config) -> rabbit_ct_helpers:testcase_started(Config, Testcase). @@ -1170,7 +1217,11 @@ roundtrip_with_drain(Config, QueueType, QName) % wait for a delivery receive {amqp10_msg, Receiver, InMsg} -> ok = amqp10_client:accept_msg(Receiver, InMsg) +<<<<<<< HEAD after 2000 -> +======= + after 30000 -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Reason = delivery_timeout, flush(Reason), ct:fail(Reason) @@ -1257,7 +1308,11 @@ drain_many(Config, QueueType, QName) %% We expect the server to send us the last message and %% to advance the delivery-count promptly. receive {amqp10_msg, _, _} -> ok +<<<<<<< HEAD after 2000 -> ct:fail({missing_delivery, ?LINE}) +======= + after 30000 -> ct:fail({missing_delivery, ?LINE}) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end, receive {amqp10_event, {link, Receiver, credit_exhausted}} -> ok after 300 -> ct:fail("expected credit_exhausted") @@ -1307,7 +1362,11 @@ amqp_amqpl(QType, Config) -> ok = amqp10_client:send_msg( Sender, amqp10_msg:set_application_properties( +<<<<<<< HEAD #{"my int" => -2}, +======= + #{"my int" => {int, -2}}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) amqp10_msg:new(<<>>, Body1, true))), %% Send with properties CorrelationID = <<"my correlation ID">>, @@ -1322,7 +1381,11 @@ amqp_amqpl(QType, Config) -> amqp10_msg:set_properties( #{correlation_id => CorrelationID}, amqp10_msg:set_application_properties( +<<<<<<< HEAD #{"my int" => -2}, +======= + #{"my long" => -9_000_000_000}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) amqp10_msg:new(<<>>, Body1, true)))), %% Send with footer Footer = #'v1_0.footer'{content = [{{symbol, <<"x-my footer">>}, {ubyte, 255}}]}, @@ -1411,7 +1474,11 @@ amqp_amqpl(QType, Config) -> correlation_id = Corr9}}} -> ?assertEqual([Body1], amqp10_framing:decode_bin(Payload9)), ?assertEqual(CorrelationID, Corr9), +<<<<<<< HEAD ?assertEqual({signedint, -2}, rabbit_misc:table_lookup(Headers9, <<"my int">>)) +======= + ?assertEqual({long, -9_000_000_000}, rabbit_misc:table_lookup(Headers9, <<"my long">>)) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) after 30000 -> ct:fail({missing_deliver, ?LINE}) end, receive {_, #amqp_msg{payload = Payload10}} -> @@ -1459,12 +1526,22 @@ amqp10_to_amqp091_header_conversion(Session,Ch, QName, Address) -> OutMsg1 = amqp10_msg:new(<<"my-tag">>, <<"my-body">>, false), OutMsg2 = amqp10_msg:set_application_properties( #{"string" => "string-val", +<<<<<<< HEAD "int" => 2, +======= + "long" => -2, + "uint" => {uint, 2}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "bool" => false}, OutMsg1), OutMsg3 = amqp10_msg:set_message_annotations( #{"x-string" => "string-value", +<<<<<<< HEAD "x-int" => 3, +======= + "x-long" => -3, + "x-uint" => {uint, 3}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "x-bool" => true}, OutMsg2), OutMsg = amqp10_msg:set_headers( @@ -1484,11 +1561,21 @@ amqp10_to_amqp091_header_conversion(Session,Ch, QName, Address) -> %% assert application properties ?assertEqual({longstr, <<"string-val">>}, rabbit_misc:table_lookup(Headers, <<"string">>)), +<<<<<<< HEAD ?assertEqual({unsignedint, 2}, rabbit_misc:table_lookup(Headers, <<"int">>)), ?assertEqual({bool, false}, rabbit_misc:table_lookup(Headers, <<"bool">>)), %% assert message annotations ?assertEqual({longstr, <<"string-value">>}, rabbit_misc:table_lookup(Headers, <<"x-string">>)), ?assertEqual({unsignedint, 3}, rabbit_misc:table_lookup(Headers, <<"x-int">>)), +======= + ?assertEqual({long, -2}, rabbit_misc:table_lookup(Headers, <<"long">>)), + ?assertEqual({unsignedint, 2}, rabbit_misc:table_lookup(Headers, <<"uint">>)), + ?assertEqual({bool, false}, rabbit_misc:table_lookup(Headers, <<"bool">>)), + %% assert message annotations + ?assertEqual({longstr, <<"string-value">>}, rabbit_misc:table_lookup(Headers, <<"x-string">>)), + ?assertEqual({long, -3}, rabbit_misc:table_lookup(Headers, <<"x-long">>)), + ?assertEqual({unsignedint, 3}, rabbit_misc:table_lookup(Headers, <<"x-uint">>)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?assertEqual({bool, true}, rabbit_misc:table_lookup(Headers, <<"x-bool">>)), %% assert headers ?assertEqual(2, DeliveryMode), @@ -1888,18 +1975,31 @@ events(Config) -> Protocol = {protocol, {1, 0}}, AuthProps = [{name, <<"guest">>}, +<<<<<<< HEAD {auth_mechanism, <<"PLAIN">>}, {ssl, false}, Protocol], ?assertMatch( {value, _}, find_event(user_authentication_success, AuthProps, Events)), +======= + {auth_mechanism, <<"PLAIN">>}, + {ssl, false}, + Protocol], + ?assertMatch( + {value, _}, + find_event(user_authentication_success, AuthProps, Events)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Node = get_node_config(Config, 0, nodename), ConnectionCreatedProps = [Protocol, {node, Node}, {vhost, <<"/">>}, {user, <<"guest">>}, +<<<<<<< HEAD +======= + {container_id, <<"my container">>}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {type, network}], {value, ConnectionCreatedEvent} = find_event( connection_created, @@ -1920,8 +2020,13 @@ events(Config) -> Pid, ClientProperties], ?assertMatch( +<<<<<<< HEAD {value, _}, find_event(connection_closed, ConnectionClosedProps, Events)), +======= + {value, _}, + find_event(connection_closed, ConnectionClosedProps, Events)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ok. sync_get_unsettled_classic_queue(Config) -> @@ -3011,7 +3116,11 @@ detach_requeues_two_connections(QType, Config) -> ok = gen_statem:cast(Session0, {flow_session, #'v1_0.flow'{incoming_window = {uint, 1}}}), ok = amqp10_client:flow_link_credit(Receiver0, 50, never), %% Wait for credit being applied to the queue. +<<<<<<< HEAD timer:sleep(10), +======= + timer:sleep(100), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {ok, Receiver1} = amqp10_client:attach_receiver_link(Session1, <<"receiver 1">>, Address, unsettled), receive {amqp10_event, {link, Receiver1, attached}} -> ok @@ -3019,7 +3128,11 @@ detach_requeues_two_connections(QType, Config) -> end, ok = amqp10_client:flow_link_credit(Receiver1, 40, never), %% Wait for credit being applied to the queue. +<<<<<<< HEAD timer:sleep(10), +======= + timer:sleep(100), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) NumMsgs = 6, [begin @@ -3409,7 +3522,11 @@ last_queue_confirms(Config) -> ok = rabbit_ct_broker_helpers:start_node(Config, 2), %% Since the quorum queue has become available, we should now get a confirmation for m2. receive {amqp10_disposition, {accepted, DTag2}} -> ok +<<<<<<< HEAD after 10_000 -> ct:fail({missing_accepted, DTag2}) +======= + after 30_000 -> ct:fail({missing_accepted, DTag2}) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end, ok = amqp10_client:detach_link(SenderClassicQ), @@ -3458,7 +3575,11 @@ target_queue_deleted(Config) -> after 30000 -> ct:fail({missing_accepted, DTag1}) end, +<<<<<<< HEAD N0 = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), +======= + N0 = get_node_config(Config, 0, nodename), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) RaName = ra_name(QuorumQ), ServerId0 = {RaName, N0}, {ok, Members, _Leader} = ra:members(ServerId0), @@ -3484,7 +3605,11 @@ target_queue_deleted(Config) -> ok = rabbit_ct_broker_helpers:start_node(Config, ReplicaNode), %% Since the quorum queue has become available, we should now get a confirmation for m2. receive {amqp10_disposition, {accepted, DTag2}} -> ok +<<<<<<< HEAD after 10_000 -> ct:fail({missing_accepted, DTag2}) +======= + after 30_000 -> ct:fail({missing_accepted, DTag2}) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end, ok = amqp10_client:detach_link(Sender), @@ -3527,7 +3652,11 @@ target_classic_queue_down(Config) -> %% We expect that the server closes links that receive from classic queues that are down. ExpectedError = #'v1_0.error'{condition = ?V_1_0_AMQP_ERROR_ILLEGAL_STATE}, receive {amqp10_event, {link, Receiver1, {detached, ExpectedError}}} -> ok +<<<<<<< HEAD after 10_000 -> ct:fail({missing_event, ?LINE}) +======= + after 30_000 -> ct:fail({missing_event, ?LINE}) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end, %% However the server should not close links that send to classic queues that are down. receive Unexpected -> ct:fail({unexpected, Unexpected}) @@ -3567,6 +3696,7 @@ async_notify_settled_stream(Config) -> async_notify(settled, <<"stream">>, Config). async_notify_unsettled_classic_queue(Config) -> +<<<<<<< HEAD case rabbit_ct_broker_helpers:enable_feature_flag(Config, 'rabbitmq_4.0.0') of ok -> async_notify(unsettled, <<"classic">>, Config); @@ -3575,6 +3705,13 @@ async_notify_unsettled_classic_queue(Config) -> "queues with credit API v1 is known to be broken: " "https://github.com/rabbitmq/rabbitmq-server/issues/2597"} end. +======= + %% This test flakes with feature flag 'rabbitmq_4.0.0' disabled. + %% Link flow control in classic queues with credit API v1 is known to be broken: + %% https://github.com/rabbitmq/rabbitmq-server/issues/2597 + ok = rabbit_ct_broker_helpers:enable_feature_flag(Config, 'rabbitmq_4.0.0'), + async_notify(unsettled, <<"classic">>, Config). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) async_notify_unsettled_quorum_queue(Config) -> async_notify(unsettled, <<"quorum">>, Config). @@ -3876,7 +4013,10 @@ leader_transfer_credit(QName, QType, Credit, Config) -> ok = end_session_sync(Session1), ok = close_connection_sync(Connection1), +<<<<<<< HEAD %% Consume from a follower. +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) OpnConf = connection_config(0, Config), {ok, Connection0} = amqp10_client:open_connection(OpnConf), {ok, Session0} = amqp10_client:begin_session_sync(Connection0), @@ -3890,6 +4030,10 @@ leader_transfer_credit(QName, QType, Credit, Config) -> ok = wait_for_accepts(NumMsgs), ok = detach_link_sync(Sender), +<<<<<<< HEAD +======= + %% Consume from a follower. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ok = wait_for_local_member(QType, QName, Config), Filter = consume_from_first(QType), {ok, Receiver} = amqp10_client:attach_receiver_link( @@ -3958,8 +4102,17 @@ list_connections(Config) -> [ok = rabbit_ct_client_helpers:close_channels_and_connection(Config, Node) || Node <- [0, 1, 2]], Connection091 = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 0), +<<<<<<< HEAD {ok, C0} = amqp10_client:open_connection(connection_config(0, Config)), {ok, C2} = amqp10_client:open_connection(connection_config(2, Config)), +======= + ContainerId0 = <<"ID 0">>, + ContainerId2 = <<"ID 2">>, + Cfg0 = maps:put(container_id, ContainerId0, connection_config(0, Config)), + Cfg2 = maps:put(container_id, ContainerId2, connection_config(2, Config)), + {ok, C0} = amqp10_client:open_connection(Cfg0), + {ok, C2} = amqp10_client:open_connection(Cfg2), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) receive {amqp10_event, {connection, C0, opened}} -> ok after 30000 -> ct:fail({missing_event, ?LINE}) end, @@ -3967,8 +4120,13 @@ list_connections(Config) -> after 30000 -> ct:fail({missing_event, ?LINE}) end, +<<<<<<< HEAD {ok, StdOut} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["list_connections", "--silent", "protocol"]), Protocols0 = re:split(StdOut, <<"\n">>, [trim]), +======= + {ok, StdOut0} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["list_connections", "--silent", "protocol"]), + Protocols0 = re:split(StdOut0, <<"\n">>, [trim]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% Remove any whitespaces. Protocols1 = [binary:replace(Subject, <<" ">>, <<>>, [global]) || Subject <- Protocols0], Protocols = lists:sort(Protocols1), @@ -3977,6 +4135,16 @@ list_connections(Config) -> <<"{1,0}">>], Protocols), +<<<<<<< HEAD +======= + %% CLI should list AMQP 1.0 container-id + {ok, StdOut1} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["list_connections", "--silent", "container_id"]), + ContainerIds0 = re:split(StdOut1, <<"\n">>, [trim]), + ContainerIds = lists:sort(ContainerIds0), + ?assertEqual([<<>>, ContainerId0, ContainerId2], + ContainerIds), + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ok = rabbit_ct_client_helpers:close_connection(Connection091), ok = close_connection_sync(C0), ok = close_connection_sync(C2). @@ -4129,7 +4297,11 @@ global_counters(Config) -> ok = end_session_sync(Session), ok = amqp10_client:close_connection(Connection). +<<<<<<< HEAD stream_filtering(Config) -> +======= +stream_bloom_filter(Config) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Stream = atom_to_binary(?FUNCTION_NAME), Address = rabbitmq_amqp_address:queue(Stream), Ch = rabbit_ct_client_helpers:open_channel(Config), @@ -4412,16 +4584,37 @@ incoming_message_interceptors(Config) -> ok = amqp10_client:close_connection(Connection), true = rpc(Config, persistent_term, erase, [Key]). +<<<<<<< HEAD trace(Config) -> Node = atom_to_binary(get_node_config(Config, 0, nodename)), TraceQ = <<"my trace queue">>, Q = <<"my queue">>, +======= +trace_classic_queue(Config) -> + trace(atom_to_binary(?FUNCTION_NAME), <<"classic">>, Config). + +trace_stream(Config) -> + trace(atom_to_binary(?FUNCTION_NAME), <<"stream">>, Config). + +trace(Q, QType, Config) -> + Node = atom_to_binary(get_node_config(Config, 0, nodename)), + TraceQ = <<"my trace queue">>, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Qs = [Q, TraceQ], RoutingKey = <<"my routing key">>, Payload = <<"my payload">>, CorrelationId = <<"my correlation 👀"/utf8>>, Ch = rabbit_ct_client_helpers:open_channel(Config), +<<<<<<< HEAD [#'queue.declare_ok'{} = amqp_channel:call(Ch, #'queue.declare'{queue = Q0}) || Q0 <- Qs], +======= + #'queue.declare_ok'{} = amqp_channel:call( + Ch, #'queue.declare'{ + queue = Q, + durable = true, + arguments = [{<<"x-queue-type">>, longstr, QType}]}), + #'queue.declare_ok'{} = amqp_channel:call(Ch, #'queue.declare'{queue = TraceQ}), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) #'queue.bind_ok'{} = amqp_channel:call( Ch, #'queue.bind'{queue = TraceQ, exchange = <<"amq.rabbitmq.trace">>, @@ -4439,16 +4632,32 @@ trace(Config) -> {ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["trace_on"]), {ok, SessionReceiver} = amqp10_client:begin_session_sync(Connection), +<<<<<<< HEAD +======= + {ok, Receiver} = amqp10_client:attach_receiver_link(SessionReceiver, + <<"test-receiver">>, + rabbitmq_amqp_address:queue(Q)), + receive {amqp10_event, {link, Receiver, attached}} -> ok + after 30000 -> ct:fail({missing_event, ?LINE}) + end, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {ok, Sender} = amqp10_client:attach_sender_link( SessionSender, <<"test-sender">>, rabbitmq_amqp_address:exchange(<<"amq.direct">>, RoutingKey)), ok = wait_for_credit(Sender), +<<<<<<< HEAD {ok, Receiver} = amqp10_client:attach_receiver_link(SessionReceiver, <<"test-receiver">>, rabbitmq_amqp_address:queue(Q)), Msg0 = amqp10_msg:new(<<"tag 1">>, Payload, true), Msg = amqp10_msg:set_properties(#{correlation_id => CorrelationId}, Msg0), +======= + Msg0 = amqp10_msg:new(<<"tag 1">>, Payload, true), + Msg = amqp10_msg:set_message_annotations( + #{<<"x-cc">> => {list, [{utf8, <<"my CC key">>}]}}, + amqp10_msg:set_properties(#{correlation_id => CorrelationId}, Msg0)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ok = amqp10_client:send_msg(Sender, Msg), {ok, _} = amqp10_client:get_msg(Receiver), @@ -4458,7 +4667,11 @@ trace(Config) -> payload = Payload}} = amqp_channel:call(Ch, #'basic.get'{queue = TraceQ}), ?assertMatch(#{<<"exchange_name">> := <<"amq.direct">>, +<<<<<<< HEAD <<"routing_keys">> := [RoutingKey], +======= + <<"routing_keys">> := [RoutingKey, <<"my CC key">>], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) <<"connection">> := <<"127.0.0.1:", _/binary>>, <<"node">> := Node, <<"vhost">> := <<"/">>, @@ -4473,7 +4686,11 @@ trace(Config) -> payload = Payload}} = amqp_channel:call(Ch, #'basic.get'{queue = TraceQ}), ?assertMatch(#{<<"exchange_name">> := <<"amq.direct">>, +<<<<<<< HEAD <<"routing_keys">> := [RoutingKey], +======= + <<"routing_keys">> := [RoutingKey, <<"my CC key">>], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) <<"connection">> := <<"127.0.0.1:", _/binary>>, <<"node">> := Node, <<"vhost">> := <<"/">>, @@ -4644,9 +4861,13 @@ idle_time_out_on_client(Config) -> receive {amqp10_event, {connection, Connection, +<<<<<<< HEAD {closed, {resource_limit_exceeded, <<"remote idle-time-out">>}}}} -> ok +======= + {closed, _}}} -> ok +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) after 30000 -> ct:fail({missing_event, ?LINE}) end, @@ -4668,7 +4889,11 @@ handshake_timeout(Config) -> Par = ?FUNCTION_NAME, {ok, DefaultVal} = rpc(Config, application, get_env, [App, Par]), ok = rpc(Config, application, set_env, [App, Par, 200]), +<<<<<<< HEAD Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp), +======= + Port = get_node_config(Config, 0, tcp_port_amqp), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {ok, Socket} = gen_tcp:connect("localhost", Port, [{active, false}]), ?assertEqual({error, closed}, gen_tcp:recv(Socket, 0, 400)), ok = rpc(Config, application, set_env, [App, Par, DefaultVal]). @@ -4683,7 +4908,11 @@ credential_expires(Config) -> OpnConf = connection_config(Config), {ok, Connection} = amqp10_client:open_connection(OpnConf), receive {amqp10_event, {connection, Connection, opened}} -> ok +<<<<<<< HEAD after 2000 -> ct:fail({missing_event, ?LINE}) +======= + after 30000 -> ct:fail({missing_event, ?LINE}) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end, %% Since we don't renew our credential, we expect the server to close our connection. @@ -4692,7 +4921,11 @@ credential_expires(Config) -> {connection, Connection, {closed, {unauthorized_access, <<"credential expired">>}}}} -> ok +<<<<<<< HEAD after 10_000 -> +======= + after 30_000 -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) flush(?LINE), ct:fail({missing_event, ?LINE}) end, @@ -5977,6 +6210,7 @@ reserved_annotation(Config) -> end, ok = close_connection_sync(Connection). +<<<<<<< HEAD %% internal %% @@ -5990,6 +6224,244 @@ init(Node, Config) -> {ok, LinkPair} = rabbitmq_amqp_client:attach_management_link_pair_sync(Session, <<"my link pair">>), {Connection, Session, LinkPair}. +======= +%% Test that x-cc routing keys work together with target address +%% /exchanges/:exchange/:routing-key +x_cc_annotation_exchange(Config) -> + QName1 = <<"queue 1">>, + QName2 = <<"queue 2">>, + {Connection, Session, LinkPair} = init(Config), + {ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName1, #{}), + {ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName2, #{}), + ok = rabbitmq_amqp_client:bind_queue(LinkPair, QName1, <<"amq.direct">>, <<"key 1">>, #{}), + ok = rabbitmq_amqp_client:bind_queue(LinkPair, QName2, <<"amq.direct">>, <<"key 2">>, #{}), + Address = rabbitmq_amqp_address:exchange(<<"amq.direct">>, <<"key 1">>), + {ok, Sender} = amqp10_client:attach_sender_link(Session, <<"sender">>, Address), + ok = wait_for_credit(Sender), + + Payload = <<"my message">>, + ok = amqp10_client:send_msg(Sender, amqp10_msg:set_message_annotations( + #{<<"x-cc">> => {list, [{utf8, <<"key 2">>}]}}, + amqp10_msg:new(<<"tag">>, Payload))), + ok = wait_for_accepted(<<"tag">>), + ok = amqp10_client:detach_link(Sender), + + {ok, Receiver1} = amqp10_client:attach_receiver_link( + Session, <<"receiver 1">>, rabbitmq_amqp_address:queue(QName1), settled), + {ok, Receiver2} = amqp10_client:attach_receiver_link( + Session, <<"receiver 2">>, rabbitmq_amqp_address:queue(QName2), settled), + {ok, Msg1} = amqp10_client:get_msg(Receiver1), + {ok, Msg2} = amqp10_client:get_msg(Receiver2), + ?assertEqual([Payload], amqp10_msg:body(Msg1)), + ?assertEqual([Payload], amqp10_msg:body(Msg2)), + + {ok, #{message_count := 0}} = rabbitmq_amqp_client:delete_queue(LinkPair, QName1), + {ok, #{message_count := 0}} = rabbitmq_amqp_client:delete_queue(LinkPair, QName2), + ok = end_session_sync(Session), + ok = amqp10_client:close_connection(Connection). + +%% Test that x-cc routing keys work together with target address +%% /exchanges/:exchange +x_cc_annotation_exchange_routing_key_empty(Config) -> + QName1 = <<"queue 1">>, + QName2 = <<"queue 2">>, + {Connection, Session, LinkPair} = init(Config), + {ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName1, #{}), + {ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName2, #{}), + ok = rabbitmq_amqp_client:bind_queue(LinkPair, QName1, <<"amq.direct">>, <<"key 1">>, #{}), + ok = rabbitmq_amqp_client:bind_queue(LinkPair, QName2, <<"amq.direct">>, <<"key 2">>, #{}), + AddressEmptyRoutingKey = rabbitmq_amqp_address:exchange(<<"amq.direct">>), + {ok, Sender} = amqp10_client:attach_sender_link(Session, <<"sender">>, AddressEmptyRoutingKey), + ok = wait_for_credit(Sender), + + Payload = <<"my message">>, + ok = amqp10_client:send_msg(Sender, amqp10_msg:set_message_annotations( + #{<<"x-cc">> => {list, [{utf8, <<"key 1">>}, + {utf8, <<"key 2">>}]}}, + amqp10_msg:new(<<"tag">>, Payload))), + ok = wait_for_accepted(<<"tag">>), + ok = amqp10_client:detach_link(Sender), + + {ok, Receiver1} = amqp10_client:attach_receiver_link( + Session, <<"receiver 1">>, rabbitmq_amqp_address:queue(QName1), settled), + {ok, Receiver2} = amqp10_client:attach_receiver_link( + Session, <<"receiver 2">>, rabbitmq_amqp_address:queue(QName2), settled), + {ok, Msg1} = amqp10_client:get_msg(Receiver1), + {ok, Msg2} = amqp10_client:get_msg(Receiver2), + ?assertEqual([Payload], amqp10_msg:body(Msg1)), + ?assertEqual([Payload], amqp10_msg:body(Msg2)), + + {ok, #{message_count := 0}} = rabbitmq_amqp_client:delete_queue(LinkPair, QName1), + {ok, #{message_count := 0}} = rabbitmq_amqp_client:delete_queue(LinkPair, QName2), + ok = end_session_sync(Session), + ok = amqp10_client:close_connection(Connection). + +%% Test that x-cc routing keys work together with target address +%% /queues/:queue +x_cc_annotation_queue(Config) -> + QName1 = <<"queue 1">>, + QName2 = <<"queue 2">>, + Address1 = rabbitmq_amqp_address:queue(QName1), + Address2 = rabbitmq_amqp_address:queue(QName2), + {Connection, Session, LinkPair} = init(Config), + {ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName1, #{}), + {ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName2, #{}), + {ok, Sender} = amqp10_client:attach_sender_link(Session, <<"sender">>, Address1), + ok = wait_for_credit(Sender), + + Payload = <<"my message">>, + ok = amqp10_client:send_msg(Sender, amqp10_msg:set_message_annotations( + #{<<"x-cc">> => {list, [{utf8, QName2}]}}, + amqp10_msg:new(<<"tag">>, Payload))), + ok = wait_for_accepted(<<"tag">>), + ok = amqp10_client:detach_link(Sender), + + {ok, Receiver1} = amqp10_client:attach_receiver_link(Session, <<"receiver 1">>, Address1, settled), + {ok, Receiver2} = amqp10_client:attach_receiver_link(Session, <<"receiver 2">>, Address2, settled), + {ok, Msg1} = amqp10_client:get_msg(Receiver1), + {ok, Msg2} = amqp10_client:get_msg(Receiver2), + ?assertEqual([Payload], amqp10_msg:body(Msg1)), + ?assertEqual([Payload], amqp10_msg:body(Msg2)), + + {ok, #{message_count := 0}} = rabbitmq_amqp_client:delete_queue(LinkPair, QName1), + {ok, #{message_count := 0}} = rabbitmq_amqp_client:delete_queue(LinkPair, QName2), + ok = end_session_sync(Session), + ok = amqp10_client:close_connection(Connection). + +%% Test that x-cc routing keys work together with target address 'null' +x_cc_annotation_null(Config) -> + QName1 = <<"queue 1">>, + QName2 = <<"queue 2">>, + QAddress1 = rabbitmq_amqp_address:queue(QName1), + QAddress2 = rabbitmq_amqp_address:queue(QName2), + {Connection, Session, LinkPair} = init(Config), + {ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName1, #{}), + {ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName2, #{}), + ok = rabbitmq_amqp_client:bind_queue(LinkPair, QName1, <<"amq.direct">>, <<"key-1">>, #{}), + ok = rabbitmq_amqp_client:bind_queue(LinkPair, QName2, <<"amq.direct">>, <<"🗝️-2"/utf8>>, #{}), + {ok, Sender} = amqp10_client:attach_sender_link(Session, <<"sender">>, null), + ok = wait_for_credit(Sender), + {ok, Receiver1} = amqp10_client:attach_receiver_link(Session, <<"receiver 1">>, QAddress1, settled), + {ok, Receiver2} = amqp10_client:attach_receiver_link(Session, <<"receiver 2">>, QAddress2, settled), + + Msg1 = amqp10_msg:set_message_annotations( + #{<<"x-cc">> => {list, [{utf8, <<"key-1">>}, + {utf8, <<"key-3">>}]}}, + amqp10_msg:set_properties( + #{to => rabbitmq_amqp_address:exchange(<<"amq.direct">>, <<"🗝️-2"/utf8>>)}, + amqp10_msg:new(<<"t1">>, <<"m1">>))), + ok = amqp10_client:send_msg(Sender, Msg1), + ok = wait_for_accepted(<<"t1">>), + {ok, R1M1} = amqp10_client:get_msg(Receiver1), + {ok, R2M1} = amqp10_client:get_msg(Receiver2), + ?assertEqual([<<"m1">>], amqp10_msg:body(R1M1)), + ?assertEqual([<<"m1">>], amqp10_msg:body(R2M1)), + + Msg2 = amqp10_msg:set_message_annotations( + #{<<"x-cc">> => {list, [{utf8, <<"🗝️-2"/utf8>>}, + {utf8, <<"key-1">>}]}}, + amqp10_msg:set_properties( + #{to => rabbitmq_amqp_address:exchange(<<"amq.direct">>)}, + amqp10_msg:new(<<"t2">>, <<"m2">>))), + ok = amqp10_client:send_msg(Sender, Msg2), + ok = wait_for_accepted(<<"t2">>), + {ok, R1M2} = amqp10_client:get_msg(Receiver1), + {ok, R2M2} = amqp10_client:get_msg(Receiver2), + ?assertEqual([<<"m2">>], amqp10_msg:body(R1M2)), + ?assertEqual([<<"m2">>], amqp10_msg:body(R2M2)), + + Msg3 = amqp10_msg:set_message_annotations( + #{<<"x-cc">> => {list, [{utf8, QName1}]}}, + amqp10_msg:set_properties( + #{to => rabbitmq_amqp_address:queue(QName2)}, + amqp10_msg:new(<<"t3">>, <<"m3">>))), + ok = amqp10_client:send_msg(Sender, Msg3), + ok = wait_for_accepted(<<"t3">>), + {ok, R1M3} = amqp10_client:get_msg(Receiver1), + {ok, R2M3} = amqp10_client:get_msg(Receiver2), + ?assertEqual([<<"m3">>], amqp10_msg:body(R1M3)), + ?assertEqual([<<"m3">>], amqp10_msg:body(R2M3)), + + Msg4 = amqp10_msg:set_message_annotations( + %% We send a symbol instead of utf8.. + #{<<"x-cc">> => {list, [{symbol, QName1}]}}, + amqp10_msg:set_properties( + #{to => rabbitmq_amqp_address:queue(QName2)}, + amqp10_msg:new(<<"t4">>, <<"m4">>))), + ok = amqp10_client:send_msg(Sender, Msg4), + %% "If the source of the link supports the rejected outcome, and the message has not + %% already been settled by the sender, then the routing node MUST reject the message. + %% In this case the error field of rejected MUST contain the error which would have been communicated + %% in the detach which would have be sent if a link to the same address had been attempted." + %% https://docs.oasis-open.org/amqp/anonterm/v1.0/cs01/anonterm-v1.0-cs01.html#doc-routingerrors + receive {amqp10_disposition, {{rejected, Error}, <<"t4">>}} -> + ?assertMatch( + #'v1_0.error'{ + condition = ?V_1_0_AMQP_ERROR_INVALID_FIELD, + description = {utf8, <<"bad value for 'x-cc' message-annotation:", _/binary>>}}, + Error) + after 30000 -> ct:fail({missing_event, ?LINE}) + end, + + ok = amqp10_client:detach_link(Sender), + ok = amqp10_client:detach_link(Receiver1), + ok = amqp10_client:detach_link(Receiver2), + {ok, #{message_count := 0}} = rabbitmq_amqp_client:delete_queue(LinkPair, QName1), + {ok, #{message_count := 0}} = rabbitmq_amqp_client:delete_queue(LinkPair, QName2), + ok = end_session_sync(Session), + ok = amqp10_client:close_connection(Connection). + +bad_x_cc_annotation_exchange(Config) -> + OpnConf = connection_config(Config), + {ok, Connection} = amqp10_client:open_connection(OpnConf), + {ok, Session} = amqp10_client:begin_session(Connection), + + Address = rabbitmq_amqp_address:exchange(<<"amq.direct">>, <<"key-1">>), + {ok, Sender1} = amqp10_client:attach_sender_link(Session, <<"sender 1">>, Address), + ok = wait_for_credit(Sender1), + ok = amqp10_client:send_msg( + Sender1, + amqp10_msg:set_message_annotations( + %% We send an array instead of a list. + #{<<"x-cc">> => {array, utf8, [{utf8, <<"🗝️-2"/utf8>>}]}}, + amqp10_msg:new(<<"t1">>, <<"m1">>))), + ok = wait_for_settlement(<<"t1">>, released), + receive {amqp10_event, {link, Sender1, {detached, Error1}}} -> + ?assertMatch( + #'v1_0.error'{ + condition = ?V_1_0_AMQP_ERROR_INVALID_FIELD, + description = {utf8, <<"bad value for 'x-cc' message-annotation: " + "{array,utf8,[{utf8,<<\"🗝️-2"/utf8, _Rest/binary>>}}, + Error1) + after 30000 -> ct:fail({missing_event, ?LINE}) + end, + + {ok, Sender2} = amqp10_client:attach_sender_link(Session, <<"sender 2">>, Address), + ok = wait_for_credit(Sender2), + ok = amqp10_client:send_msg( + Sender2, + amqp10_msg:set_message_annotations( + %% We include a non-utf8 type in the list. + #{<<"x-cc">> => {list, [{symbol, <<"key-3">>}]}}, + amqp10_msg:new(<<"t2">>, <<"m2">>))), + ok = wait_for_settlement(<<"t2">>, released), + receive {amqp10_event, {link, Sender2, {detached, Error2}}} -> + ?assertEqual( + #'v1_0.error'{ + condition = ?V_1_0_AMQP_ERROR_INVALID_FIELD, + description = {utf8, <<"bad value for 'x-cc' message-annotation: " + "{list,[{symbol,<<\"key-3\">>}]}">>}}, + Error2) + after 30000 -> ct:fail({missing_event, ?LINE}) + end, + + ok = end_session_sync(Session), + ok = amqp10_client:close_connection(Connection). + +%% internal +%% + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) receive_all_messages(Receiver, Accept) -> receive_all_messages0(Receiver, Accept, []). @@ -6004,6 +6476,7 @@ receive_all_messages0(Receiver, Accept, Acc) -> lists:reverse(Acc) end. +<<<<<<< HEAD connection_config(Config) -> connection_config(0, Config). @@ -6024,6 +6497,8 @@ flush(Prefix) -> ok end. +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) open_and_close_connection(Config) -> OpnConf = connection_config(Config), {ok, Connection} = amqp10_client:open_connection(OpnConf), @@ -6032,6 +6507,7 @@ open_and_close_connection(Config) -> end, ok = close_connection_sync(Connection). +<<<<<<< HEAD % before we can send messages we have to wait for credit from the server wait_for_credit(Sender) -> receive @@ -6084,6 +6560,8 @@ wait_for_connection_close(Connection) -> ct:fail({connection_close_timeout, Connection}) end. +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) wait_for_accepted(Tag) -> wait_for_settlement(Tag, accepted). @@ -6096,6 +6574,7 @@ wait_for_settlement(Tag, State) -> ct:fail({settled_timeout, Tag}) end. +<<<<<<< HEAD wait_for_accepts(0) -> ok; wait_for_accepts(N) -> @@ -6106,6 +6585,8 @@ wait_for_accepts(N) -> ct:fail({missing_accepted, N}) end. +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) delete_queue(Session, QName) -> {ok, LinkPair} = rabbitmq_amqp_client:attach_management_link_pair_sync( Session, <<"delete queue">>), @@ -6156,6 +6637,7 @@ count_received_messages0(Receiver, Count) -> Count end. +<<<<<<< HEAD send_messages(Sender, Left, Settled) -> send_messages(Sender, Left, Settled, <<>>). @@ -6182,6 +6664,8 @@ send_messages(Sender, Left, Settled, BodySuffix) -> send_messages(Sender, Left, Settled, BodySuffix) end. +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) send_until_remote_incoming_window_exceeded(Session, Address) -> {ok, Sender} = amqp10_client:attach_sender_link(Session, <<"sender">>, Address, settled), ok = wait_for_credit(Sender), @@ -6216,7 +6700,11 @@ assert_link_credit_runs_out(Sender, Left) -> receive {amqp10_event, {link, Sender, credited}} -> ct:pal("credited with ~b messages left", [Left]), assert_link_credit_runs_out(Sender, Left - 1) +<<<<<<< HEAD after 500 -> +======= + after 30000 -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ct:pal("insufficient link credit with ~b messages left", [Left]), ok end @@ -6331,8 +6819,13 @@ find_event(Type, Props, Events) when is_list(Props), is_list(Events) -> fun(#event{type = EventType, props = EventProps}) -> Type =:= EventType andalso lists:all( +<<<<<<< HEAD fun({Key, _Value}) -> lists:keymember(Key, 1, EventProps) +======= + fun(Prop) -> + lists:member(Prop, EventProps) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end, Props) end, Events). diff --git a/deps/rabbit/test/amqp_filtex_SUITE.erl b/deps/rabbit/test/amqp_filtex_SUITE.erl new file mode 100644 index 000000000000..75abe3357bcd --- /dev/null +++ b/deps/rabbit/test/amqp_filtex_SUITE.erl @@ -0,0 +1,665 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2023 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +%% Test suite for +%% AMQP Filter Expressions Version 1.0 Working Draft 09 +-module(amqp_filtex_SUITE). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("amqp10_common/include/amqp10_filtex.hrl"). +-include_lib("amqp10_common/include/amqp10_framing.hrl"). + +-compile([nowarn_export_all, + export_all]). + +-import(rabbit_ct_broker_helpers, + [rpc/4]). +-import(rabbit_ct_helpers, + [eventually/1]). +-import(amqp_utils, + [init/1, + connection_config/1, + flush/1, + wait_for_credit/1, + wait_for_accepts/1, + send_messages/3, + detach_link_sync/1, + end_session_sync/1, + wait_for_session_end/1, + close_connection_sync/1]). + +all() -> + [ + {group, cluster_size_1} + ]. + +groups() -> + [ + {cluster_size_1, [shuffle], + [ + properties_section, + application_properties_section, + multiple_sections, + filter_few_messages_from_many, + string_modifier + ]} + ]. + +init_per_suite(Config) -> + {ok, _} = application:ensure_all_started(amqp10_client), + rabbit_ct_helpers:log_environment(), + rabbit_ct_helpers:merge_app_env( + Config, {rabbit, [{quorum_tick_interval, 1000}, + {stream_tick_interval, 1000} + ]}). + +end_per_suite(Config) -> + Config. + +init_per_group(_Group, Config) -> + Suffix = rabbit_ct_helpers:testcase_absname(Config, "", "-"), + Config1 = rabbit_ct_helpers:set_config( + Config, [{rmq_nodename_suffix, Suffix}]), + rabbit_ct_helpers:run_setup_steps( + Config1, + rabbit_ct_broker_helpers:setup_steps() ++ + rabbit_ct_client_helpers:setup_steps()). + +end_per_group(_, Config) -> + rabbit_ct_helpers:run_teardown_steps(Config, + rabbit_ct_client_helpers:teardown_steps() ++ + rabbit_ct_broker_helpers:teardown_steps()). + +init_per_testcase(Testcase, Config) -> + rabbit_ct_helpers:testcase_started(Config, Testcase). + +end_per_testcase(Testcase, Config) -> + %% Assert that every testcase cleaned up. + eventually(?_assertEqual([], rpc(Config, rabbit_amqqueue, list, []))), + %% Wait for sessions to terminate before starting the next test case. + eventually(?_assertEqual([], rpc(Config, rabbit_amqp_session, list_local, []))), + rabbit_ct_helpers:testcase_finished(Config, Testcase). + +properties_section(Config) -> + Stream = atom_to_binary(?FUNCTION_NAME), + Address = rabbitmq_amqp_address:queue(Stream), + + OpnConf0 = connection_config(Config), + OpnConf = OpnConf0#{notify_with_performative => true}, + {ok, Connection} = amqp10_client:open_connection(OpnConf), + {ok, Session} = amqp10_client:begin_session_sync(Connection), + {ok, LinkPair} = rabbitmq_amqp_client:attach_management_link_pair_sync(Session, <<"my link pair">>), + {ok, #{}} = rabbitmq_amqp_client:declare_queue( + LinkPair, + Stream, + #{arguments => #{<<"x-queue-type">> => {utf8, <<"stream">>}}}), + {ok, Sender} = amqp10_client:attach_sender_link(Session, <<"sender">>, Address), + ok = wait_for_credit(Sender), + + Now = erlang:system_time(millisecond), + To = rabbitmq_amqp_address:exchange(<<"some exchange">>, <<"routing key">>), + ReplyTo = rabbitmq_amqp_address:queue(<<"some queue">>), + ok = amqp10_client:send_msg( + Sender, + amqp10_msg:set_properties( + #{message_id => {ulong, 999}, + user_id => <<"guest">>, + to => To, + subject => <<"🐇"/utf8>>, + reply_to => ReplyTo, + correlation_id => <<"corr-123">>, + content_type => <<"text/plain">>, + content_encoding => <<"some encoding">>, + absolute_expiry_time => Now + 100_000, + creation_time => Now, + group_id => <<"my group ID">>, + group_sequence => 16#ff_ff_ff_ff, + reply_to_group_id => <<"other group ID">>}, + amqp10_msg:new(<<"t1">>, <<"m1">>))), + ok = amqp10_client:send_msg( + Sender, + amqp10_msg:new(<<"t2">>, <<"m2">>)), + ok = amqp10_client:send_msg( + Sender, + amqp10_msg:set_properties( + #{group_id => <<"my group ID">>}, + amqp10_msg:new(<<"t3">>, <<"m3">>))), + + ok = wait_for_accepts(3), + ok = detach_link_sync(Sender), + flush(sent), + + PropsFilter1 = [ + {{symbol, <<"message-id">>}, {ulong, 999}}, + {{symbol, <<"user-id">>}, {binary, <<"guest">>}}, + {{symbol, <<"subject">>}, {utf8, <<"🐇"/utf8>>}}, + {{symbol, <<"to">>}, {utf8, To}}, + {{symbol, <<"reply-to">>}, {utf8, ReplyTo}}, + {{symbol, <<"correlation-id">>}, {utf8, <<"corr-123">>}}, + {{symbol, <<"content-type">>}, {symbol, <<"text/plain">>}}, + {{symbol, <<"content-encoding">>}, {symbol, <<"some encoding">>}}, + {{symbol, <<"absolute-expiry-time">>}, {timestamp, Now + 100_000}}, + {{symbol, <<"creation-time">>}, {timestamp, Now}}, + {{symbol, <<"group-id">>}, {utf8, <<"my group ID">>}}, + {{symbol, <<"group-sequence">>}, {uint, 16#ff_ff_ff_ff}}, + {{symbol, <<"reply-to-group-id">>}, {utf8, <<"other group ID">>}} + ], + Filter1 = #{<<"rabbitmq:stream-offset-spec">> => <<"first">>, + ?DESCRIPTOR_NAME_PROPERTIES_FILTER => {map, PropsFilter1}}, + {ok, Receiver1} = amqp10_client:attach_receiver_link( + Session, <<"receiver 1">>, Address, + settled, configuration, Filter1), + ok = amqp10_client:flow_link_credit(Receiver1, 10, never), + receive {amqp10_msg, Receiver1, R1M1} -> + ?assertEqual([<<"m1">>], amqp10_msg:body(R1M1)) + after 30000 -> ct:fail({missing_msg, ?LINE}) + end, + ok = assert_no_msg_received(?LINE), + ok = detach_link_sync(Receiver1), + + PropsFilter2 = [{{symbol, <<"group-id">>}, {utf8, <<"my group ID">>}}], + Filter2 = #{<<"rabbitmq:stream-offset-spec">> => <<"first">>, + ?DESCRIPTOR_NAME_PROPERTIES_FILTER => {map, PropsFilter2}}, + {ok, Receiver2} = amqp10_client:attach_receiver_link( + Session, <<"receiver 2">>, Address, + unsettled, configuration, Filter2), + {ok, R2M1} = amqp10_client:get_msg(Receiver2), + {ok, R2M2} = amqp10_client:get_msg(Receiver2), + ok = amqp10_client:accept_msg(Receiver2, R2M1), + ok = amqp10_client:accept_msg(Receiver2, R2M2), + ?assertEqual([<<"m1">>], amqp10_msg:body(R2M1)), + ?assertEqual([<<"m3">>], amqp10_msg:body(R2M2)), + ok = detach_link_sync(Receiver2), + + %% Filter is in place, but no message matches. + PropsFilter3 = [{{symbol, <<"group-id">>}, {utf8, <<"no match">>}}], + Filter3 = #{<<"rabbitmq:stream-offset-spec">> => <<"first">>, + ?DESCRIPTOR_NAME_PROPERTIES_FILTER => {map, PropsFilter3}}, + {ok, Receiver3} = amqp10_client:attach_receiver_link( + Session, <<"receiver 3">>, Address, + unsettled, configuration, Filter3), + receive {amqp10_event, {link, Receiver3, {attached, #'v1_0.attach'{}}}} -> ok + after 30000 -> ct:fail({missing_event, ?LINE}) + end, + ok = amqp10_client:flow_link_credit(Receiver3, 10, never), + ok = assert_no_msg_received(?LINE), + ok = detach_link_sync(Receiver3), + + %% Wrong type should fail validation in the server. + %% RabbitMQ should exclude this filter in its reply attach frame because + %% "the sending endpoint [RabbitMQ] sets the filter actually in place". + %% Hence, no filter expression is actually in place and we should receive all messages. + PropsFilter4 = [{{symbol, <<"group-id">>}, {uint, 3}}], + Filter4 = #{<<"rabbitmq:stream-offset-spec">> => <<"first">>, + ?DESCRIPTOR_NAME_PROPERTIES_FILTER => {map, PropsFilter4}}, + {ok, Receiver4} = amqp10_client:attach_receiver_link( + Session, <<"receiver 4">>, Address, + unsettled, configuration, Filter4), + receive {amqp10_event, + {link, Receiver4, + {attached, #'v1_0.attach'{ + source = #'v1_0.source'{filter = {map, ActualFilter}}}}}} -> + ?assertMatch([{{symbol,<<"rabbitmq:stream-offset-spec">>}, _}], + ActualFilter) + after 30000 -> ct:fail({missing_event, ?LINE}) + end, + {ok, R4M1} = amqp10_client:get_msg(Receiver4), + {ok, R4M2} = amqp10_client:get_msg(Receiver4), + {ok, R4M3} = amqp10_client:get_msg(Receiver4), + ok = amqp10_client:accept_msg(Receiver4, R4M1), + ok = amqp10_client:accept_msg(Receiver4, R4M2), + ok = amqp10_client:accept_msg(Receiver4, R4M3), + ?assertEqual([<<"m1">>], amqp10_msg:body(R4M1)), + ?assertEqual([<<"m2">>], amqp10_msg:body(R4M2)), + ?assertEqual([<<"m3">>], amqp10_msg:body(R4M3)), + ok = detach_link_sync(Receiver4), + + {ok, _} = rabbitmq_amqp_client:delete_queue(LinkPair, Stream), + ok = rabbitmq_amqp_client:detach_management_link_pair_sync(LinkPair), + ok = end_session_sync(Session), + ok = close_connection_sync(Connection). + +application_properties_section(Config) -> + Stream = atom_to_binary(?FUNCTION_NAME), + Address = rabbitmq_amqp_address:queue(Stream), + OpnConf0 = connection_config(Config), + OpnConf = OpnConf0#{notify_with_performative => true}, + {ok, Connection} = amqp10_client:open_connection(OpnConf), + {ok, Session} = amqp10_client:begin_session_sync(Connection), + {ok, LinkPair} = rabbitmq_amqp_client:attach_management_link_pair_sync(Session, <<"my link pair">>), + {ok, #{}} = rabbitmq_amqp_client:declare_queue( + LinkPair, + Stream, + #{arguments => #{<<"x-queue-type">> => {utf8, <<"stream">>}}}), + {ok, Sender} = amqp10_client:attach_sender_link(Session, <<"sender">>, Address), + ok = wait_for_credit(Sender), + + ok = amqp10_client:send_msg( + Sender, + amqp10_msg:set_application_properties( + #{<<"k1">> => -2, + <<"k2">> => 10, + <<"k3">> => false, + <<"k4">> => true, + <<"k5">> => <<"hey">>}, + amqp10_msg:new(<<"t1">>, <<"m1">>))), + ok = amqp10_client:send_msg( + Sender, + amqp10_msg:set_application_properties( + #{<<"k2">> => 10.1}, + amqp10_msg:new(<<"t2">>, <<"m2">>))), + ok = amqp10_client:send_msg( + Sender, + amqp10_msg:new(<<"t3">>, <<"m3">>)), + ok = amqp10_client:send_msg( + Sender, + amqp10_msg:set_application_properties( + #{<<"k2">> => 10.0}, + amqp10_msg:new(<<"t4">>, <<"m4">>))), + + ok = wait_for_accepts(4), + ok = detach_link_sync(Sender), + flush(sent), + + AppPropsFilter0 = [{{utf8, <<"k5">>}, {symbol, <<"no match">>}}], + Filter0 = #{<<"rabbitmq:stream-offset-spec">> => <<"first">>, + ?DESCRIPTOR_NAME_APPLICATION_PROPERTIES_FILTER => {map, AppPropsFilter0}}, + {ok, Receiver0} = amqp10_client:attach_receiver_link( + Session, <<"receiver 0">>, Address, + unsettled, configuration, Filter0), + %% Wait for the attach so the detach command won't fail + receive {amqp10_event, + {link, Receiver0, {attached, #'v1_0.attach'{}}}} -> + ok + after 30000 -> ct:fail({missing_event, ?LINE}) + end, + ok = amqp10_client:flow_link_credit(Receiver0, 10, never), + ok = assert_no_msg_received(?LINE), + ok = detach_link_sync(Receiver0), + + AppPropsFilter1 = [ + {{utf8, <<"k1">>}, {int, -2}}, + {{utf8, <<"k5">>}, {symbol, <<"hey">>}}, + {{utf8, <<"k4">>}, {boolean, true}}, + {{utf8, <<"k3">>}, false} + ], + Filter1 = #{<<"rabbitmq:stream-offset-spec">> => <<"first">>, + ?DESCRIPTOR_NAME_APPLICATION_PROPERTIES_FILTER => {map, AppPropsFilter1}}, + {ok, Receiver1} = amqp10_client:attach_receiver_link( + Session, <<"receiver 1">>, Address, + settled, configuration, Filter1), + receive {amqp10_event, + {link, Receiver1, + {attached, #'v1_0.attach'{ + source = #'v1_0.source'{filter = {map, ActualFilter1}}}}}} -> + ?assertMatch( + {described, _Type, {map, [ + {{utf8, <<"k1">>}, {int, -2}}, + {{utf8, <<"k5">>}, {symbol, <<"hey">>}}, + {{utf8, <<"k4">>}, true}, + {{utf8, <<"k3">>}, false} + ]}}, + proplists:get_value({symbol, ?DESCRIPTOR_NAME_APPLICATION_PROPERTIES_FILTER}, ActualFilter1)) + after 30000 -> ct:fail({missing_event, ?LINE}) + end, + ok = amqp10_client:flow_link_credit(Receiver1, 10, never), + receive {amqp10_msg, Receiver1, R1M1} -> + ?assertEqual([<<"m1">>], amqp10_msg:body(R1M1)) + after 30000 -> ct:fail({missing_msg, ?LINE}) + end, + ok = assert_no_msg_received(?LINE), + ok = detach_link_sync(Receiver1), + + %% Due to simple type matching [filtex-v1.0-wd09 §4.1.1] + %% we expect integer 10 to also match number 10.0. + AppPropsFilter2 = [{{utf8, <<"k2">>}, {uint, 10}}], + Filter2 = #{<<"rabbitmq:stream-offset-spec">> => <<"first">>, + ?DESCRIPTOR_NAME_APPLICATION_PROPERTIES_FILTER => {map, AppPropsFilter2}}, + {ok, Receiver2} = amqp10_client:attach_receiver_link( + Session, <<"receiver 2">>, Address, + unsettled, configuration, Filter2), + {ok, R2M1} = amqp10_client:get_msg(Receiver2), + {ok, R2M2} = amqp10_client:get_msg(Receiver2), + ok = amqp10_client:accept_msg(Receiver2, R2M1), + ok = amqp10_client:accept_msg(Receiver2, R2M2), + ?assertEqual([<<"m1">>], amqp10_msg:body(R2M1)), + ?assertEqual([<<"m4">>], amqp10_msg:body(R2M2)), + ok = detach_link_sync(Receiver2), + + %% A reference field value of NULL should always match. [filtex-v1.0-wd09 §4.1.1] + AppPropsFilter3 = [{{utf8, <<"k2">>}, null}], + Filter3 = #{<<"rabbitmq:stream-offset-spec">> => <<"first">>, + ?DESCRIPTOR_NAME_APPLICATION_PROPERTIES_FILTER => {map, AppPropsFilter3}}, + {ok, Receiver3} = amqp10_client:attach_receiver_link( + Session, <<"receiver 3">>, Address, + unsettled, configuration, Filter3), + {ok, R3M1} = amqp10_client:get_msg(Receiver3), + {ok, R3M2} = amqp10_client:get_msg(Receiver3), + {ok, R3M3} = amqp10_client:get_msg(Receiver3), + ok = amqp10_client:accept_msg(Receiver3, R3M1), + ok = amqp10_client:accept_msg(Receiver3, R3M2), + ok = amqp10_client:accept_msg(Receiver3, R3M3), + ?assertEqual([<<"m1">>], amqp10_msg:body(R3M1)), + ?assertEqual([<<"m2">>], amqp10_msg:body(R3M2)), + ?assertEqual([<<"m4">>], amqp10_msg:body(R3M3)), + ok = detach_link_sync(Receiver3), + + %% Wrong type should fail validation in the server. + %% RabbitMQ should exclude this filter in its reply attach frame because + %% "the sending endpoint [RabbitMQ] sets the filter actually in place". + %% Hence, no filter expression is actually in place and we should receive all messages. + AppPropsFilter4 = [{{symbol, <<"k2">>}, {uint, 10}}], + Filter4 = #{<<"rabbitmq:stream-offset-spec">> => <<"first">>, + ?DESCRIPTOR_NAME_APPLICATION_PROPERTIES_FILTER => {map, AppPropsFilter4}}, + {ok, Receiver4} = amqp10_client:attach_receiver_link( + Session, <<"receiver 4">>, Address, + unsettled, configuration, Filter4), + receive {amqp10_event, + {link, Receiver4, + {attached, #'v1_0.attach'{ + source = #'v1_0.source'{filter = {map, ActualFilter4}}}}}} -> + ?assertMatch([{{symbol,<<"rabbitmq:stream-offset-spec">>}, _}], + ActualFilter4) + after 30000 -> ct:fail({missing_event, ?LINE}) + end, + {ok, R4M1} = amqp10_client:get_msg(Receiver4), + {ok, R4M2} = amqp10_client:get_msg(Receiver4), + {ok, R4M3} = amqp10_client:get_msg(Receiver4), + {ok, R4M4} = amqp10_client:get_msg(Receiver4), + ok = amqp10_client:accept_msg(Receiver4, R4M1), + ok = amqp10_client:accept_msg(Receiver4, R4M2), + ok = amqp10_client:accept_msg(Receiver4, R4M3), + ok = amqp10_client:accept_msg(Receiver4, R4M4), + ?assertEqual([<<"m1">>], amqp10_msg:body(R4M1)), + ?assertEqual([<<"m2">>], amqp10_msg:body(R4M2)), + ?assertEqual([<<"m3">>], amqp10_msg:body(R4M3)), + ?assertEqual([<<"m4">>], amqp10_msg:body(R4M4)), + ok = detach_link_sync(Receiver4), + + {ok, _} = rabbitmq_amqp_client:delete_queue(LinkPair, Stream), + ok = rabbitmq_amqp_client:detach_management_link_pair_sync(LinkPair), + ok = end_session_sync(Session), + ok = close_connection_sync(Connection). + +%% Test filter expressions matching multiple message sections. +multiple_sections(Config) -> + Stream = atom_to_binary(?FUNCTION_NAME), + Address = rabbitmq_amqp_address:queue(Stream), + {Connection, Session, LinkPair} = init(Config), + {ok, #{}} = rabbitmq_amqp_client:declare_queue( + LinkPair, + Stream, + #{arguments => #{<<"x-queue-type">> => {utf8, <<"stream">>}}}), + {ok, Sender} = amqp10_client:attach_sender_link(Session, <<"sender">>, Address), + ok = wait_for_credit(Sender), + + ok = amqp10_client:send_msg( + Sender, + amqp10_msg:set_properties( + #{subject => <<"The Subject">>}, + amqp10_msg:new(<<"t1">>, <<"m1">>))), + ok = amqp10_client:send_msg( + Sender, + amqp10_msg:set_application_properties( + #{<<"The Key">> => -123}, + amqp10_msg:new(<<"t2">>, <<"m2">>))), + ok = amqp10_client:send_msg( + Sender, + amqp10_msg:set_properties( + #{subject => <<"The Subject">>}, + amqp10_msg:set_application_properties( + #{<<"The Key">> => -123}, + amqp10_msg:new(<<"t3">>, <<"m3">>)))), + + ok = wait_for_accepts(3), + ok = detach_link_sync(Sender), + flush(sent), + + PropsFilter = [{{symbol, <<"subject">>}, {utf8, <<"The Subject">>}}], + Filter1 = #{?DESCRIPTOR_NAME_PROPERTIES_FILTER => {map, PropsFilter}, + <<"rabbitmq:stream-offset-spec">> => <<"first">>}, + {ok, Receiver1} = amqp10_client:attach_receiver_link( + Session, <<"receiver 1">>, Address, + unsettled, configuration, Filter1), + {ok, R1M1} = amqp10_client:get_msg(Receiver1), + {ok, R1M3} = amqp10_client:get_msg(Receiver1), + ok = amqp10_client:accept_msg(Receiver1, R1M1), + ok = amqp10_client:accept_msg(Receiver1, R1M3), + ?assertEqual([<<"m1">>], amqp10_msg:body(R1M1)), + ?assertEqual([<<"m3">>], amqp10_msg:body(R1M3)), + ok = detach_link_sync(Receiver1), + + AppPropsFilter = [{{utf8, <<"The Key">>}, {byte, -123}}], + Filter2 = #{?DESCRIPTOR_NAME_APPLICATION_PROPERTIES_FILTER => {map, AppPropsFilter}, + <<"rabbitmq:stream-offset-spec">> => <<"first">>}, + {ok, Receiver2} = amqp10_client:attach_receiver_link( + Session, <<"receiver 2">>, Address, + unsettled, configuration, Filter2), + {ok, R2M2} = amqp10_client:get_msg(Receiver2), + {ok, R2M3} = amqp10_client:get_msg(Receiver2), + ok = amqp10_client:accept_msg(Receiver2, R2M2), + ok = amqp10_client:accept_msg(Receiver2, R2M3), + ?assertEqual([<<"m2">>], amqp10_msg:body(R2M2)), + ?assertEqual([<<"m3">>], amqp10_msg:body(R2M3)), + ok = detach_link_sync(Receiver2), + + Filter3 = #{?DESCRIPTOR_NAME_PROPERTIES_FILTER => {map, PropsFilter}, + ?DESCRIPTOR_NAME_APPLICATION_PROPERTIES_FILTER => {map, AppPropsFilter}, + <<"rabbitmq:stream-offset-spec">> => <<"first">>}, + {ok, Receiver3} = amqp10_client:attach_receiver_link( + Session, <<"receiver 3">>, Address, + unsettled, configuration, Filter3), + {ok, R3M3} = amqp10_client:get_msg(Receiver3), + ok = amqp10_client:accept_msg(Receiver3, R3M3), + ?assertEqual([<<"m3">>], amqp10_msg:body(R3M3)), + ok = detach_link_sync(Receiver3), + + {ok, _} = rabbitmq_amqp_client:delete_queue(LinkPair, Stream), + ok = rabbitmq_amqp_client:detach_management_link_pair_sync(LinkPair), + ok = end_session_sync(Session), + ok = close_connection_sync(Connection). + +%% Filter a small subset from many messages. +%% We test here that flow control still works correctly. +filter_few_messages_from_many(Config) -> + Stream = atom_to_binary(?FUNCTION_NAME), + Address = rabbitmq_amqp_address:queue(Stream), + {Connection, Session, LinkPair} = init(Config), + {ok, #{}} = rabbitmq_amqp_client:declare_queue( + LinkPair, + Stream, + #{arguments => #{<<"x-queue-type">> => {utf8, <<"stream">>}}}), + {ok, Sender} = amqp10_client:attach_sender_link(Session, <<"sender">>, Address), + ok = wait_for_credit(Sender), + + ok = amqp10_client:send_msg( + Sender, + amqp10_msg:set_properties( + #{group_id => <<"my group ID">>}, + amqp10_msg:new(<<"t1">>, <<"first msg">>))), + ok = send_messages(Sender, 1000, false), + ok = amqp10_client:send_msg( + Sender, + amqp10_msg:set_properties( + #{group_id => <<"my group ID">>}, + amqp10_msg:new(<<"t2">>, <<"last msg">>))), + ok = wait_for_accepts(1002), + ok = detach_link_sync(Sender), + flush(sent), + + %% Our filter should cause us to receive only the first and + %% last message out of the 1002 messages in the stream. + PropsFilter = [{{symbol, <<"group-id">>}, {utf8, <<"my group ID">>}}], + Filter = #{<<"rabbitmq:stream-offset-spec">> => <<"first">>, + ?DESCRIPTOR_NAME_PROPERTIES_FILTER => {map, PropsFilter}}, + {ok, Receiver} = amqp10_client:attach_receiver_link( + Session, <<"receiver">>, Address, + unsettled, configuration, Filter), + + ok = amqp10_client:flow_link_credit(Receiver, 2, never), + receive {amqp10_msg, Receiver, M1} -> + ?assertEqual([<<"first msg">>], amqp10_msg:body(M1)), + ok = amqp10_client:accept_msg(Receiver, M1) + after 30000 -> ct:fail({missing_msg, ?LINE}) + end, + receive {amqp10_msg, Receiver, M2} -> + ?assertEqual([<<"last msg">>], amqp10_msg:body(M2)), + ok = amqp10_client:accept_msg(Receiver, M2) + after 30000 -> ct:fail({missing_msg, ?LINE}) + end, + ok = detach_link_sync(Receiver), + + {ok, _} = rabbitmq_amqp_client:delete_queue(LinkPair, Stream), + ok = rabbitmq_amqp_client:detach_management_link_pair_sync(LinkPair), + ok = end_session_sync(Session), + ok = close_connection_sync(Connection). + +string_modifier(Config) -> + Stream = atom_to_binary(?FUNCTION_NAME), + Address = rabbitmq_amqp_address:queue(Stream), + {Connection, Session, LinkPair} = init(Config), + {ok, #{}} = rabbitmq_amqp_client:declare_queue( + LinkPair, + Stream, + #{arguments => #{<<"x-queue-type">> => {utf8, <<"stream">>}}}), + {ok, Sender} = amqp10_client:attach_sender_link(Session, <<"sender">>, Address), + ok = wait_for_credit(Sender), + + ok = amqp10_client:send_msg( + Sender, + amqp10_msg:set_properties( + #{to => <<"abc 1">>, + reply_to => <<"abc 2">>, + subject => <<"abc 3">>, + group_id => <<"abc 4">>, + reply_to_group_id => <<"abc 5">>, + message_id => {utf8, <<"abc 6">>}, + correlation_id => <<"abc 7">>, + group_sequence => 16#ff_ff_ff_ff}, + amqp10_msg:set_application_properties( + #{<<"k1">> => <<"abc 8">>, + <<"k2">> => <<"abc 9">>}, + amqp10_msg:new(<<"t1">>, <<"m1">>)))), + ok = amqp10_client:send_msg( + Sender, + amqp10_msg:set_application_properties( + #{<<"k1">> => <<"abc">>}, + amqp10_msg:new(<<"t2">>, <<"m2">>))), + ok = amqp10_client:send_msg( + Sender, + amqp10_msg:set_properties( + #{subject => <<"&Hello">>, + reply_to_group_id => <<"xyz 5">>}, + amqp10_msg:new(<<"t3">>, <<"m3">>))), + + ok = wait_for_accepts(3), + ok = detach_link_sync(Sender), + flush(sent), + + PropsFilter1 = [ + {{symbol, <<"to">>}, {utf8, <<"&p:abc ">>}}, + {{symbol, <<"reply-to">>}, {utf8, <<"&p:abc">>}}, + {{symbol, <<"subject">>}, {utf8, <<"&p:ab">>}}, + {{symbol, <<"group-id">>}, {utf8, <<"&p:a">>}}, + {{symbol, <<"reply-to-group-id">>}, {utf8, <<"&s:5">>}}, + {{symbol, <<"correlation-id">>}, {utf8, <<"&s:abc 7">>}}, + {{symbol, <<"message-id">>}, {utf8, <<"&p:abc 6">>}} + ], + AppPropsFilter1 = [ + {{utf8, <<"k1">>}, {utf8, <<"&s: 8">>}}, + {{utf8, <<"k2">>}, {utf8, <<"&p:abc ">>}} + ], + Filter1 = #{?DESCRIPTOR_NAME_PROPERTIES_FILTER => {map, PropsFilter1}, + ?DESCRIPTOR_NAME_APPLICATION_PROPERTIES_FILTER => {map, AppPropsFilter1}, + <<"rabbitmq:stream-offset-spec">> => <<"first">>}, + {ok, Receiver1} = amqp10_client:attach_receiver_link( + Session, <<"receiver 1">>, Address, + settled, configuration, Filter1), + ok = amqp10_client:flow_link_credit(Receiver1, 10, never), + receive {amqp10_msg, Receiver1, R1M1} -> + ?assertEqual([<<"m1">>], amqp10_msg:body(R1M1)) + after 30000 -> ct:fail({missing_msg, ?LINE}) + end, + ok = assert_no_msg_received(?LINE), + ok = detach_link_sync(Receiver1), + + %% Same filters as before except for subject which shouldn't match anymore. + PropsFilter2 = lists:keyreplace( + {symbol, <<"subject">>}, 1, PropsFilter1, + {{symbol, <<"subject">>}, {utf8, <<"&s:xxxxxxxxxxxxxx">>}}), + Filter2 = #{?DESCRIPTOR_NAME_PROPERTIES_FILTER => {map, PropsFilter2}, + ?DESCRIPTOR_NAME_APPLICATION_PROPERTIES_FILTER => {map, AppPropsFilter1}, + <<"rabbitmq:stream-offset-spec">> => <<"first">>}, + {ok, Receiver2} = amqp10_client:attach_receiver_link( + Session, <<"receiver 2">>, Address, + settled, configuration, Filter2), + ok = amqp10_client:flow_link_credit(Receiver2, 10, never), + ok = assert_no_msg_received(?LINE), + ok = detach_link_sync(Receiver2), + + PropsFilter3 = [{{symbol, <<"reply-to-group-id">>}, {utf8, <<"&s: 5">>}}], + Filter3 = #{?DESCRIPTOR_NAME_PROPERTIES_FILTER => {map, PropsFilter3}, + <<"rabbitmq:stream-offset-spec">> => <<"first">>}, + {ok, Receiver3} = amqp10_client:attach_receiver_link( + Session, <<"receiver 3">>, Address, + settled, configuration, Filter3), + ok = amqp10_client:flow_link_credit(Receiver3, 10, never), + receive {amqp10_msg, Receiver3, R3M1} -> + ?assertEqual([<<"m1">>], amqp10_msg:body(R3M1)) + after 30000 -> ct:fail({missing_msg, ?LINE}) + end, + receive {amqp10_msg, Receiver3, R3M3} -> + ?assertEqual([<<"m3">>], amqp10_msg:body(R3M3)) + after 30000 -> ct:fail({missing_msg, ?LINE}) + end, + ok = detach_link_sync(Receiver3), + + %% '&&" is the escape prefix for case-sensitive matching of a string starting with ‘&’ + PropsFilter4 = [{{symbol, <<"subject">>}, {utf8, <<"&&Hello">>}}], + Filter4 = #{?DESCRIPTOR_NAME_PROPERTIES_FILTER => {map, PropsFilter4}, + <<"rabbitmq:stream-offset-spec">> => <<"first">>}, + {ok, Receiver4} = amqp10_client:attach_receiver_link( + Session, <<"receiver 4">>, Address, + settled, configuration, Filter4), + {ok, R4M3} = amqp10_client:get_msg(Receiver4), + ?assertEqual([<<"m3">>], amqp10_msg:body(R4M3)), + ok = detach_link_sync(Receiver4), + + %% Starting the reference field value with & is invalid without using a valid modifier + %% prefix is invalid. + %% RabbitMQ should exclude this filter in its reply attach frame because + %% "the sending endpoint [RabbitMQ] sets the filter actually in place". + %% Hence, no filter expression is actually in place and we should receive all messages. + PropsFilter5 = [{{symbol, <<"subject">>}, {utf8, <<"&Hello">>}}], + Filter5 = #{?DESCRIPTOR_NAME_PROPERTIES_FILTER => {map, PropsFilter5}, + <<"rabbitmq:stream-offset-spec">> => <<"first">>}, + {ok, Receiver5} = amqp10_client:attach_receiver_link( + Session, <<"receiver 5">>, Address, + settled, configuration, Filter5), + {ok, R5M1} = amqp10_client:get_msg(Receiver5), + ?assertEqual([<<"m1">>], amqp10_msg:body(R5M1)), + {ok, R5M2} = amqp10_client:get_msg(Receiver5), + ?assertEqual([<<"m2">>], amqp10_msg:body(R5M2)), + {ok, R5M3} = amqp10_client:get_msg(Receiver5), + ?assertEqual([<<"m3">>], amqp10_msg:body(R5M3)), + ok = detach_link_sync(Receiver5), + + {ok, _} = rabbitmq_amqp_client:delete_queue(LinkPair, Stream), + ok = rabbitmq_amqp_client:detach_management_link_pair_sync(LinkPair), + ok = end_session_sync(Session), + ok = close_connection_sync(Connection). + +%% ------------------------------------------------------------------- +%% Helpers +%% ------------------------------------------------------------------- + +assert_no_msg_received(Line) -> + receive {amqp10_msg, _, _} = Msg -> + ct:fail({received_unexpected_msg, Line, Msg}) + after 10 -> + ok + end. diff --git a/deps/rabbit/test/amqp_system_SUITE.erl b/deps/rabbit/test/amqp_system_SUITE.erl index 0b3fcba3d186..c07e7643cc59 100644 --- a/deps/rabbit/test/amqp_system_SUITE.erl +++ b/deps/rabbit/test/amqp_system_SUITE.erl @@ -51,6 +51,14 @@ groups() -> %% Testsuite setup/teardown. %% ------------------------------------------------------------------- +<<<<<<< HEAD +======= +suite() -> + [ + {timetrap, {minutes, 3}} + ]. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) init_per_suite(Config) -> rabbit_ct_helpers:log_environment(), Config. diff --git a/deps/rabbit/test/amqp_utils.erl b/deps/rabbit/test/amqp_utils.erl new file mode 100644 index 000000000000..22865df9192d --- /dev/null +++ b/deps/rabbit/test/amqp_utils.erl @@ -0,0 +1,144 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2023 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(amqp_utils). + +-include_lib("amqp10_common/include/amqp10_framing.hrl"). + +-export([init/1, init/2, + connection_config/1, connection_config/2, + flush/1, + wait_for_credit/1, + wait_for_accepts/1, + send_messages/3, send_messages/4, + detach_link_sync/1, + end_session_sync/1, + wait_for_session_end/1, + close_connection_sync/1]). + +init(Config) -> + init(0, Config). + +init(Node, Config) -> + OpnConf = connection_config(Node, Config), + {ok, Connection} = amqp10_client:open_connection(OpnConf), + {ok, Session} = amqp10_client:begin_session_sync(Connection), + {ok, LinkPair} = rabbitmq_amqp_client:attach_management_link_pair_sync(Session, <<"my link pair">>), + {Connection, Session, LinkPair}. + +connection_config(Config) -> + connection_config(0, Config). + +connection_config(Node, Config) -> + Host = proplists:get_value(rmq_hostname, Config), + Port = rabbit_ct_broker_helpers:get_node_config(Config, Node, tcp_port_amqp), + #{address => Host, + port => Port, + container_id => <<"my container">>, + sasl => {plain, <<"guest">>, <<"guest">>}}. + +flush(Prefix) -> + receive + Msg -> + ct:pal("~p flushed: ~p~n", [Prefix, Msg]), + flush(Prefix) + after 1 -> + ok + end. + +% Before we can send messages we have to wait for credit from the server. +wait_for_credit(Sender) -> + receive + {amqp10_event, {link, Sender, credited}} -> + ok + after 5000 -> + flush("wait_for_credit timed out"), + ct:fail(credited_timeout) + end. + +wait_for_accepts(0) -> + ok; +wait_for_accepts(N) -> + receive + {amqp10_disposition, {accepted, _}} -> + wait_for_accepts(N - 1) + after 5000 -> + ct:fail({missing_accepted, N}) + end. + +send_messages(Sender, Left, Settled) -> + send_messages(Sender, Left, Settled, <<>>). + +send_messages(_, 0, _, _) -> + ok; +send_messages(Sender, Left, Settled, BodySuffix) -> + Bin = integer_to_binary(Left), + Body = <>, + Msg = amqp10_msg:new(Bin, Body, Settled), + case amqp10_client:send_msg(Sender, Msg) of + ok -> + send_messages(Sender, Left - 1, Settled, BodySuffix); + {error, insufficient_credit} -> + ok = wait_for_credit(Sender), + %% The credited event we just processed could have been received some time ago, + %% i.e. we might have 0 credits right now. This happens in the following scenario: + %% 1. We (test case proc) send a message successfully, the client session proc decrements remaining link credit from 1 to 0. + %% 2. The server grants our client session proc new credits. + %% 3. The client session proc sends us (test case proc) a credited event. + %% 4. We didn't even notice that we ran out of credits temporarily. We send the next message, it succeeds, + %% but do not process the credited event in our mailbox. + %% So, we must be defensive here and assume that the next amqp10_client:send/2 call might return {error, insufficient_credit} + %% again causing us then to really wait to receive a credited event (instead of just processing an old credited event). + send_messages(Sender, Left, Settled, BodySuffix) + end. + +detach_link_sync(Link) -> + ok = amqp10_client:detach_link(Link), + ok = wait_for_link_detach(Link). + +wait_for_link_detach(Link) -> + receive + {amqp10_event, {link, Link, {detached, normal}}} -> + flush(?FUNCTION_NAME), + ok; + {amqp10_event, {link, Link, {detached, #'v1_0.detach'{}}}} -> + flush(?FUNCTION_NAME), + ok + after 5000 -> + flush("wait_for_link_detach timed out"), + ct:fail({link_detach_timeout, Link}) + end. + +end_session_sync(Session) + when is_pid(Session) -> + ok = amqp10_client:end_session(Session), + ok = wait_for_session_end(Session). + +wait_for_session_end(Session) -> + receive + {amqp10_event, {session, Session, {ended, _}}} -> + flush(?FUNCTION_NAME), + ok + after 5000 -> + flush("wait_for_session_end timed out"), + ct:fail({session_end_timeout, Session}) + end. + +close_connection_sync(Connection) + when is_pid(Connection) -> + ok = amqp10_client:close_connection(Connection), + ok = wait_for_connection_close(Connection). + +wait_for_connection_close(Connection) -> + receive + {amqp10_event, {connection, Connection, {closed, normal}}} -> + flush(?FUNCTION_NAME), + ok + after 5000 -> + flush("wait_for_connection_close timed out"), + ct:fail({connection_close_timeout, Connection}) + end. diff --git a/deps/rabbit/test/dead_lettering_SUITE.erl b/deps/rabbit/test/dead_lettering_SUITE.erl index 6d0ad63b13d8..000ad4b6eebb 100644 --- a/deps/rabbit/test/dead_lettering_SUITE.erl +++ b/deps/rabbit/test/dead_lettering_SUITE.erl @@ -177,6 +177,7 @@ end_per_group(Group, Config) -> init_per_testcase(T, Config) when T =:= dead_letter_reject_expire_expire orelse T =:= stream -> +<<<<<<< HEAD case rabbit_ct_broker_helpers:enable_feature_flag(Config, message_containers_deaths_v2) of ok -> init_per_testcase0(T, Config); @@ -186,6 +187,13 @@ init_per_testcase(T, Config) %% * stream is known to fail due to https://github.com/rabbitmq/rabbitmq-server/issues/11173 Skip end; +======= + %% With feature flag message_containers_deaths_v2 disabled, test case: + %% * dead_letter_reject_expire_expire is known to fail due to https://github.com/rabbitmq/rabbitmq-server/issues/11159 + %% * stream is known to fail due to https://github.com/rabbitmq/rabbitmq-server/issues/11173 + ok = rabbit_ct_broker_helpers:enable_feature_flag(Config, message_containers_deaths_v2), + init_per_testcase0(T, Config); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) init_per_testcase(Testcase, Config) -> init_per_testcase0(Testcase, Config). @@ -1860,6 +1868,13 @@ stream(Config) -> {timestamp, T2} = rabbit_misc:table_lookup(Death2, <<"time">>), ?assert(T1 < T2), +<<<<<<< HEAD +======= + ?assertEqual({array, [{longstr, <<"cc 1">>}, + {longstr, <<"cc 2">>}]}, + rabbit_misc:table_lookup(Headers, <<"CC">>)), + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ok = rabbit_ct_client_helpers:close_channel(Ch0), ok = rabbit_ct_client_helpers:close_channel(Ch1). diff --git a/deps/rabbit/test/disconnect_detected_during_alarm_SUITE.erl b/deps/rabbit/test/disconnect_detected_during_alarm_SUITE.erl index b44c6de1440f..294b7ac2cc52 100644 --- a/deps/rabbit/test/disconnect_detected_during_alarm_SUITE.erl +++ b/deps/rabbit/test/disconnect_detected_during_alarm_SUITE.erl @@ -96,7 +96,11 @@ disconnect_detected_during_alarm(Config) -> ListConnections = fun() -> +<<<<<<< HEAD rpc:call(A, rabbit_networking, connection_info_all, []) +======= + rpc:call(A, rabbit_networking, connection_info_all, [[state]]) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end, %% We've already disconnected, but blocked connection still should still linger on. diff --git a/deps/rabbit/test/feature_flags_v2_SUITE.erl b/deps/rabbit/test/feature_flags_v2_SUITE.erl index 37e881597153..c1437aa3b46a 100644 --- a/deps/rabbit/test/feature_flags_v2_SUITE.erl +++ b/deps/rabbit/test/feature_flags_v2_SUITE.erl @@ -47,8 +47,15 @@ enable_feature_flag_in_cluster_and_remove_member_concurrently_mfv2/1, enable_feature_flag_with_post_enable/1, failed_enable_feature_flag_with_post_enable/1, +<<<<<<< HEAD have_required_feature_flag_in_cluster_and_add_member_with_it_disabled/1, have_required_feature_flag_in_cluster_and_add_member_without_it/1, +======= + have_soft_required_feature_flag_in_cluster_and_add_member_with_it_disabled/1, + have_soft_required_feature_flag_in_cluster_and_add_member_without_it/1, + have_hard_required_feature_flag_in_cluster_and_add_member_without_it/1, + have_unknown_feature_flag_in_cluster_and_add_member_with_it_enabled/1, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) error_during_migration_after_initial_success/1, controller_waits_for_own_task_to_finish_before_exiting/1, controller_waits_for_remote_task_to_finish_before_exiting/1 @@ -96,8 +103,15 @@ groups() -> enable_feature_flag_in_cluster_and_remove_member_concurrently_mfv2, enable_feature_flag_with_post_enable, failed_enable_feature_flag_with_post_enable, +<<<<<<< HEAD have_required_feature_flag_in_cluster_and_add_member_with_it_disabled, have_required_feature_flag_in_cluster_and_add_member_without_it, +======= + have_soft_required_feature_flag_in_cluster_and_add_member_with_it_disabled, + have_soft_required_feature_flag_in_cluster_and_add_member_without_it, + have_hard_required_feature_flag_in_cluster_and_add_member_without_it, + have_unknown_feature_flag_in_cluster_and_add_member_with_it_enabled, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) error_during_migration_after_initial_success, controller_waits_for_own_task_to_finish_before_exiting, controller_waits_for_remote_task_to_finish_before_exiting @@ -199,7 +213,11 @@ stop_slave_node(Node) -> persistent_term:erase({?MODULE, Node}), ct:pal("- Stopping slave node `~ts`...", [Node]), +<<<<<<< HEAD ok = peer:stop(NodePid) +======= + _ = peer:stop(NodePid) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end. connect_nodes([FirstNode | OtherNodes] = Nodes) -> @@ -1325,7 +1343,11 @@ failed_enable_feature_flag_with_post_enable(Config) -> ok. +<<<<<<< HEAD have_required_feature_flag_in_cluster_and_add_member_with_it_disabled( +======= +have_soft_required_feature_flag_in_cluster_and_add_member_with_it_disabled( +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Config) -> AllNodes = [NewNode | [FirstNode | _] = Nodes] = ?config(nodes, Config), connect_nodes(Nodes), @@ -1408,7 +1430,11 @@ have_required_feature_flag_in_cluster_and_add_member_with_it_disabled( || Node <- AllNodes], ok. +<<<<<<< HEAD have_required_feature_flag_in_cluster_and_add_member_without_it( +======= +have_soft_required_feature_flag_in_cluster_and_add_member_without_it( +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Config) -> AllNodes = [NewNode | [FirstNode | _] = Nodes] = ?config(nodes, Config), connect_nodes(Nodes), @@ -1478,6 +1504,101 @@ have_required_feature_flag_in_cluster_and_add_member_without_it( ok = run_on_node( NewNode, fun() -> +<<<<<<< HEAD +======= + ?assertEqual( + ok, + rabbit_feature_flags:sync_feature_flags_with_cluster( + Nodes, false)), + ok + end, []), + + ct:pal("Checking the feature flag state is unchanged"), + _ = [ok = + run_on_node( + Node, + fun() -> + ?assertEqual( + true, + rabbit_feature_flags:is_enabled(FeatureName)), + ok + end, + []) + || Node <- AllNodes], + ok. + +have_hard_required_feature_flag_in_cluster_and_add_member_without_it( + Config) -> + AllNodes = [NewNode | [FirstNode | _] = Nodes] = ?config(nodes, Config), + connect_nodes(Nodes), + override_running_nodes([NewNode]), + override_running_nodes(Nodes), + + FeatureName = ?FUNCTION_NAME, + FeatureFlags = #{FeatureName => + #{provided_by => rabbit, + stability => stable}}, + RequiredFeatureFlags = #{FeatureName => + #{provided_by => rabbit, + stability => required, + require_level => hard}}, + ?assertEqual(ok, inject_on_nodes([NewNode], FeatureFlags)), + ?assertEqual(ok, inject_on_nodes(Nodes, RequiredFeatureFlags)), + + ct:pal( + "Checking the feature flag is supported and enabled on existing the " + "cluster only"), + ok = run_on_node( + NewNode, + fun() -> + ?assert(rabbit_feature_flags:is_supported(FeatureName)), + ?assertNot(rabbit_feature_flags:is_enabled(FeatureName)), + + DBDir = rabbit_db:dir(), + ok = filelib:ensure_path(DBDir), + SomeFile = filename:join(DBDir, "some-file.db"), + ok = file:write_file(SomeFile, <<>>), + ?assertNot(rabbit_db:is_virgin_node()), + ok + end, + []), + _ = [ok = + run_on_node( + Node, + fun() -> + ?assert(rabbit_feature_flags:is_supported(FeatureName)), + ?assert(rabbit_feature_flags:is_enabled(FeatureName)), + ok + end, + []) + || Node <- Nodes], + + %% Check compatibility between NewNodes and Nodes. + ok = run_on_node( + NewNode, + fun() -> + ?assertEqual( + ok, + rabbit_feature_flags:check_node_compatibility( + FirstNode)), + ok + end, []), + + %% Add node to cluster and synchronize feature flags. + connect_nodes(AllNodes), + override_running_nodes(AllNodes), + ct:pal( + "Synchronizing feature flags in the expanded cluster~n" + "~n" + "NOTE: Error messages about crashed migration functions can be " + "ignored for feature~n" + " flags other than `~ts`~n" + " because they assume they run inside RabbitMQ.", + [FeatureName]), + ok = run_on_node( + NewNode, + fun() -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?assertMatch( {error, {exception, @@ -1506,6 +1627,56 @@ have_required_feature_flag_in_cluster_and_add_member_without_it( || Node <- AllNodes], ok. +<<<<<<< HEAD +======= +have_unknown_feature_flag_in_cluster_and_add_member_with_it_enabled( + Config) -> + [NewNode | [FirstNode | _] = Nodes] = ?config(nodes, Config), + connect_nodes(Nodes), + override_running_nodes([NewNode]), + override_running_nodes(Nodes), + + FeatureName = ?FUNCTION_NAME, + FeatureFlags = #{FeatureName => + #{provided_by => rabbit, + stability => stable}}, + ?assertEqual(ok, inject_on_nodes([NewNode], FeatureFlags)), + + ct:pal( + "Checking the feature flag is unsupported on the cluster but enabled on " + "the standalone node"), + ok = run_on_node( + NewNode, + fun() -> + ?assertEqual(ok, rabbit_feature_flags:enable(FeatureName)), + ?assert(rabbit_feature_flags:is_enabled(FeatureName)), + ok + end, + []), + _ = [ok = + run_on_node( + Node, + fun() -> + ?assertNot(rabbit_feature_flags:is_supported(FeatureName)), + ?assertNot(rabbit_feature_flags:is_enabled(FeatureName)), + ok + end, + []) + || Node <- Nodes], + + %% Check compatibility between NewNodes and Nodes. + ok = run_on_node( + NewNode, + fun() -> + ?assertEqual( + ok, + rabbit_feature_flags:check_node_compatibility( + FirstNode, true)), + ok + end, []), + ok. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) error_during_migration_after_initial_success(Config) -> AllNodes = [NewNode | [FirstNode | _] = Nodes] = ?config(nodes, Config), connect_nodes(Nodes), diff --git a/deps/rabbit/test/mc_unit_SUITE.erl b/deps/rabbit/test/mc_unit_SUITE.erl index acc9ea69adfe..edb621828c63 100644 --- a/deps/rabbit/test/mc_unit_SUITE.erl +++ b/deps/rabbit/test/mc_unit_SUITE.erl @@ -42,7 +42,13 @@ all_tests() -> amqp_amqpl_message_id_binary, amqp_amqpl_unsupported_values_not_converted, amqp_to_amqpl_data_body, +<<<<<<< HEAD amqp_amqpl_amqp_bodies +======= + amqp_amqpl_amqp_bodies, + amqp_x_headers, + amqpl_x_headers +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]. %%%=================================================================== @@ -195,10 +201,14 @@ amqpl_table_x_header_array_of_tbls(_Config) -> [{{symbol, <<"type">>}, {utf8, <<"orange">>}}, {{symbol, <<"count">>}, {long, 45}}]} ]}, +<<<<<<< HEAD mc:x_header(<<"x-fruit">>, Msg)), ok. +======= + mc:x_header(<<"x-fruit">>, Msg)). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) amqpl_death_v1_records(_Config) -> ok = amqpl_death_records(#{?FF_MC_DEATHS_V2 => false}). @@ -314,6 +324,7 @@ amqpl_amqp_bin_amqpl(_Config) -> %% incoming amqpl converted to amqp, serialized / deserialized then converted %% back to amqpl. %% simulates a legacy message published then consumed to a stream +<<<<<<< HEAD Props = #'P_basic'{content_type = <<"text/plain">>, content_encoding = <<"gzip">>, headers = [{<<"a-stream-offset">>, long, 99}, @@ -342,6 +353,39 @@ amqpl_amqp_bin_amqpl(_Config) -> user_id = <<"banana">>, app_id = <<"rmq">> }, +======= + String5k = binary:copy(<<"x">>, 5000), + Props = #'P_basic'{ + content_type = <<"text/plain">>, + content_encoding = <<"gzip">>, + headers = [{<<"a-stream-offset">>, long, 99}, + {<<"a-string">>, longstr, <<"a string">>}, + {<<"a-very-long-string">>, longstr, String5k}, + {<<"a-bool">>, bool, false}, + {<<"a-unsignedbyte">>, unsignedbyte, 1}, + {<<"a-unsignedshort">>, unsignedshort, 1}, + {<<"a-unsignedint">>, unsignedint, 1}, + {<<"a-signedint">>, signedint, 1}, + {<<"a-timestamp">>, timestamp, 1}, + {<<"a-double">>, double, 1.0}, + {<<"a-float">>, float, 1.0}, + {<<"a-void">>, void, undefined}, + {<<"a-binary">>, binary, <<"data">>}, + {<<"a-array">>, array, [{long, 1}, {long, 2}]}, + {<<"x-stream-filter">>, longstr, <<"apple">>} + ], + delivery_mode = 2, + priority = 98, + correlation_id = <<"corr">> , + reply_to = <<"reply-to">>, + expiration = <<"1">>, + message_id = <<"msg-id">>, + timestamp = 99, + type = <<"45">>, + user_id = <<"banana">>, + app_id = <<"rmq">> + }, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Content = #content{properties = Props, payload_fragments_rev = [<<"data">>]}, Msg = mc:init(mc_amqpl, Content, annotations()), @@ -364,8 +408,14 @@ amqpl_amqp_bin_amqpl(_Config) -> Msg10Pre = mc:convert(mc_amqp, Msg), Payload = iolist_to_binary(mc:protocol_state(Msg10Pre)), Msg10 = mc:init(mc_amqp, Payload, #{}), +<<<<<<< HEAD ?assertEqual(<<"exch">>, mc:exchange(Msg10)), ?assertEqual([<<"apple">>], mc:routing_keys(Msg10)), +======= + ?assertMatch(#{<<"x-exchange">> := {utf8, <<"exch">>}, + <<"x-routing-key">> := {utf8, <<"apple">>}}, + mc:x_headers(Msg10)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?assertEqual(98, mc:priority(Msg10)), ?assertEqual(true, mc:is_persistent(Msg10)), ?assertEqual(99000, mc:timestamp(Msg10)), @@ -404,6 +454,12 @@ amqpl_amqp_bin_amqpl(_Config) -> ?assertEqual({long, 99}, Get(<<"a-stream-offset">>, AP10)), ?assertEqual({utf8, <<"a string">>}, Get(<<"a-string">>, AP10)), +<<<<<<< HEAD +======= + %% We expect that a very long string is not scanned for valid UTF-8 + %% and instead directly turned into a binary. + ?assertEqual({binary, String5k}, Get(<<"a-very-long-string">>, AP10)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?assertEqual(false, Get(<<"a-bool">>, AP10)), ?assertEqual({ubyte, 1}, Get(<<"a-unsignedbyte">>, AP10)), ?assertEqual({ushort, 1}, Get(<<"a-unsignedshort">>, AP10)), @@ -422,8 +478,11 @@ amqpl_amqp_bin_amqpl(_Config) -> MsgL2 = mc:convert(mc_amqpl, Msg10), +<<<<<<< HEAD ?assertEqual(<<"exch">>, mc:exchange(MsgL2)), ?assertEqual([<<"apple">>], mc:routing_keys(MsgL2)), +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?assertEqual(98, mc:priority(MsgL2)), ?assertEqual(true, mc:is_persistent(MsgL2)), ?assertEqual(99000, mc:timestamp(MsgL2)), @@ -450,9 +509,23 @@ amqpl_cc_amqp_bin_amqpl(_Config) -> Msg10Pre = mc:convert(mc_amqp, Msg), Sections = iolist_to_binary(mc:protocol_state(Msg10Pre)), Msg10 = mc:init(mc_amqp, Sections, #{}), +<<<<<<< HEAD ?assertEqual(RoutingKeys, mc:routing_keys(Msg10)), MsgL2 = mc:convert(mc_amqpl, Msg10), +======= + ?assertMatch(#{<<"x-exchange">> := {utf8, <<"exch">>}, + <<"x-routing-key">> := {utf8, <<"apple">>}, + <<"x-cc">> := {list, [{utf8, <<"q1">>}, + {utf8, <<"q2">>}]}}, + mc:x_headers(Msg10)), + + %% Here, we simulate what rabbit_stream_queue does: + Msg10b = mc:set_annotation(?ANN_EXCHANGE, <<"exch">>, Msg10), + Msg10c = mc:set_annotation(?ANN_ROUTING_KEYS, [<<"apple">>, <<"q1">>, <<"q2">>], Msg10b), + + MsgL2 = mc:convert(mc_amqpl, Msg10c), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?assertEqual(RoutingKeys, mc:routing_keys(MsgL2)), ?assertMatch(#content{properties = #'P_basic'{headers = Headers}}, mc:protocol_state(MsgL2)). @@ -751,6 +824,55 @@ amqp_amqpl_amqp_bodies(_Config) -> end || Body <- Bodies], ok. +<<<<<<< HEAD +======= +amqp_x_headers(_Config) -> + MAC = [ + {{symbol, <<"x-stream-filter">>}, {utf8, <<"apple">>}}, + thead2('x-list', list, [utf8(<<"l">>)]), + thead2('x-map', map, [{utf8(<<"k">>), utf8(<<"v">>)}]) + ], + M = #'v1_0.message_annotations'{content = MAC}, + AC = [thead(long, 5)], + A = #'v1_0.application_properties'{content = AC}, + D = #'v1_0.data'{content = <<"data">>}, + + Payload = serialize_sections([M, A, D]), + Msg0 = mc:init(mc_amqp, Payload, annotations()), + Msg1 = mc:set_annotation(<<"x-1">>, {byte, -2}, Msg0), + ?assertEqual(#{<<"x-1">> => {byte, -2}, + <<"x-list">> => {list,[{utf8,<<"l">>}]}, + <<"x-map">> => {map,[{{utf8,<<"k">>},{utf8,<<"v">>}}]}, + <<"x-stream-filter">> => {utf8,<<"apple">>}}, + mc:x_headers(Msg1)). + +amqpl_x_headers(_Config) -> + Props = #'P_basic'{headers = [{<<"a-string">>, longstr, <<"a string">>}, + {<<"x-1">>, binary, <<"v1">>}, + {<<"x-stream-filter">>, longstr, <<"apple">>}]}, + Payload = [<<"data">>], + Content = #content{properties = Props, + payload_fragments_rev = Payload}, + + Msg0 = mc:init(mc_amqpl, Content, annotations()), + Msg1 = mc:set_annotation(delivery_count, 1, Msg0), + Msg = mc:set_annotation(<<"x-delivery-count">>, 2, Msg1), + ?assertEqual(#{<<"x-1">> => {binary, <<"v1">>}, + <<"x-stream-filter">> => {utf8,<<"apple">>}, + <<"x-delivery-count">> => {long, 2}}, + mc:x_headers(Msg)), + + XName = <<"exch">>, + RoutingKey = <<"apple">>, + {ok, BasicMsg0} = rabbit_basic:message_no_id(XName, RoutingKey, Content), + BasicMsg1 = mc:set_annotation(delivery_count, 1, BasicMsg0), + BasicMsg = mc:set_annotation(<<"x-delivery-count">>, 2, BasicMsg1), + ?assertEqual(#{<<"x-1">> => {binary, <<"v1">>}, + <<"x-stream-filter">> => {utf8,<<"apple">>}, + <<"x-delivery-count">> => {long, 2}}, + mc:x_headers(BasicMsg)). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% Utility amqp10_encode_bin(L) when is_list(L) -> diff --git a/deps/rabbit/test/msg_size_metrics_SUITE.erl b/deps/rabbit/test/msg_size_metrics_SUITE.erl new file mode 100644 index 000000000000..0b33ecf1a36b --- /dev/null +++ b/deps/rabbit/test/msg_size_metrics_SUITE.erl @@ -0,0 +1,154 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(msg_size_metrics_SUITE). + +-compile([export_all, nowarn_export_all]). +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +-import(rabbit_ct_broker_helpers, + [rpc/4]). + +all() -> + [ + {group, tests} + ]. + +groups() -> + [ + {tests, [shuffle], + [message_size, + over_max_message_size]} + ]. + +%% ------------------------------------------------------------------- +%% Testsuite setup/teardown. +%% ------------------------------------------------------------------- + +init_per_suite(Config) -> + {ok, _} = application:ensure_all_started(amqp10_client), + rabbit_ct_helpers:log_environment(), + rabbit_ct_helpers:run_setup_steps(Config). + +end_per_suite(Config) -> + rabbit_ct_helpers:run_teardown_steps(Config). + +init_per_group(_Group, Config) -> + rabbit_ct_helpers:run_steps( + Config, + rabbit_ct_broker_helpers:setup_steps() ++ + rabbit_ct_client_helpers:setup_steps()). + +end_per_group(_Group, Config) -> + rabbit_ct_helpers:run_steps( + Config, + rabbit_ct_client_helpers:teardown_steps() ++ + rabbit_ct_broker_helpers:teardown_steps()). + +init_per_testcase(Testcase, Config) -> + rabbit_ct_helpers:testcase_started(Config, Testcase). + +end_per_testcase(Testcase, Config) -> + rabbit_ct_helpers:testcase_finished(Config, Testcase). + +%% ------------------------------------------------------------------- +%% Test cases +%% ------------------------------------------------------------------- + +message_size(Config) -> + AmqplBefore = get_msg_size_metrics(amqp091, Config), + AmqpBefore = get_msg_size_metrics(amqp10, Config), + + Binary2B = <<"12">>, + Binary200K = binary:copy(<<"x">>, 200_000), + Payloads = [Binary2B, Binary200K, Binary2B], + + {AmqplConn, Ch} = rabbit_ct_client_helpers:open_connection_and_channel(Config), + [amqp_channel:call(Ch, + #'basic.publish'{routing_key = <<"nowhere">>}, + #amqp_msg{payload = Payload}) + || Payload <- Payloads], + + OpnConf = connection_config(Config), + {ok, Connection} = amqp10_client:open_connection(OpnConf), + {ok, Session} = amqp10_client:begin_session_sync(Connection), + Address = rabbitmq_amqp_address:exchange(<<"amq.fanout">>), + {ok, Sender} = amqp10_client:attach_sender_link_sync(Session, <<"sender">>, Address), + receive {amqp10_event, {link, Sender, credited}} -> ok + after 5000 -> ct:fail(credited_timeout) + end, + + ok = amqp10_client:send_msg(Sender, amqp10_msg:new(<<"tag1">>, Binary2B)), + ok = amqp10_client:send_msg(Sender, amqp10_msg:new(<<"tag2">>, Binary200K)), + ok = amqp10_client:send_msg(Sender, amqp10_msg:new(<<"tag3">>, Binary2B)), + + ok = wait_for_settlement(released, <<"tag1">>), + ok = wait_for_settlement(released, <<"tag2">>), + ok = wait_for_settlement(released, <<"tag3">>), + + AmqplAfter = get_msg_size_metrics(amqp091, Config), + AmqpAfter = get_msg_size_metrics(amqp10, Config), + + ExpectedDiff = [{100, 2}, + {1_000_000, 1}], + ?assertEqual(ExpectedDiff, + rabbit_msg_size_metrics:diff_raw_buckets(AmqplAfter, AmqplBefore)), + ?assertEqual(ExpectedDiff, + rabbit_msg_size_metrics:diff_raw_buckets(AmqpAfter, AmqpBefore)), + + ok = amqp10_client:close_connection(Connection), + ok = rabbit_ct_client_helpers:close_connection_and_channel(AmqplConn, Ch). + +over_max_message_size(Config) -> + DefaultMaxMessageSize = rpc(Config, persistent_term, get, [max_message_size]), + %% Limit the server to only accept messages up to 2KB. + MaxMessageSize = 2_000, + ok = rpc(Config, persistent_term, put, [max_message_size, MaxMessageSize]), + + Before = get_msg_size_metrics(amqp091, Config), + {Conn, Ch} = rabbit_ct_client_helpers:open_connection_and_channel(Config, 0), + MonitorRef = erlang:monitor(process, Ch), + MessageTooLarge = binary:copy(<<"x">>, MaxMessageSize + 1), + amqp_channel:call(Ch, + #'basic.publish'{routing_key = <<"none">>}, + #amqp_msg{payload = MessageTooLarge}), + receive {'DOWN', MonitorRef, process, Ch, Info} -> + ?assertEqual({shutdown, + {server_initiated_close, + 406, + <<"PRECONDITION_FAILED - message size 2001 is larger than configured max size 2000">>}}, + Info) + after 2000 -> ct:fail(expected_channel_closed) + end, + + After = get_msg_size_metrics(amqp091, Config), + %% No metrics should be increased if client sent message that is too large. + ?assertEqual(Before, After), + + ok = rabbit_ct_client_helpers:close_connection(Conn), + ok = rpc(Config, persistent_term, put, [max_message_size, DefaultMaxMessageSize]). + +get_msg_size_metrics(Protocol, Config) -> + rpc(Config, rabbit_msg_size_metrics, raw_buckets, [Protocol]). + +connection_config(Config) -> + Host = ?config(rmq_hostname, Config), + Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp), + #{address => Host, + port => Port, + container_id => <<"my container">>, + sasl => anon}. + +wait_for_settlement(State, Tag) -> + receive + {amqp10_disposition, {State, Tag}} -> + ok + after 5000 -> + ct:fail({disposition_timeout, Tag}) + end. diff --git a/deps/rabbit/test/quorum_queue_SUITE.erl b/deps/rabbit/test/quorum_queue_SUITE.erl index dd3f2f50ce0d..f5992fd9c304 100644 --- a/deps/rabbit/test/quorum_queue_SUITE.erl +++ b/deps/rabbit/test/quorum_queue_SUITE.erl @@ -322,8 +322,11 @@ init_per_testcase(Testcase, Config) -> {skip, "reclaim_memory_with_wrong_queue_type isn't mixed versions compatible"}; peek_with_wrong_queue_type when IsMixed -> {skip, "peek_with_wrong_queue_type isn't mixed versions compatible"}; +<<<<<<< HEAD subscribe_redelivery_limit_disable when IsMixed -> {skip, "subscribe_redelivery_limit_disable isn't mixed versions compatible"}; +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) _ -> Config1 = rabbit_ct_helpers:testcase_started(Config, Testcase), rabbit_ct_broker_helpers:rpc(Config, 0, ?MODULE, delete_queues, []), @@ -1471,14 +1474,22 @@ gh_12635(Config) -> rabbit_ct_broker_helpers:get_node_configs(Config, nodename), ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, +<<<<<<< HEAD [rabbit, quorum_min_checkpoint_interval, 1]), +======= + [rabbit, quorum_min_checkpoint_interval, 1]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Ch0 = rabbit_ct_client_helpers:open_channel(Config, Server0), #'confirm.select_ok'{} = amqp_channel:call(Ch0, #'confirm.select'{}), QQ = ?config(queue_name, Config), RaName = ra_name(QQ), ?assertEqual({'queue.declare_ok', QQ, 0, 0}, +<<<<<<< HEAD declare(Ch0, QQ, [{<<"x-queue-type">>, longstr, <<"quorum">>}])), +======= + declare(Ch0, QQ, [{<<"x-queue-type">>, longstr, <<"quorum">>}])), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% stop member to simulate slow or down member ok = rpc:call(Server2, ra, stop_server, [quorum_queues, {RaName, Server2}]), @@ -1489,10 +1500,17 @@ gh_12635(Config) -> %% force a checkpoint on leader ok = rpc:call(Server0, ra, cast_aux_command, [{RaName, Server0}, force_checkpoint]), rabbit_ct_helpers:await_condition( +<<<<<<< HEAD fun () -> {ok, #{log := Log}, _} = rpc:call(Server0, ra, member_overview, [{RaName, Server0}]), undefined =/= maps:get(latest_checkpoint_index, Log) end), +======= + fun () -> + {ok, #{log := Log}, _} = rpc:call(Server0, ra, member_overview, [{RaName, Server0}]), + undefined =/= maps:get(latest_checkpoint_index, Log) + end), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% publish 1 more message publish_confirm(Ch0, QQ), @@ -1508,10 +1526,17 @@ gh_12635(Config) -> #'queue.purge_ok'{} = amqp_channel:call(Ch0, #'queue.purge'{queue = QQ}), rabbit_ct_helpers:await_condition( +<<<<<<< HEAD fun () -> {ok, #{log := Log}, _} = rpc:call(Server0, ra, member_overview, [{RaName, Server0}]), undefined =/= maps:get(snapshot_index, Log) end), +======= + fun () -> + {ok, #{log := Log}, _} = rpc:call(Server0, ra, member_overview, [{RaName, Server0}]), + undefined =/= maps:get(snapshot_index, Log) + end), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% restart the down member ok = rpc:call(Server2, ra, restart_server, [quorum_queues, {RaName, Server2}]), Pid2 = rpc:call(Server2, erlang, whereis, [RaName]), @@ -1521,12 +1546,19 @@ gh_12635(Config) -> {'DOWN',Ref, process,_, _} -> ct:fail("unexpected DOWN") after 500 -> +<<<<<<< HEAD ok +======= + ok +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end, flush(1), ok. +<<<<<<< HEAD +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) priority_queue_fifo(Config) -> %% testing: if hi priority messages are published before lo priority %% messages they are always consumed first (fifo) diff --git a/deps/rabbit/test/rabbit_db_binding_SUITE.erl b/deps/rabbit/test/rabbit_db_binding_SUITE.erl index 9055e4ff1ddb..8ccd1a61d0d7 100644 --- a/deps/rabbit/test/rabbit_db_binding_SUITE.erl +++ b/deps/rabbit/test/rabbit_db_binding_SUITE.erl @@ -131,8 +131,13 @@ delete1(_Config) -> Ret = rabbit_db_binding:delete(Binding, fun(_, _) -> ok end), ?assertMatch({ok, _}, Ret), {ok, Deletions} = Ret, +<<<<<<< HEAD ?assertMatch({#exchange{}, not_deleted, [#binding{}], none}, dict:fetch(XName1, Deletions)), +======= + ?assertMatch({#exchange{}, not_deleted, [#binding{}]}, + rabbit_binding:fetch_deletion(XName1, Deletions)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?assertEqual(false, rabbit_db_binding:exists(Binding)), passed. @@ -152,8 +157,13 @@ auto_delete1(_Config) -> Ret = rabbit_db_binding:delete(Binding, fun(_, _) -> ok end), ?assertMatch({ok, _}, Ret), {ok, Deletions} = Ret, +<<<<<<< HEAD ?assertMatch({#exchange{}, deleted, [#binding{}], none}, dict:fetch(XName1, Deletions)), +======= + ?assertMatch({#exchange{}, not_deleted, [#binding{}]}, + rabbit_binding:fetch_deletion(XName1, Deletions)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?assertEqual(false, rabbit_db_binding:exists(Binding)), passed. diff --git a/deps/rabbit/test/rabbit_db_queue_SUITE.erl b/deps/rabbit/test/rabbit_db_queue_SUITE.erl index f66e8fd236c9..dd19aeb50d8a 100644 --- a/deps/rabbit/test/rabbit_db_queue_SUITE.erl +++ b/deps/rabbit/test/rabbit_db_queue_SUITE.erl @@ -292,8 +292,13 @@ delete1(_Config) -> ?assertEqual({ok, Q}, rabbit_db_queue:get(QName)), %% TODO Can we handle the deletions outside of rabbit_db_queue? Probably not because %% they should be done in a single transaction, but what a horrid API to have! +<<<<<<< HEAD Dict = rabbit_db_queue:delete(QName, normal), ?assertEqual(0, dict:size(Dict)), +======= + Deletions = rabbit_db_queue:delete(QName, normal), + ?assertEqual(rabbit_binding:new_deletions(), Deletions), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?assertEqual(ok, rabbit_db_queue:delete(QName, normal)), ?assertEqual({error, not_found}, rabbit_db_queue:get(QName)), passed. diff --git a/deps/rabbit/test/topic_permission_SUITE.erl b/deps/rabbit/test/topic_permission_SUITE.erl index 2849b76fd3b9..a0c551e2a7c2 100644 --- a/deps/rabbit/test/topic_permission_SUITE.erl +++ b/deps/rabbit/test/topic_permission_SUITE.erl @@ -8,6 +8,10 @@ -module(topic_permission_SUITE). -include_lib("eunit/include/eunit.hrl"). +<<<<<<< HEAD +======= +-include_lib("amqp10_common/include/amqp10_framing.hrl"). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -include_lib("amqp_client/include/amqp_client.hrl"). -compile([export_all, nowarn_export_all]). @@ -21,6 +25,10 @@ groups() -> [ {sequential_tests, [], [ +<<<<<<< HEAD +======= + amqp_x_cc_annotation, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) amqpl_cc_headers, amqpl_bcc_headers, topic_permission_database_access, @@ -29,6 +37,10 @@ groups() -> ]. init_per_suite(Config) -> +<<<<<<< HEAD +======= + {ok, _} = application:ensure_all_started(amqp10_client), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_helpers:log_environment(), Config1 = rabbit_ct_helpers:set_config( Config, @@ -56,6 +68,94 @@ init_per_testcase(Testcase, Config) -> end_per_testcase(Testcase, Config) -> rabbit_ct_helpers:testcase_finished(Config, Testcase). +<<<<<<< HEAD +======= +amqp_x_cc_annotation(Config) -> + ok = set_topic_permissions(Config, "^a", ".*"), + + QName1 = <<"queue 1">>, + QName2 = <<"queue 2">>, + {Connection, Session1, LinkPair} = amqp_utils:init(Config), + {ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName1, #{}), + {ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName2, #{}), + ok = rabbitmq_amqp_client:bind_queue(LinkPair, QName1, <<"amq.topic">>, <<"a.1">>, #{}), + ok = rabbitmq_amqp_client:bind_queue(LinkPair, QName2, <<"amq.topic">>, <<"a.2">>, #{}), + + {ok, Sender1} = amqp10_client:attach_sender_link( + Session1, + <<"sender 1">>, + rabbitmq_amqp_address:exchange(<<"amq.topic">>, <<"a.1">>)), + ok = amqp_utils:wait_for_credit(Sender1), + {ok, Receiver1} = amqp10_client:attach_receiver_link( + Session1, <<"receiver 1">>, rabbitmq_amqp_address:queue(QName1), settled), + {ok, Receiver2} = amqp10_client:attach_receiver_link( + Session1, <<"receiver 2">>, rabbitmq_amqp_address:queue(QName2), settled), + %% We have permissions to send to both topics. + %% Therefore, m1 should be sent to both queues. + ok = amqp10_client:send_msg(Sender1, amqp10_msg:set_message_annotations( + #{<<"x-cc">> => {list, [{utf8, <<"a.2">>}]}}, + amqp10_msg:new(<<"t1">>, <<"m1">>, true))), + {ok, Msg1} = amqp10_client:get_msg(Receiver1), + {ok, Msg2} = amqp10_client:get_msg(Receiver2), + ?assertEqual([<<"m1">>], amqp10_msg:body(Msg1)), + ?assertEqual([<<"m1">>], amqp10_msg:body(Msg2)), + ok = amqp_utils:detach_link_sync(Sender1), + ok = amqp_utils:detach_link_sync(Receiver1), + ok = amqp_utils:detach_link_sync(Receiver2), + + {ok, Session2} = amqp10_client:begin_session_sync(Connection), + {ok, Sender2} = amqp10_client:attach_sender_link( + Session2, + <<"sender 2">>, + rabbitmq_amqp_address:exchange(<<"amq.topic">>, <<"x.1">>)), + ok = amqp_utils:wait_for_credit(Sender2), + ok = amqp10_client:send_msg(Sender2, amqp10_msg:set_message_annotations( + #{<<"x-cc">> => {list, [{utf8, <<"a.2">>}]}}, + amqp10_msg:new(<<"t2">>, <<"m2">>, true))), + receive + {amqp10_event, + {session, Session2, + {ended, + #'v1_0.error'{ + condition = ?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, + description = {utf8, Description1}}}}} -> + ?assertEqual( + <<"write access to topic 'x.1' in exchange 'amq.topic' in vhost '/' refused for user 'guest'">>, + Description1) + after 5000 -> amqp_utils:flush(missing_ended), + ct:fail({missing_event, ?LINE}) + end, + + {ok, Session3} = amqp10_client:begin_session_sync(Connection), + {ok, Sender3} = amqp10_client:attach_sender_link( + Session3, + <<"sender 3">>, + rabbitmq_amqp_address:exchange(<<"amq.topic">>, <<"a.1">>)), + ok = amqp_utils:wait_for_credit(Sender3), + ok = amqp10_client:send_msg(Sender3, amqp10_msg:set_message_annotations( + #{<<"x-cc">> => {list, [{utf8, <<"x.2">>}]}}, + amqp10_msg:new(<<"t3">>, <<"m3">>, true))), + receive + {amqp10_event, + {session, Session3, + {ended, + #'v1_0.error'{ + condition = ?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, + description = {utf8, Description2}}}}} -> + ?assertEqual( + <<"write access to topic 'x.2' in exchange 'amq.topic' in vhost '/' refused for user 'guest'">>, + Description2) + after 5000 -> amqp_utils:flush(missing_ended), + ct:fail({missing_event, ?LINE}) + end, + + {ok, #{message_count := 0}} = rabbitmq_amqp_client:delete_queue(LinkPair, QName1), + {ok, #{message_count := 0}} = rabbitmq_amqp_client:delete_queue(LinkPair, QName2), + ok = amqp_utils:end_session_sync(Session1), + ok = amqp10_client:close_connection(Connection), + ok = clear_topic_permissions(Config). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) amqpl_cc_headers(Config) -> amqpl_headers(<<"CC">>, Config). diff --git a/deps/rabbit/test/unit_msg_size_metrics_SUITE.erl b/deps/rabbit/test/unit_msg_size_metrics_SUITE.erl new file mode 100644 index 000000000000..cd496932cd92 --- /dev/null +++ b/deps/rabbit/test/unit_msg_size_metrics_SUITE.erl @@ -0,0 +1,64 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(unit_msg_size_metrics_SUITE). + +-include_lib("stdlib/include/assert.hrl"). + +-compile([nowarn_export_all, export_all]). + +all() -> + [ + {group, tests} + ]. + +groups() -> + [ + {tests, [], + [ + prometheus_format + ]} + ]. + +%% ------------------------------------------------------------------- +%% Testsuite setup/teardown. +%% ------------------------------------------------------------------- + +init_per_suite(Config) -> + ok = rabbit_msg_size_metrics:init(fake_protocol), + Config. + +end_per_suite(Config) -> + ok = rabbit_msg_size_metrics:cleanup(fake_protocol), + Config. + +%% ------------------------------------------------------------------- +%% Testcases. +%% ------------------------------------------------------------------- + +prometheus_format(_Config) -> + MsgSizes = [1, 100, 1_000_000_000, 99_000_000, 15_000, 15_000], + [ok = rabbit_msg_size_metrics:observe(fake_protocol, MsgSize) || MsgSize <- MsgSizes], + + ?assertEqual( + #{message_size_bytes => + #{type => histogram, + help => "Size of messages received from publishers", + values => [{ + [{protocol, fake_protocol}], + [{100, 2}, + {1_000, 2}, + {10_000, 2}, + {100_000, 4}, + {1_000_000, 4}, + {10_000_000, 4}, + {50_000_000, 4}, + {100_000_000, 5}, + {infinity, 6}], + length(MsgSizes), + lists:sum(MsgSizes)}]}}, + rabbit_msg_size_metrics:prometheus_format()). diff --git a/deps/rabbit_common/mk/rabbitmq-early-plugin.mk b/deps/rabbit_common/mk/rabbitmq-early-plugin.mk index 1b8aaa3f422a..35308e255729 100644 --- a/deps/rabbit_common/mk/rabbitmq-early-plugin.mk +++ b/deps/rabbit_common/mk/rabbitmq-early-plugin.mk @@ -4,7 +4,11 @@ DIALYZER_OPTS ?= -Werror_handling -Wunmatched_returns -Wunknown +<<<<<<< HEAD dialyze: ERL_LIBS = $(APPS_DIR):$(DEPS_DIR):$(DEPS_DIR)/rabbitmq_cli/_build/dev/lib:$(dir $(shell elixir --eval ":io.format '~s~n', [:code.lib_dir :elixir ]")) +======= +dialyze: ERL_LIBS = $(APPS_DIR):$(DEPS_DIR):$(DEPS_DIR)/rabbitmq_cli/_build/dev/lib:$(dir $(shell elixir --eval ':io.format "~s~n", [:code.lib_dir :elixir ]')) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) # -------------------------------------------------------------------- # Common Test flags. diff --git a/deps/rabbit_common/src/rabbit_core_metrics.erl b/deps/rabbit_common/src/rabbit_core_metrics.erl index 8b5430076f53..4f3928165845 100644 --- a/deps/rabbit_common/src/rabbit_core_metrics.erl +++ b/deps/rabbit_common/src/rabbit_core_metrics.erl @@ -141,9 +141,15 @@ connection_stats(Pid, Infos) -> ets:insert(connection_metrics, {Pid, Infos}), ok. +<<<<<<< HEAD connection_stats(Pid, Recv_oct, Send_oct, Reductions) -> %% Includes delete marker ets:insert(connection_coarse_metrics, {Pid, Recv_oct, Send_oct, Reductions, 0}), +======= +connection_stats(Pid, RecvOct, SendOct, Reductions) -> + %% Includes delete marker + ets:insert(connection_coarse_metrics, {Pid, RecvOct, SendOct, Reductions, 0}), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ok. channel_created(Pid, Infos) -> diff --git a/deps/rabbit_common/src/rabbit_env.erl b/deps/rabbit_common/src/rabbit_env.erl index 4f222ab707f4..cd9fb20c4592 100644 --- a/deps/rabbit_common/src/rabbit_env.erl +++ b/deps/rabbit_common/src/rabbit_env.erl @@ -65,7 +65,10 @@ "RABBITMQ_KEEP_PID_FILE_ON_EXIT", "RABBITMQ_LOG", "RABBITMQ_LOG_BASE", +<<<<<<< HEAD "RABBITMQ_LOG_FF_REGISTRY", +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "RABBITMQ_LOGS", "RABBITMQ_MNESIA_BASE", "RABBITMQ_MNESIA_DIR", @@ -150,7 +153,10 @@ get_context_after_reloading_env(Context) -> fun keep_pid_file_on_exit/1, fun feature_flags_file/1, fun forced_feature_flags_on_init/1, +<<<<<<< HEAD fun log_feature_flags_registry/1, +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) fun plugins_path/1, fun plugins_expand_dir/1, fun enabled_plugins_file/1, @@ -999,6 +1005,7 @@ forced_feature_flags_on_init(Context) -> case Value of false -> %% get_prefixed_env_var() considers an empty string +<<<<<<< HEAD %% is the same as an undefined environment variable. update_context(Context, forced_feature_flags_on_init, undefined, default); @@ -1017,6 +1024,17 @@ log_feature_flags_registry(Context) -> Log = value_is_yes(Value), update_context(Context, log_feature_flags_registry, Log, environment) +======= + %% as an undefined environment variable. + update_context( + Context, + forced_feature_flags_on_init, undefined, default); + _ -> + FeatureNames = string:lexemes(Value, ","), + update_context( + Context, + forced_feature_flags_on_init, FeatureNames, environment) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end. %% ------------------------------------------------------------------- diff --git a/deps/rabbit_common/src/rabbit_event.erl b/deps/rabbit_common/src/rabbit_event.erl index ac584ed0819f..5ba5895e44b8 100644 --- a/deps/rabbit_common/src/rabbit_event.erl +++ b/deps/rabbit_common/src/rabbit_event.erl @@ -10,7 +10,11 @@ -include("rabbit.hrl"). -export([start_link/0]). +<<<<<<< HEAD -export([init_stats_timer/2, init_disabled_stats_timer/2, +======= +-export([init_stats_timer/0, init_stats_timer/2, init_disabled_stats_timer/2, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ensure_stats_timer/3, stop_stats_timer/2, reset_stats_timer/2]). -export([stats_level/2, if_enabled/3]). -export([notify/2, notify/3, notify_if/3]). @@ -89,6 +93,7 @@ start_link() -> %% Nowadays, instead of sending a message to rabbit_event via notify(stats), %% some stat-emitting objects update ETS tables directly via module rabbit_core_metrics. +<<<<<<< HEAD init_stats_timer(C, P) -> %% If the rabbit app is not loaded - use default none:5000 StatsLevel = application:get_env(rabbit, collect_statistics, none), @@ -106,6 +111,36 @@ ensure_stats_timer(C, P, Msg) -> TRef = erlang:send_after(Interval, self(), Msg), setelement(P, C, State#state{timer = TRef}); #state{} -> +======= +-spec init_stats_timer() -> state(). +init_stats_timer() -> + %% If the rabbit app is not loaded - use default none:5000 + StatsLevel = application:get_env(rabbit, collect_statistics, none), + Interval = application:get_env(rabbit, collect_statistics_interval, 5000), + #state{level = StatsLevel, + interval = Interval, + timer = undefined}. + +init_stats_timer(C, P) -> + State = init_stats_timer(), + setelement(P, C, State). + +init_disabled_stats_timer(C, P) -> + State = #state{level = none, + interval = 0, + timer = undefined}, + setelement(P, C, State). + +ensure_stats_timer(C, P, Msg) -> + case element(P, C) of + #state{level = Level, + interval = Interval, + timer = undefined} = State + when Level =/= none -> + TRef = erlang:send_after(Interval, self(), Msg), + setelement(P, C, State#state{timer = TRef}); + _State -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) C end. @@ -156,5 +191,9 @@ event_cons(Type, Props, Ref) -> #event{type = Type, props = Props, reference = Ref, +<<<<<<< HEAD timestamp = os:system_time(milli_seconds)}. +======= + timestamp = os:system_time(millisecond)}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbit_common/src/rabbit_ssl_options.erl b/deps/rabbit_common/src/rabbit_ssl_options.erl index ee0d1b4a3260..a650c1790be9 100644 --- a/deps/rabbit_common/src/rabbit_ssl_options.erl +++ b/deps/rabbit_common/src/rabbit_ssl_options.erl @@ -8,6 +8,10 @@ -module(rabbit_ssl_options). -export([fix/1]). +<<<<<<< HEAD +======= +-export([fix_client/1]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -define(BAD_SSL_PROTOCOL_VERSIONS, [ @@ -22,6 +26,30 @@ fix(Config) -> fix_ssl_protocol_versions( hibernate_after(Config))). +<<<<<<< HEAD +======= +-spec fix_client(rabbit_types:infos()) -> rabbit_types:infos(). +fix_client(Config) -> + fix_cacerts( + fix(Config)). + +fix_cacerts(SslOptsConfig) -> + CACerts = proplists:get_value(cacerts, SslOptsConfig, undefined), + CACertfile = proplists:get_value(cacertfile, SslOptsConfig, undefined), + case {CACerts, CACertfile} of + {undefined, undefined} -> + try public_key:cacerts_get() of + CaCerts -> + [{cacerts, CaCerts} | SslOptsConfig] + catch + _ -> + SslOptsConfig + end; + _CaCerts -> + SslOptsConfig + end. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) fix_verify_fun(SslOptsConfig) -> %% Starting with ssl 4.0.1 in Erlang R14B, the verify_fun function %% takes 3 arguments and returns a tuple. diff --git a/deps/rabbit_common/test/rabbit_env_SUITE.erl b/deps/rabbit_common/test/rabbit_env_SUITE.erl index 0961a37a1855..cea2bb519a89 100644 --- a/deps/rabbit_common/test/rabbit_env_SUITE.erl +++ b/deps/rabbit_common/test/rabbit_env_SUITE.erl @@ -187,7 +187,10 @@ check_default_values(_) -> interactive_shell => default, keep_pid_file_on_exit => default, log_base_dir => default, +<<<<<<< HEAD log_feature_flags_registry => default, +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) log_levels => default, main_config_file => default, main_log_file => default, @@ -231,7 +234,10 @@ check_default_values(_) -> interactive_shell => false, keep_pid_file_on_exit => false, log_base_dir => "/var/log/rabbitmq", +<<<<<<< HEAD log_feature_flags_registry => false, +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) log_levels => undefined, main_config_file => "/etc/rabbitmq/rabbitmq", main_log_file => "/var/log/rabbitmq/" ++ NodeS ++ ".log", @@ -282,7 +288,10 @@ check_default_values(_) -> interactive_shell => false, keep_pid_file_on_exit => false, log_base_dir => "%APPDATA%/RabbitMQ/log", +<<<<<<< HEAD log_feature_flags_registry => false, +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) log_levels => undefined, main_config_file => "%APPDATA%/RabbitMQ/rabbitmq", main_log_file => "%APPDATA%/RabbitMQ/log/" ++ NodeS ++ ".log", @@ -408,7 +417,10 @@ check_values_from_reachable_remote_node(Config) -> interactive_shell => default, keep_pid_file_on_exit => default, log_base_dir => default, +<<<<<<< HEAD log_feature_flags_registry => default, +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) log_levels => default, main_config_file => default, main_log_file => default, @@ -452,7 +464,10 @@ check_values_from_reachable_remote_node(Config) -> interactive_shell => false, keep_pid_file_on_exit => false, log_base_dir => "/var/log/rabbitmq", +<<<<<<< HEAD log_feature_flags_registry => false, +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) log_levels => undefined, main_config_file => "/etc/rabbitmq/rabbitmq", main_log_file => "/var/log/rabbitmq/" ++ NodeS ++ ".log", @@ -540,7 +555,10 @@ check_values_from_offline_remote_node(_) -> interactive_shell => default, keep_pid_file_on_exit => default, log_base_dir => default, +<<<<<<< HEAD log_feature_flags_registry => default, +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) log_levels => default, main_config_file => default, main_log_file => default, @@ -584,7 +602,10 @@ check_values_from_offline_remote_node(_) -> interactive_shell => false, keep_pid_file_on_exit => false, log_base_dir => "/var/log/rabbitmq", +<<<<<<< HEAD log_feature_flags_registry => false, +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) log_levels => undefined, main_config_file => "/etc/rabbitmq/rabbitmq", main_log_file => "/var/log/rabbitmq/" ++ NodeS ++ ".log", diff --git a/deps/rabbitmq_amqp_client/src/rabbitmq_amqp_client.erl b/deps/rabbitmq_amqp_client/src/rabbitmq_amqp_client.erl index ce38b0241d10..b2d8a7f9fef8 100644 --- a/deps/rabbitmq_amqp_client/src/rabbitmq_amqp_client.erl +++ b/deps/rabbitmq_amqp_client/src/rabbitmq_amqp_client.erl @@ -28,7 +28,13 @@ declare_exchange/3, bind_exchange/5, unbind_exchange/5, +<<<<<<< HEAD delete_exchange/2 +======= + delete_exchange/2, + + set_token/2 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]. -define(TIMEOUT, 20_000). @@ -97,6 +103,11 @@ await_attached(Ref) -> receive {amqp10_event, {link, Ref, attached}} -> ok; +<<<<<<< HEAD +======= + {amqp10_event, {link, Ref, {attached, #'v1_0.attach'{}}}} -> + ok; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {amqp10_event, {link, Ref, {detached, Err}}} -> {error, Err} after ?TIMEOUT -> @@ -129,6 +140,11 @@ await_detached(Ref) -> receive {amqp10_event, {link, Ref, {detached, normal}}} -> ok; +<<<<<<< HEAD +======= + {amqp10_event, {link, Ref, {detached, #'v1_0.detach'{}}}} -> + ok; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {amqp10_event, {link, Ref, {detached, Err}}} -> {error, Err} after ?TIMEOUT -> @@ -377,6 +393,26 @@ delete_exchange(LinkPair, ExchangeName) -> Err end. +<<<<<<< HEAD +======= +%% Renew OAuth 2.0 token. +-spec set_token(link_pair(), binary()) -> + ok | {error, term()}. +set_token(LinkPair, Token) -> + Props = #{subject => <<"PUT">>, + to => <<"/auth/tokens">>}, + Body = {binary, Token}, + case request(LinkPair, Props, Body) of + {ok, Resp} -> + case is_success(Resp) of + true -> ok; + false -> {error, Resp} + end; + Err -> + Err + end. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec request(link_pair(), amqp10_msg:amqp10_properties(), amqp10_prim()) -> {ok, Response :: amqp10_msg:amqp10_msg()} | {error, term()}. request(#link_pair{session = Session, diff --git a/deps/rabbitmq_auth_backend_http/examples/rabbitmq_auth_backend_spring_boot/pom.xml b/deps/rabbitmq_auth_backend_http/examples/rabbitmq_auth_backend_spring_boot/pom.xml index b17460d8adef..5a1f5be1c646 100644 --- a/deps/rabbitmq_auth_backend_http/examples/rabbitmq_auth_backend_spring_boot/pom.xml +++ b/deps/rabbitmq_auth_backend_http/examples/rabbitmq_auth_backend_spring_boot/pom.xml @@ -29,13 +29,21 @@ org.springframework.boot spring-boot-starter-parent +<<<<<<< HEAD 3.3.2 +======= + 3.4.0 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) 17 17 +<<<<<<< HEAD 5.10.3 +======= + 5.11.3 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) com.rabbitmq.examples diff --git a/deps/rabbitmq_auth_backend_http/examples/rabbitmq_auth_backend_spring_boot_kotlin/pom.xml b/deps/rabbitmq_auth_backend_http/examples/rabbitmq_auth_backend_spring_boot_kotlin/pom.xml index f002a7f09f4b..5eb4edce7772 100644 --- a/deps/rabbitmq_auth_backend_http/examples/rabbitmq_auth_backend_spring_boot_kotlin/pom.xml +++ b/deps/rabbitmq_auth_backend_http/examples/rabbitmq_auth_backend_spring_boot_kotlin/pom.xml @@ -14,7 +14,11 @@ org.springframework.boot spring-boot-starter-parent +<<<<<<< HEAD 3.3.2 +======= + 3.4.0 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) @@ -23,7 +27,11 @@ UTF-8 17 17 +<<<<<<< HEAD 2.0.0 +======= + 2.1.0 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) 5.10.0 diff --git a/deps/rabbitmq_auth_backend_http/src/rabbit_auth_backend_http.erl b/deps/rabbitmq_auth_backend_http/src/rabbit_auth_backend_http.erl index c61aceeb8983..d1713c045029 100644 --- a/deps/rabbitmq_auth_backend_http/src/rabbit_auth_backend_http.erl +++ b/deps/rabbitmq_auth_backend_http/src/rabbit_auth_backend_http.erl @@ -205,7 +205,11 @@ do_http_req(Path0, Query) -> ssl_options() -> case application:get_env(rabbitmq_auth_backend_http, ssl_options) of {ok, Opts0} when is_list(Opts0) -> +<<<<<<< HEAD Opts1 = [{ssl, rabbit_networking:fix_ssl_options(Opts0)}], +======= + Opts1 = [{ssl, rabbit_ssl_options:fix_client(Opts0)}], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case application:get_env(rabbitmq_auth_backend_http, ssl_hostname_verification) of {ok, wildcard} -> rabbit_log:debug("Enabling wildcard-aware hostname verification for HTTP client connections"), diff --git a/deps/rabbitmq_auth_backend_ldap/src/rabbit_auth_backend_ldap.erl b/deps/rabbitmq_auth_backend_ldap/src/rabbit_auth_backend_ldap.erl index bba6767a3ce4..860d874941ad 100644 --- a/deps/rabbitmq_auth_backend_ldap/src/rabbit_auth_backend_ldap.erl +++ b/deps/rabbitmq_auth_backend_ldap/src/rabbit_auth_backend_ldap.erl @@ -761,7 +761,11 @@ ssl_conf() -> end. ssl_options() -> +<<<<<<< HEAD Opts0 = rabbit_networking:fix_ssl_options(env(ssl_options)), +======= + Opts0 = rabbit_ssl_options:fix_client(env(ssl_options)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case env(ssl_hostname_verification, undefined) of wildcard -> rabbit_log_ldap:debug("Enabling wildcard-aware hostname verification for LDAP client connections"), diff --git a/deps/rabbitmq_auth_backend_oauth2/BUILD.bazel b/deps/rabbitmq_auth_backend_oauth2/BUILD.bazel index 71c3d2e46289..c7830b2199ed 100644 --- a/deps/rabbitmq_auth_backend_oauth2/BUILD.bazel +++ b/deps/rabbitmq_auth_backend_oauth2/BUILD.bazel @@ -113,7 +113,11 @@ rabbitmq_integration_suite( ) rabbitmq_integration_suite( +<<<<<<< HEAD name = "rabbit_oauth2_config_SUITE", +======= + name = "rabbit_oauth2_provider_SUITE", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) additional_beam = [ "test/oauth2_http_mock.beam", ], @@ -123,6 +127,13 @@ rabbitmq_integration_suite( ) rabbitmq_integration_suite( +<<<<<<< HEAD +======= + name = "rabbit_oauth2_resource_server_SUITE", +) + +rabbitmq_integration_suite( +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) name = "jwks_SUITE", additional_beam = [ "test/rabbit_auth_backend_oauth2_test_util.beam", diff --git a/deps/rabbitmq_auth_backend_oauth2/Makefile b/deps/rabbitmq_auth_backend_oauth2/Makefile index 1066e7be8271..b267284fb33c 100644 --- a/deps/rabbitmq_auth_backend_oauth2/Makefile +++ b/deps/rabbitmq_auth_backend_oauth2/Makefile @@ -6,7 +6,11 @@ BUILD_WITHOUT_QUIC=1 export BUILD_WITHOUT_QUIC LOCAL_DEPS = inets public_key +<<<<<<< HEAD BUILD_DEPS = rabbit_common +======= +BUILD_DEPS = rabbit_common rabbitmq_cli +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) DEPS = rabbit cowlib jose base64url oauth2_client TEST_DEPS = cowboy rabbitmq_web_dispatch rabbitmq_ct_helpers rabbitmq_ct_client_helpers amqp_client rabbitmq_web_mqtt emqtt rabbitmq_amqp_client diff --git a/deps/rabbitmq_auth_backend_oauth2/README.md b/deps/rabbitmq_auth_backend_oauth2/README.md index 1d72c5af3e0b..5639e23573bb 100644 --- a/deps/rabbitmq_auth_backend_oauth2/README.md +++ b/deps/rabbitmq_auth_backend_oauth2/README.md @@ -149,13 +149,21 @@ In that case, the configuration would look like this: {rabbitmq_auth_backend_oauth2, [ {resource_server_id, <<"my_rabbit_server">>}, {key_config, [ +<<<<<<< HEAD {jwks_url, <<"https://jwt-issuer.my-domain.local/jwks.json">>} +======= + {jwks_uri, <<"https://jwt-issuer.my-domain.local/jwks.json">>} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]} ]}, ]. ``` +<<<<<<< HEAD Note: if both are configured, `jwks_url` takes precedence over `signing_keys`. +======= +Note: if both are configured, `jwks_uri` takes precedence over `signing_keys`. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ### Variables Configurable in rabbitmq.conf @@ -166,7 +174,11 @@ Note: if both are configured, `jwks_url` takes precedence over `signing_keys`. | `auth_oauth2.additional_scopes_key` | Key to fetch additional scopes from (maps to `additional_rabbitmq_scopes` in the `advanced.config` format) | `auth_oauth2.default_key` | ID (name) of the default signing key | `auth_oauth2.signing_keys` | Paths to signing key files +<<<<<<< HEAD | `auth_oauth2.jwks_url` | The URL of key server. According to the [JWT Specification](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.2) key server URL must be https +======= +| `auth_oauth2.jwks_uri` | The URL of key server. According to the [JWT Specification](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.2) key server URL must be https +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) | `auth_oauth2.https.cacertfile` | Path to a file containing PEM-encoded CA certificates. The CA certificates are used during key server [peer verification](https://rabbitmq.com/ssl.html#peer-verification) | `auth_oauth2.https.depth` | The maximum number of non-self-issued intermediate certificates that may follow the peer certificate in a valid [certification path](https://rabbitmq.com/ssl.html#peer-verification-depth). Default is 10. | `auth_oauth2.https.peer_verification` | Should [peer verification](https://rabbitmq.com/ssl.html#peer-verification) be enabled Available values: `verify_none`, `verify_peer`. Default is `verify_none`. It is recommended to configure `verify_peer`. Peer verification requires a certain amount of setup and is more secure. @@ -194,7 +206,11 @@ auth_oauth2.algorithms.2 = RS256 ``` auth_oauth2.resource_server_id = new_resource_server_id +<<<<<<< HEAD auth_oauth2.jwks_url = https://my-jwt-issuer/jwks.json +======= +auth_oauth2.jwks_uri = https://my-jwt-issuer/jwks.json +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) auth_oauth2.https.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem auth_oauth2.https.peer_verification = verify_peer auth_oauth2.https.depth = 5 @@ -234,7 +250,11 @@ resolve the user's identity: `username`, `user_name`, `email`, `sub`, `client_id {resource_server_id, <<"my_rabbit_server">>}, {preferred_username_claims, [ <<"username">>, <<"user_name">>, <<"email">> ]} {key_config, [ +<<<<<<< HEAD {jwks_url, <<"https://jwt-issuer.my-domain.local/jwks.json">>} +======= + {jwks_uri, <<"https://jwt-issuer.my-domain.local/jwks.json">>} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]} ]}, ]. diff --git a/deps/rabbitmq_auth_backend_oauth2/app.bzl b/deps/rabbitmq_auth_backend_oauth2/app.bzl index 003818ac74be..b43f7ded9916 100644 --- a/deps/rabbitmq_auth_backend_oauth2/app.bzl +++ b/deps/rabbitmq_auth_backend_oauth2/app.bzl @@ -13,7 +13,14 @@ def all_beam_files(name = "all_beam_files"): "src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand.erl", "src/rabbit_auth_backend_oauth2.erl", "src/rabbit_auth_backend_oauth2_app.erl", +<<<<<<< HEAD "src/rabbit_oauth2_config.erl", +======= + "src/rabbit_oauth2_keycloak.erl", + "src/rabbit_oauth2_provider.erl", + "src/rabbit_oauth2_rar.erl", + "src/rabbit_oauth2_resource_server.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_oauth2_schema.erl", "src/rabbit_oauth2_scope.erl", "src/uaa_jwks.erl", @@ -48,7 +55,14 @@ def all_test_beam_files(name = "all_test_beam_files"): "src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand.erl", "src/rabbit_auth_backend_oauth2.erl", "src/rabbit_auth_backend_oauth2_app.erl", +<<<<<<< HEAD "src/rabbit_oauth2_config.erl", +======= + "src/rabbit_oauth2_keycloak.erl", + "src/rabbit_oauth2_provider.erl", + "src/rabbit_oauth2_rar.erl", + "src/rabbit_oauth2_resource_server.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_oauth2_schema.erl", "src/rabbit_oauth2_scope.erl", "src/uaa_jwks.erl", @@ -85,6 +99,10 @@ def all_srcs(name = "all_srcs"): ) filegroup( name = "public_hdrs", +<<<<<<< HEAD +======= + srcs = ["include/oauth2.hrl"], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ) filegroup( @@ -94,7 +112,14 @@ def all_srcs(name = "all_srcs"): "src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand.erl", "src/rabbit_auth_backend_oauth2.erl", "src/rabbit_auth_backend_oauth2_app.erl", +<<<<<<< HEAD "src/rabbit_oauth2_config.erl", +======= + "src/rabbit_oauth2_keycloak.erl", + "src/rabbit_oauth2_provider.erl", + "src/rabbit_oauth2_rar.erl", + "src/rabbit_oauth2_resource_server.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_oauth2_schema.erl", "src/rabbit_oauth2_scope.erl", "src/uaa_jwks.erl", @@ -174,7 +199,11 @@ def test_suite_beam_files(name = "test_suite_beam_files"): outs = ["test/system_SUITE.beam"], app_name = "rabbitmq_auth_backend_oauth2", erlc_opts = "//:test_erlc_opts", +<<<<<<< HEAD deps = ["//deps/amqp_client:erlang_app"], +======= + deps = ["//deps/amqp10_common:erlang_app", "//deps/amqp_client:erlang_app"], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ) erlang_bytecode( name = "test_jwks_http_app_beam", @@ -223,9 +252,19 @@ def test_suite_beam_files(name = "test_suite_beam_files"): testonly = True, srcs = ["test/unit_SUITE.erl"], outs = ["test/unit_SUITE.beam"], +<<<<<<< HEAD app_name = "rabbitmq_auth_backend_oauth2", erlc_opts = "//:test_erlc_opts", deps = ["//deps/rabbit_common:erlang_app"], +======= + hdrs = ["include/oauth2.hrl"], + app_name = "rabbitmq_auth_backend_oauth2", + erlc_opts = "//:test_erlc_opts", + deps = [ + "//deps/oauth2_client:erlang_app", + "//deps/rabbit_common:erlang_app", + ], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ) erlang_bytecode( name = "wildcard_match_SUITE_beam_files", @@ -236,10 +275,28 @@ def test_suite_beam_files(name = "test_suite_beam_files"): erlc_opts = "//:test_erlc_opts", ) erlang_bytecode( +<<<<<<< HEAD name = "rabbit_oauth2_config_SUITE_beam_files", testonly = True, srcs = ["test/rabbit_oauth2_config_SUITE.erl"], outs = ["test/rabbit_oauth2_config_SUITE.beam"], +======= + name = "rabbit_oauth2_provider_SUITE_beam_files", + testonly = True, + srcs = ["test/rabbit_oauth2_provider_SUITE.erl"], + outs = ["test/rabbit_oauth2_provider_SUITE.beam"], + hdrs = ["include/oauth2.hrl"], + app_name = "rabbitmq_auth_backend_oauth2", + erlc_opts = "//:test_erlc_opts", + deps = ["//deps/oauth2_client:erlang_app"], + ) + erlang_bytecode( + name = "rabbit_oauth2_resource_server_SUITE_beam_files", + testonly = True, + srcs = ["test/rabbit_oauth2_resource_server_SUITE.erl"], + outs = ["test/rabbit_oauth2_resource_server_SUITE.beam"], + hdrs = ["include/oauth2.hrl"], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) app_name = "rabbitmq_auth_backend_oauth2", erlc_opts = "//:test_erlc_opts", deps = ["//deps/oauth2_client:erlang_app"], diff --git a/deps/rabbitmq_auth_backend_oauth2/include/oauth2.hrl b/deps/rabbitmq_auth_backend_oauth2/include/oauth2.hrl new file mode 100644 index 000000000000..4652c16ddcd1 --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/include/oauth2.hrl @@ -0,0 +1,47 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2020-2023 VMware, Inc. or its affiliates. All rights reserved. +%% + + +-include_lib("oauth2_client/include/oauth2_client.hrl"). + +-define(APP, rabbitmq_auth_backend_oauth2). +-define(DEFAULT_PREFERRED_USERNAME_CLAIMS, [<<"sub">>, <<"client_id">>]). +%% scope aliases map "role names" to a set of scopes + +%% +%% Key JWT fields +%% + +-define(AUD_JWT_FIELD, <<"aud">>). +-define(SCOPE_JWT_FIELD, <<"scope">>). +-define(TAG_SCOPE_PREFIX, <<"tag:">>). + +%% End of Key JWT fields + +-type raw_jwt_token() :: binary() | #{binary() => any()}. +-type decoded_jwt_token() :: #{binary() => any()}. + +-record(internal_oauth_provider, { + id :: oauth_provider_id(), + default_key :: binary() | undefined, + algorithms :: list() | undefined +}). +-type internal_oauth_provider() :: #internal_oauth_provider{}. + +-record(resource_server, { + id :: resource_server_id(), + resource_server_type :: binary() | undefined, + verify_aud :: boolean(), + scope_prefix :: binary(), + additional_scopes_key :: binary() | undefined, + preferred_username_claims :: list(), + scope_aliases :: map() | undefined, + oauth_provider_id :: oauth_provider_id() + }). + +-type resource_server() :: #resource_server{}. +-type resource_server_id() :: binary() | list(). diff --git a/deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema b/deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema index 399708ae2562..464ec95b7154 100644 --- a/deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema +++ b/deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema @@ -73,6 +73,29 @@ list_to_binary(cuttlefish:conf_get("auth_oauth2.additional_scopes_key", Conf)) end}. +<<<<<<< HEAD +======= +{mapping, + "auth_oauth2.scope_aliases.$alias", + "rabbitmq_auth_backend_oauth2.scope_aliases", + [{datatype, string}]}. + +{mapping, + "auth_oauth2.scope_aliases.$index.alias", + "rabbitmq_auth_backend_oauth2.scope_aliases", + [{datatype, string}]}. + +{mapping, + "auth_oauth2.scope_aliases.$index.scope", + "rabbitmq_auth_backend_oauth2.scope_aliases", + [{datatype, string}]}. + +{translation, + "rabbitmq_auth_backend_oauth2.scope_aliases", + fun(Conf) -> + rabbit_oauth2_schema:translate_scope_aliases(Conf) + end}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% Configure the plugin to skip validation of the aud field %% @@ -143,6 +166,16 @@ "rabbitmq_auth_backend_oauth2.token_endpoint", [{datatype, string}, {validators, ["uri", "https_uri"]}]}. +<<<<<<< HEAD +======= +%% DEPRECATES auth_oauth2.jwks_url +{mapping, + "auth_oauth2.jwks_uri", + "rabbitmq_auth_backend_oauth2.jwks_uri", + [{datatype, string}, {validators, ["uri", "https_uri"]}]}. + +%% DEPRECATED +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {mapping, "auth_oauth2.jwks_url", "rabbitmq_auth_backend_oauth2.key_config.jwks_url", @@ -159,6 +192,44 @@ [{datatype, string}, {validators, ["uri", "https_uri"]}]}. {mapping, +<<<<<<< HEAD +======= + "auth_oauth2.discovery_endpoint_path", + "rabbitmq_auth_backend_oauth2.discovery_endpoint_path", + [{datatype, string}]}. + +{mapping, + "auth_oauth2.discovery_endpoint_params.$param", + "rabbitmq_auth_backend_oauth2.discovery_endpoint_params", + [{datatype, string}]}. + +{translation, "rabbitmq_auth_backend_oauth2.discovery_endpoint_params", + fun(Conf) -> + rabbit_oauth2_schema:translate_endpoint_params("discovery_endpoint_params", Conf) + end}. + +{mapping, + "auth_oauth2.oauth_providers.$name.discovery_endpoint_params.$param", + "rabbitmq_auth_backend_oauth2.oauth_providers", + [{datatype, string}]}. + +{mapping, + "auth_oauth2.oauth_providers.$name.discovery_endpoint_path", + "rabbitmq_auth_backend_oauth2.oauth_providers", + [{datatype, string}]}. + +{mapping, + "auth_oauth2.oauth_providers.$name.algorithms.$algorithm", + "rabbitmq_auth_backend_oauth2.oauth_providers", + [{datatype, string}]}. + +{translation, "rabbitmq_auth_backend_oauth2.oauth_providers", + fun(Conf) -> + rabbit_oauth2_schema:translate_oauth_providers(Conf) + end}. + +{mapping, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "auth_oauth2.https.peer_verification", "rabbitmq_auth_backend_oauth2.key_config.peer_verification", [{datatype, {enum, [verify_peer, verify_none]}}]}. @@ -301,6 +372,10 @@ [{datatype, string}] }. +<<<<<<< HEAD +======= + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {mapping, "auth_oauth2.resource_servers.$name.scope_prefix", "rabbitmq_auth_backend_oauth2.resource_servers", @@ -320,6 +395,24 @@ }. {mapping, +<<<<<<< HEAD +======= + "auth_oauth2.resource_servers.$name.scope_aliases.$alias", + "rabbitmq_auth_backend_oauth2.resource_servers", + [{datatype, string}]}. + +{mapping, + "auth_oauth2.resource_servers.$name.scope_aliases.$index.alias", + "rabbitmq_auth_backend_oauth2.resource_servers", + [{datatype, string}]}. + +{mapping, + "auth_oauth2.resource_servers.$name.scope_aliases.$index.scope", + "rabbitmq_auth_backend_oauth2.resource_servers", + [{datatype, string}]}. + +{mapping, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "auth_oauth2.resource_servers.$name.oauth_provider_id", "rabbitmq_auth_backend_oauth2.resource_servers", [{datatype, string}] @@ -333,5 +426,9 @@ {translation, "rabbitmq_auth_backend_oauth2.resource_servers", fun(Conf) -> +<<<<<<< HEAD rabbit_oauth2_schema:translate_resource_servers(Conf) +======= + rabbit_oauth2_schema:translate_resource_servers(Conf) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end}. diff --git a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl index cdfe6e15056e..c98e9625c7cd 100644 --- a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl +++ b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl @@ -8,6 +8,10 @@ -module(rabbit_auth_backend_oauth2). -include_lib("rabbit_common/include/rabbit.hrl"). +<<<<<<< HEAD +======= +-include("oauth2.hrl"). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -behaviour(rabbit_authn_backend). -behaviour(rabbit_authz_backend). @@ -15,6 +19,7 @@ -export([description/0]). -export([user_login_authentication/2, user_login_authorization/2, check_vhost_access/3, check_resource_access/4, +<<<<<<< HEAD check_topic_access/4, check_token/1, update_state/2, expiry_timestamp/1]). @@ -23,12 +28,31 @@ -import(uaa_jwt, [resolve_resource_server_id/1]). -import(rabbit_data_coercion, [to_map/1]). -import(rabbit_oauth2_config, [get_preferred_username_claims/1]). +======= + check_topic_access/4, update_state/2, + expiry_timestamp/1]). + +%% for testing +-export([normalize_token_scope/2, get_expanded_scopes/2]). + +-import(rabbit_data_coercion, [to_map/1]). +-import(uaa_jwt, [ + decode_and_verify/3, + get_scope/1, set_scope/2, + resolve_resource_server/1]). + +-import(rabbit_oauth2_keycloak, [has_keycloak_scopes/1, extract_scopes_from_keycloak_format/1]). +-import(rabbit_oauth2_rar, [extract_scopes_from_rich_auth_request/2, has_rich_auth_request_scopes/1]). + +-import(rabbit_oauth2_scope, [filter_matching_scope_prefix_and_drop_it/2]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -ifdef(TEST). -compile(export_all). -endif. %% +<<<<<<< HEAD %% App environment %% @@ -46,6 +70,14 @@ -define(AUD_JWT_FIELD, <<"aud">>). -define(SCOPE_JWT_FIELD, <<"scope">>). +======= +%% Types +%% + +-type ok_extracted_auth_user() :: {ok, rabbit_types:auth_user()}. +-type auth_user_extraction_fun() :: fun((decoded_jwt_token()) -> any()). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% %% API %% @@ -56,6 +88,14 @@ description() -> %%-------------------------------------------------------------------- +<<<<<<< HEAD +======= +-spec user_login_authentication(rabbit_types:username(), [term()] | map()) -> + {'ok', rabbit_types:auth_user()} | + {'refused', string(), [any()]} | + {'error', any()}. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) user_login_authentication(Username, AuthProps) -> case authenticate(Username, AuthProps) of {refused, Msg, Args} = AuthResult -> @@ -65,18 +105,37 @@ user_login_authentication(Username, AuthProps) -> AuthResult end. +<<<<<<< HEAD +======= +-spec user_login_authorization(rabbit_types:username(), [term()] | map()) -> + {'ok', any()} | + {'ok', any(), any()} | + {'refused', string(), [any()]} | + {'error', any()}. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) user_login_authorization(Username, AuthProps) -> case authenticate(Username, AuthProps) of {ok, #auth_user{impl = Impl}} -> {ok, Impl}; Else -> Else end. +<<<<<<< HEAD +======= +-spec check_vhost_access(AuthUser :: rabbit_types:auth_user(), + VHost :: rabbit_types:vhost(), + AuthzData :: rabbit_types:authz_data()) -> boolean() | {'error', any()}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) check_vhost_access(#auth_user{impl = DecodedTokenFun}, VHost, _AuthzData) -> with_decoded_token(DecodedTokenFun(), fun(_Token) -> DecodedToken = DecodedTokenFun(), +<<<<<<< HEAD Scopes = get_scopes(DecodedToken), +======= + Scopes = get_scope(DecodedToken), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ScopeString = rabbit_oauth2_scope:concat_scopes(Scopes, ","), rabbit_log:debug("Matching virtual host '~ts' against the following scopes: ~ts", [VHost, ScopeString]), rabbit_oauth2_scope:vhost_access(VHost, Scopes) @@ -86,7 +145,11 @@ check_resource_access(#auth_user{impl = DecodedTokenFun}, Resource, Permission, _AuthzContext) -> with_decoded_token(DecodedTokenFun(), fun(Token) -> +<<<<<<< HEAD Scopes = get_scopes(Token), +======= + Scopes = get_scope(Token), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_oauth2_scope:resource_access(Resource, Permission, Scopes) end). @@ -99,6 +162,7 @@ check_topic_access(#auth_user{impl = DecodedTokenFun}, end). update_state(AuthUser, NewToken) -> +<<<<<<< HEAD case check_token(NewToken) of %% avoid logging the token {error, _} = E -> E; @@ -119,6 +183,30 @@ update_state(AuthUser, NewToken) -> {error, mismatch_username_after_token_refresh} -> {refused, "Not allowed to change username on refreshed token"} +======= + case resolve_resource_server(NewToken) of + {error, _} = Err0 -> Err0; + {ResourceServer, _} = Tuple -> + case check_token(NewToken, Tuple) of + %% avoid logging the token + {refused, {error, {invalid_token, error, _Err, _Stacktrace}}} -> + {refused, "Authentication using an OAuth 2/JWT token failed: provided token is invalid"}; + {refused, Err} -> + {refused, rabbit_misc:format("Authentication using an OAuth 2/JWT token failed: ~tp", [Err])}; + {ok, DecodedToken} -> + CurToken = AuthUser#auth_user.impl, + case ensure_same_username( + ResourceServer#resource_server.preferred_username_claims, + CurToken(), DecodedToken) of + ok -> + Tags = tags_from(DecodedToken), + {ok, AuthUser#auth_user{tags = Tags, + impl = fun() -> DecodedToken end}}; + {error, mismatch_username_after_token_refresh} -> + {refused, + "Not allowed to change username on refreshed token"} + end +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end end. @@ -132,6 +220,7 @@ expiry_timestamp(#auth_user{impl = DecodedTokenFun}) -> %%-------------------------------------------------------------------- +<<<<<<< HEAD authenticate(_, AuthProps0) -> AuthProps = to_map(AuthProps0), Token = token_from_context(AuthProps), @@ -162,6 +251,39 @@ authenticate(_, AuthProps0) -> end end. +======= +-spec authenticate(Username, Props) -> Result + when Username :: rabbit_types:username(), + Props :: list() | map(), + Result :: {ok, any()} | {refused, list(), list()} | {refused, {error, any()}}. + +authenticate(_, AuthProps0) -> + AuthProps = to_map(AuthProps0), + Token = token_from_context(AuthProps), + case resolve_resource_server(Token) of + {error, _} = Err0 -> + {refused, "Authentication using OAuth 2/JWT token failed: ~tp", [Err0]}; + {ResourceServer, _} = Tuple -> + case check_token(Token, Tuple) of + {refused, {error, {invalid_token, error, _Err, _Stacktrace}}} -> + {refused, "Authentication using an OAuth 2/JWT token failed: provided token is invalid", []}; + {refused, Err} -> + {refused, "Authentication using an OAuth 2/JWT token failed: ~tp", [Err]}; + {ok, DecodedToken} -> + case with_decoded_token(DecodedToken, fun(In) -> auth_user_from_token(In, ResourceServer) end) of + {error, Err} -> + {refused, "Authentication using an OAuth 2/JWT token failed: ~tp", [Err]}; + Else -> + Else + end + end + end. + +-spec with_decoded_token(Token, Fun) -> Result + when Token :: decoded_jwt_token(), + Fun :: auth_user_extraction_fun(), + Result :: {ok, any()} | {'error', any()}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) with_decoded_token(DecodedToken, Fun) -> case validate_token_expiry(DecodedToken) of ok -> Fun(DecodedToken); @@ -169,6 +291,24 @@ with_decoded_token(DecodedToken, Fun) -> rabbit_log:error(Msg), Err end. +<<<<<<< HEAD +======= + +%% This is a helper function used with HOFs that may return errors. +-spec auth_user_from_token(Token, ResourceServer) -> Result + when Token :: decoded_jwt_token(), + ResourceServer :: resource_server(), + Result :: ok_extracted_auth_user(). +auth_user_from_token(Token0, ResourceServer) -> + Username = username_from( + ResourceServer#resource_server.preferred_username_claims, + Token0), + Tags = tags_from(Token0), + {ok, #auth_user{username = Username, + tags = Tags, + impl = fun() -> Token0 end}}. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ensure_same_username(PreferredUsernameClaims, CurrentDecodedToken, NewDecodedToken) -> CurUsername = username_from(PreferredUsernameClaims, CurrentDecodedToken), case {CurUsername, username_from(PreferredUsernameClaims, NewDecodedToken)} of @@ -184,6 +324,7 @@ validate_token_expiry(#{<<"exp">> := Exp}) when is_integer(Exp) -> end; validate_token_expiry(#{}) -> ok. +<<<<<<< HEAD -spec check_token(binary() | map()) -> {'ok', map()} | {'error', term() }| @@ -278,6 +419,63 @@ post_process_payload_with_scope_alias_in_extra_scopes_source(ResourceServerId, P ScopeAliasMapping :: map()) -> map(). post_process_payload_with_scope_alias_field_named(Payload, FieldName, ScopeAliasMapping) -> Scopes0 = maps:get(FieldName, Payload, []), +======= +-spec check_token(raw_jwt_token(), {resource_server(), internal_oauth_provider()}) -> + {'ok', decoded_jwt_token()} | + {'error', term() } | + {'refused', 'signature_invalid' | {'error', term()} | {'invalid_aud', term()}}. + +check_token(DecodedToken, _) when is_map(DecodedToken) -> + {ok, DecodedToken}; + +check_token(Token, {ResourceServer, InternalOAuthProvider}) -> + case decode_and_verify(Token, ResourceServer, InternalOAuthProvider) of + {error, Reason} -> {refused, {error, Reason}}; + {true, Payload} -> {ok, normalize_token_scope(ResourceServer, Payload)}; + {false, _} -> {refused, signature_invalid} + end. + +-spec normalize_token_scope( + ResourceServer :: resource_server(), DecodedToken :: decoded_jwt_token()) -> map(). +normalize_token_scope(ResourceServer, Payload) -> + Payload0 = maps:map(fun(K, V) -> + case K of + ?SCOPE_JWT_FIELD when is_binary(V) -> + binary:split(V, <<" ">>, [global, trim_all]); + _ -> V + end + end, Payload), + + Payload1 = case has_additional_scopes_key(ResourceServer, Payload0) of + true -> extract_scopes_from_additional_scopes_key(ResourceServer, Payload0); + false -> Payload0 + end, + + Payload2 = case has_keycloak_scopes(Payload1) of + true -> extract_scopes_from_keycloak_format(Payload1); + false -> Payload1 + end, + + Payload3 = case ResourceServer#resource_server.scope_aliases of + undefined -> Payload2; + ScopeAliases -> extract_scopes_using_scope_aliases(ScopeAliases, Payload2) + end, + + Payload4 = case has_rich_auth_request_scopes(Payload3) of + true -> extract_scopes_from_rich_auth_request(ResourceServer, Payload3); + false -> Payload3 + end, + + FilteredScopes = filter_matching_scope_prefix_and_drop_it( + get_scope(Payload4), ResourceServer#resource_server.scope_prefix), + set_scope(FilteredScopes, Payload4). + + +-spec extract_scopes_using_scope_aliases( + ScopeAliasMapping :: map(), Payload :: map()) -> map(). +extract_scopes_using_scope_aliases(ScopeAliasMapping, Payload) -> + Scopes0 = get_scope(Payload), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Scopes = rabbit_data_coercion:to_list_of_binaries(Scopes0), %% for all scopes, look them up in the scope alias map, and if they are %% present, add the alias to the final scope list. Note that we also preserve @@ -295,6 +493,7 @@ post_process_payload_with_scope_alias_field_named(Payload, FieldName, ScopeAlias Acc ++ Binaries end end, Scopes, Scopes), +<<<<<<< HEAD maps:put(?SCOPE_JWT_FIELD, ExpandedScopes, Payload). @@ -593,6 +792,44 @@ resolve_scope_var(Elem, Token, Vhost) -> _ -> ElemAsBinary end) end. +======= + set_scope(ExpandedScopes, Payload). + +-spec has_additional_scopes_key( + ResourceServer :: resource_server(), Payload :: map()) -> boolean(). +has_additional_scopes_key(ResourceServer, Payload) when is_map(Payload) -> + case ResourceServer#resource_server.additional_scopes_key of + undefined -> false; + ScopeKey -> maps:is_key(ScopeKey, Payload) + end. + +-spec extract_scopes_from_additional_scopes_key( + ResourceServer :: resource_server(), Payload :: map()) -> map(). +extract_scopes_from_additional_scopes_key(ResourceServer, Payload) -> + Claim = maps:get(ResourceServer#resource_server.additional_scopes_key, Payload), + AdditionalScopes = extract_additional_scopes(ResourceServer, Claim), + set_scope(AdditionalScopes ++ get_scope(Payload), Payload). + +extract_additional_scopes(ResourceServer, ComplexClaim) -> + ResourceServerId = ResourceServer#resource_server.id, + case ComplexClaim of + L when is_list(L) -> L; + M when is_map(M) -> + case maps:get(ResourceServerId, M, undefined) of + undefined -> []; + Ks when is_list(Ks) -> + [erlang:iolist_to_binary([ResourceServerId, <<".">>, K]) || K <- Ks]; + ClaimBin when is_binary(ClaimBin) -> + UnprefixedClaims = binary:split(ClaimBin, <<" ">>, [global, trim_all]), + [erlang:iolist_to_binary([ResourceServerId, <<".">>, K]) || K <- UnprefixedClaims]; + _ -> [] + end; + Bin when is_binary(Bin) -> + binary:split(Bin, <<" ">>, [global, trim_all]); + _ -> [] + end. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% A token may be present in the password credential or in the rabbit_auth_backend_oauth2 %% credential. The former is the most common scenario for the first time authentication. @@ -648,6 +885,7 @@ find_claim_in_token(Claim, Token) -> _ -> false end. +<<<<<<< HEAD -define(TAG_SCOPE_PREFIX, <<"tag:">>). -spec tags_from(map()) -> list(atom()). @@ -670,3 +908,52 @@ matching_scopes_without_prefix(Scopes, PrefixPattern) -> end end, Scopes). +======= +-spec get_expanded_scopes(map(), #resource{}) -> [binary()]. +get_expanded_scopes(Token, #resource{virtual_host = VHost}) -> + Context = #{ token => Token , vhost => VHost}, + case get_scope(Token) of + [] -> []; + Scopes -> lists:map(fun(Scope) -> list_to_binary(parse_scope(Scope, Context)) end, Scopes) + end. + + +parse_scope(Scope, Context) -> + { Acc0, _} = lists:foldl(fun(Elem, { Acc, Stage }) -> parse_scope_part(Elem, Acc, Stage, Context) end, + { [], undefined }, re:split(Scope,"([\{.*\}])",[{return,list},trim])), + Acc0. + +parse_scope_part(Elem, Acc, Stage, Context) -> + case Stage of + error -> {Acc, error}; + undefined -> + case Elem of + "{" -> { Acc, fun capture_var_name/3}; + Value -> { Acc ++ Value, Stage} + end; + _ -> Stage(Elem, Acc, Context) + end. + +capture_var_name(Elem, Acc, #{ token := Token, vhost := Vhost}) -> + { Acc ++ resolve_scope_var(Elem, Token, Vhost), fun expect_closing_var/3}. + +expect_closing_var("}" , Acc, _Context) -> { Acc , undefined }; +expect_closing_var(_ , _Acc, _Context) -> {"", error}. + +resolve_scope_var(Elem, Token, Vhost) -> + case Elem of + "vhost" -> binary_to_list(Vhost); + _ -> + ElemAsBinary = list_to_binary(Elem), + binary_to_list(case maps:get(ElemAsBinary, Token, ElemAsBinary) of + Value when is_binary(Value) -> Value; + _ -> ElemAsBinary + end) + end. + +-spec tags_from(decoded_jwt_token()) -> list(atom()). +tags_from(DecodedToken) -> + Scopes = maps:get(?SCOPE_JWT_FIELD, DecodedToken, []), + TagScopes = filter_matching_scope_prefix_and_drop_it(Scopes, ?TAG_SCOPE_PREFIX), + lists:usort(lists:map(fun rabbit_data_coercion:to_atom/1, TagScopes)). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_keycloak.erl b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_keycloak.erl new file mode 100644 index 000000000000..79c056a808a8 --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_keycloak.erl @@ -0,0 +1,41 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(rabbit_oauth2_keycloak). + +-include("oauth2.hrl"). + +-export([extract_scopes_from_keycloak_format/1, has_keycloak_scopes/1]). +-import(uaa_jwt, [get_scope/1, set_scope/2]). + +-define(AUTHORIZATION_CLAIM, <<"authorization">>). +-define(PERMISSIONS_CLAIM, <<"permissions">>). +-define(SCOPES_CLAIM, <<"scopes">>). + +-spec has_keycloak_scopes(Payload::map()) -> boolean(). +has_keycloak_scopes(Payload) -> + maps:is_key(?AUTHORIZATION_CLAIM, Payload). + +-spec extract_scopes_from_keycloak_format(Payload :: map()) -> map(). +%% keycloak token format: https://github.com/rabbitmq/rabbitmq-auth-backend-oauth2/issues/36 +extract_scopes_from_keycloak_format(#{?AUTHORIZATION_CLAIM := Authorization} = Payload) -> + AdditionalScopes = extract_scopes_from_keycloak_permissions([], + maps:get(?PERMISSIONS_CLAIM, Authorization, [])), + set_scope(AdditionalScopes ++ get_scope(Payload), Payload). + +extract_scopes_from_keycloak_permissions(Acc, []) -> + Acc; +extract_scopes_from_keycloak_permissions(Acc, [H | T]) when is_map(H) -> + Scopes = case maps:get(?SCOPES_CLAIM, H, []) of + ScopesAsList when is_list(ScopesAsList) -> + ScopesAsList; + ScopesAsBinary when is_binary(ScopesAsBinary) -> + [ScopesAsBinary] + end, + extract_scopes_from_keycloak_permissions(Acc ++ Scopes, T); +extract_scopes_from_keycloak_permissions(Acc, [_ | T]) -> + extract_scopes_from_keycloak_permissions(Acc, T). diff --git a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_provider.erl b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_provider.erl new file mode 100644 index 000000000000..2891af5a8b8d --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_provider.erl @@ -0,0 +1,197 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(rabbit_oauth2_provider). + +-include("oauth2.hrl"). + +-export([ + get_internal_oauth_provider/0, get_internal_oauth_provider/1, + add_signing_key/2, add_signing_key/3, replace_signing_keys/1, + replace_signing_keys/2, + get_signing_keys/0, get_signing_keys/1, get_signing_key/1, get_signing_key/2 +]). + +-spec get_internal_oauth_provider() -> internal_oauth_provider(). +get_internal_oauth_provider() -> + get_internal_oauth_provider(root). + +-spec get_internal_oauth_provider(oauth_provider_id()) -> internal_oauth_provider(). +get_internal_oauth_provider(OAuthProviderId) -> + #internal_oauth_provider{ + id = OAuthProviderId, + default_key = get_default_key(OAuthProviderId), + algorithms = get_algorithms(OAuthProviderId) + }. + + +%% +%% Signing Key storage: +%% +%% * Static signing keys configured via config file are stored under signing_keys attribute +%% in their respective location (under key_config for the root oauth provider and +%% directly under each oauth provider) +%% * Dynamic signing keys loaded via rabbitmqctl or via JWKS endpoint are stored under +%% jwks attribute in their respective location. However, this attribute stores the +%% combination of static signing keys and dynamic signing keys. If the same kid is +%% found in both sets, the dynamic kid overrides the static kid. +%% + +-type key_type() :: json | pem | map. +-spec add_signing_key(binary(), {key_type(), binary()} ) -> map() | {error, term()}. +add_signing_key(KeyId, Key) -> + LockId = lock(), + try do_add_signing_key(KeyId, Key, root) of + V -> V + after + unlock(LockId) + end. + +-spec add_signing_key(binary(), {key_type(), binary()}, oauth_provider_id()) -> + map() | {error, term()}. +add_signing_key(KeyId, Key, OAuthProviderId) -> + case lock() of + {error, _} = Error -> + Error; + LockId -> + try do_add_signing_key(KeyId, Key, OAuthProviderId) of + V -> V + after + unlock(LockId) + end + end. + +do_add_signing_key(KeyId, Key, OAuthProviderId) -> + do_replace_signing_keys(maps:put(KeyId, Key, + get_signing_keys_from_jwks(OAuthProviderId)), OAuthProviderId). + +get_signing_keys_from_jwks(root) -> + KeyConfig = get_env(key_config, []), + proplists:get_value(jwks, KeyConfig, #{}); +get_signing_keys_from_jwks(OAuthProviderId) -> + OAuthProviders0 = get_env(oauth_providers, #{}), + OAuthProvider0 = maps:get(OAuthProviderId, OAuthProviders0, []), + proplists:get_value(jwks, OAuthProvider0, #{}). + +-spec replace_signing_keys(map()) -> map() | {error, term()}. +replace_signing_keys(SigningKeys) -> + replace_signing_keys(SigningKeys, root). + +-spec replace_signing_keys(map(), oauth_provider_id()) -> map() | {error, term()}. +replace_signing_keys(SigningKeys, OAuthProviderId) -> + case lock() of + {error,_} = Error -> + Error; + LockId -> + try do_replace_signing_keys(SigningKeys, OAuthProviderId) of + V -> V + after + unlock(LockId) + end + end. + +do_replace_signing_keys(SigningKeys, root) -> + KeyConfig = get_env(key_config, []), + KeyConfig1 = proplists:delete(jwks, KeyConfig), + KeyConfig2 = [{jwks, maps:merge( + proplists:get_value(signing_keys, KeyConfig1, #{}), + SigningKeys)} | KeyConfig1], + set_env(key_config, KeyConfig2), + rabbit_log:debug("Replacing signing keys for key_config with ~p keys", + [maps:size(SigningKeys)]), + SigningKeys; + +do_replace_signing_keys(SigningKeys, OauthProviderId) -> + OauthProviders0 = get_env(oauth_providers, #{}), + OauthProvider0 = maps:get(OauthProviderId, OauthProviders0, []), + OauthProvider1 = proplists:delete(jwks, OauthProvider0), + OauthProvider = [{jwks, maps:merge( + proplists:get_value(signing_keys, OauthProvider1, #{}), + SigningKeys)} | OauthProvider1], + + OauthProviders = maps:put(OauthProviderId, OauthProvider, OauthProviders0), + set_env(oauth_providers, OauthProviders), + rabbit_log:debug("Replacing signing keys for ~p -> ~p with ~p keys", + [OauthProviderId, OauthProvider, maps:size(SigningKeys)]), + SigningKeys. + + +-spec get_signing_keys() -> map(). +get_signing_keys() -> + get_signing_keys(root). + +-spec get_signing_keys(oauth_provider_id()) -> map(). +get_signing_keys(root) -> + case get_env(key_config) of + undefined -> + #{}; + KeyConfig -> + case proplists:get_value(jwks, KeyConfig, undefined) of + undefined -> proplists:get_value(signing_keys, KeyConfig, #{}); + Jwks -> Jwks + end + end; +get_signing_keys(OauthProviderId) -> + OauthProviders = get_env(oauth_providers, #{}), + OauthProvider = maps:get(OauthProviderId, OauthProviders, []), + case proplists:get_value(jwks, OauthProvider, undefined) of + undefined -> + proplists:get_value(signing_keys, OauthProvider, #{}); + Jwks -> + Jwks + end. + +get_signing_key(KeyId) -> + maps:get(KeyId, get_signing_keys(root), undefined). +get_signing_key(KeyId, OAuthProviderId) -> + maps:get(KeyId, get_signing_keys(OAuthProviderId), undefined). + +-spec get_default_key(oauth_provider_id()) -> binary() | undefined. +get_default_key(root) -> + case application:get_env(?APP, key_config, undefined) of + undefined -> undefined; + KeyConfig -> proplists:get_value(default_key, KeyConfig, undefined) + end; +get_default_key(OauthProviderId) -> + OauthProviders = application:get_env(?APP, oauth_providers, #{}), + case maps:get(OauthProviderId, OauthProviders, []) of + [] -> undefined; + OauthProvider -> proplists:get_value(default_key, OauthProvider, undefined) + end. + +-spec get_algorithms(oauth_provider_id()) -> list() | undefined. +get_algorithms(root) -> + proplists:get_value(algorithms, get_env(key_config, []), undefined); +get_algorithms(OAuthProviderId) -> + OAuthProviders = get_env(oauth_providers, #{}), + case maps:get(OAuthProviderId, OAuthProviders, undefined) of + undefined -> undefined; + V -> proplists:get_value(algorithms, V, undefined) + end. + +get_env(Par) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par, undefined). +get_env(Par, Def) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par, Def). +set_env(Par, Value) -> + application:set_env(rabbitmq_auth_backend_oauth2, Par, Value). + + +lock() -> + Nodes = rabbit_nodes:list_running(), + Retries = rabbit_nodes:lock_retries(), + LockId = case global:set_lock({oauth2_config_lock, + rabbitmq_auth_backend_oauth2}, Nodes, Retries) of + true -> rabbitmq_auth_backend_oauth2; + false -> {error, unable_to_claim_lock} + end, + LockId. + +unlock(LockId) -> + Nodes = rabbit_nodes:list_running(), + global:del_lock({oauth2_config_lock, LockId}, Nodes), + ok. diff --git a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_rar.erl b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_rar.erl new file mode 100644 index 000000000000..d8a2c36f8325 --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_rar.erl @@ -0,0 +1,183 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +% Rich Authorization Request +-module(rabbit_oauth2_rar). + +-include("oauth2.hrl"). +-import(uaa_jwt, [get_scope/1, set_scope/2]). + +-export([extract_scopes_from_rich_auth_request/2, has_rich_auth_request_scopes/1]). + +-define(AUTHORIZATION_DETAILS_CLAIM, <<"authorization_details">>). +-define(RAR_ACTIONS_FIELD, <<"actions">>). +-define(RAR_LOCATIONS_FIELD, <<"locations">>). +-define(RAR_TYPE_FIELD, <<"type">>). + +-define(RAR_CLUSTER_LOCATION_ATTRIBUTE, <<"cluster">>). +-define(RAR_VHOST_LOCATION_ATTRIBUTE, <<"vhost">>). +-define(RAR_QUEUE_LOCATION_ATTRIBUTE, <<"queue">>). +-define(RAR_EXCHANGE_LOCATION_ATTRIBUTE, <<"exchange">>). +-define(RAR_ROUTING_KEY_LOCATION_ATTRIBUTE, <<"routing-key">>). +-define(RAR_LOCATION_ATTRIBUTES, [ + ?RAR_CLUSTER_LOCATION_ATTRIBUTE, + ?RAR_VHOST_LOCATION_ATTRIBUTE, + ?RAR_QUEUE_LOCATION_ATTRIBUTE, + ?RAR_EXCHANGE_LOCATION_ATTRIBUTE, + ?RAR_ROUTING_KEY_LOCATION_ATTRIBUTE]). + +-define(RAR_ALLOWED_TAG_VALUES, [ + <<"monitoring">>, + <<"administrator">>, + <<"management">>, + <<"policymaker">> ]). +-define(RAR_ALLOWED_ACTION_VALUES, [ + <<"read">>, + <<"write">>, + <<"configure">>, + <<"monitoring">>, + <<"administrator">>, + <<"management">>, + <<"policymaker">> ]). + +-spec has_rich_auth_request_scopes(Payload::map()) -> boolean(). +has_rich_auth_request_scopes(Payload) -> + maps:is_key(?AUTHORIZATION_DETAILS_CLAIM, Payload). + +-spec extract_scopes_from_rich_auth_request(ResourceServer :: resource_server(), + Payload :: map()) -> map(). +%% https://oauth.net/2/rich-authorization-requests/ +extract_scopes_from_rich_auth_request(ResourceServer, + #{?AUTHORIZATION_DETAILS_CLAIM := Permissions} = Payload) -> + ResourceServerType = ResourceServer#resource_server.resource_server_type, + + FilteredPermissionsByType = lists:filter(fun(P) -> + is_recognized_permission(P, ResourceServerType) end, Permissions), + AdditionalScopes = map_rich_auth_permissions_to_scopes( + ResourceServer#resource_server.id, FilteredPermissionsByType), + + ExistingScopes = get_scope(Payload), + set_scope(AdditionalScopes ++ ExistingScopes, Payload). + +put_location_attribute(Attribute, Map) -> + put_attribute(binary:split(Attribute, <<":">>, [global, trim_all]), Map). + +put_attribute([Key, Value | _], Map) -> + case lists:member(Key, ?RAR_LOCATION_ATTRIBUTES) of + true -> maps:put(Key, Value, Map); + false -> Map + end; +put_attribute([_|_], Map) -> Map. + + +% convert [ <<"cluster:A">>, <<"vhost:B" >>, <<"A">>, <<"unknown:C">> ] to #{ <<"cluster">> : <<"A">>, <<"vhost">> : <<"B">> } +% filtering out non-key-value-pairs and keys which are not part of LOCATION_ATTRIBUTES +convert_attribute_list_to_attribute_map(L) -> + convert_attribute_list_to_attribute_map(L, #{}). +convert_attribute_list_to_attribute_map([H|L],Map) when is_binary(H) -> + convert_attribute_list_to_attribute_map(L, put_location_attribute(H,Map)); +convert_attribute_list_to_attribute_map([], Map) -> Map. + +build_permission_resource_path(Map) -> + Vhost = maps:get(?RAR_VHOST_LOCATION_ATTRIBUTE, Map, <<"*">>), + Resource = maps:get(?RAR_QUEUE_LOCATION_ATTRIBUTE, Map, + maps:get(?RAR_EXCHANGE_LOCATION_ATTRIBUTE, Map, <<"*">>)), + RoutingKey = maps:get(?RAR_ROUTING_KEY_LOCATION_ATTRIBUTE, Map, <<"*">>), + + <>. + +map_locations_to_permission_resource_paths(ResourceServerId, L) -> + Locations = case L of + undefined -> []; + LocationsAsList when is_list(LocationsAsList) -> + lists:map(fun(Location) -> convert_attribute_list_to_attribute_map( + binary:split(Location,<<"/">>,[global,trim_all])) end, LocationsAsList); + LocationsAsBinary when is_binary(LocationsAsBinary) -> + [convert_attribute_list_to_attribute_map( + binary:split(LocationsAsBinary,<<"/">>,[global,trim_all]))] + end, + + FilteredLocations = lists:filtermap(fun(L2) -> + case cluster_matches_resource_server_id(L2, ResourceServerId) and + legal_queue_and_exchange_values(L2) of + true -> { true, build_permission_resource_path(L2) }; + false -> false + end end, Locations), + + FilteredLocations. + +cluster_matches_resource_server_id(#{?RAR_CLUSTER_LOCATION_ATTRIBUTE := Cluster}, + ResourceServerId) -> + wildcard:match(ResourceServerId, Cluster); + +cluster_matches_resource_server_id(_,_) -> + false. + +legal_queue_and_exchange_values(#{?RAR_QUEUE_LOCATION_ATTRIBUTE := Queue, + ?RAR_EXCHANGE_LOCATION_ATTRIBUTE := Exchange}) -> + case Queue of + <<>> -> + case Exchange of + <<>> -> true; + _ -> false + end; + _ -> + case Exchange of + Queue -> true; + _ -> false + end + end; +legal_queue_and_exchange_values(_) -> true. + +map_rich_auth_permissions_to_scopes(ResourceServerId, Permissions) -> + map_rich_auth_permissions_to_scopes(ResourceServerId, Permissions, []). +map_rich_auth_permissions_to_scopes(_, [], Acc) -> Acc; +map_rich_auth_permissions_to_scopes(ResourceServerId, + [ #{?RAR_ACTIONS_FIELD := Actions, ?RAR_LOCATIONS_FIELD := Locations } | T ], Acc) -> + ResourcePaths = map_locations_to_permission_resource_paths(ResourceServerId, Locations), + case ResourcePaths of + [] -> map_rich_auth_permissions_to_scopes(ResourceServerId, T, Acc); + _ -> + Scopes = case Actions of + undefined -> []; + ActionsAsList when is_list(ActionsAsList) -> + build_scopes(ResourceServerId, + skip_unknown_actions(ActionsAsList), ResourcePaths); + ActionsAsBinary when is_binary(ActionsAsBinary) -> + build_scopes(ResourceServerId, + skip_unknown_actions([ActionsAsBinary]), ResourcePaths) + end, + map_rich_auth_permissions_to_scopes(ResourceServerId, T, Acc ++ Scopes) + end. + +skip_unknown_actions(Actions) -> + lists:filter(fun(A) -> lists:member(A, ?RAR_ALLOWED_ACTION_VALUES) end, Actions). + +produce_list_of_user_tag_or_action_on_resources(ResourceServerId, ActionOrUserTag, Locations) -> + case lists:member(ActionOrUserTag, ?RAR_ALLOWED_TAG_VALUES) of + true -> [<< ResourceServerId/binary, ".tag:", ActionOrUserTag/binary >>]; + _ -> build_scopes_for_action(ResourceServerId, ActionOrUserTag, Locations, []) + end. + +build_scopes_for_action(ResourceServerId, Action, [Location|Locations], Acc) -> + Scope = << ResourceServerId/binary, ".", Action/binary, ":", Location/binary >>, + build_scopes_for_action(ResourceServerId, Action, Locations, [ Scope | Acc ] ); +build_scopes_for_action(_, _, [], Acc) -> Acc. + +build_scopes(ResourceServerId, Actions, Locations) -> + lists:flatmap(fun(Action) -> + produce_list_of_user_tag_or_action_on_resources(ResourceServerId, + Action, Locations) end, Actions). + +is_recognized_permission(#{?RAR_ACTIONS_FIELD := _, ?RAR_LOCATIONS_FIELD:= _ , + ?RAR_TYPE_FIELD := Type }, ResourceServerType) -> + case ResourceServerType of + <<>> -> false; + V when V == Type -> true; + _ -> false + end; +is_recognized_permission(_, _) -> false. diff --git a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_resource_server.erl b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_resource_server.erl new file mode 100644 index 000000000000..84675df7c96d --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_resource_server.erl @@ -0,0 +1,240 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(rabbit_oauth2_resource_server). + +-include("oauth2.hrl"). + +-export([ + resolve_resource_server_from_audience/1, + new_resource_server/1 +]). + +-spec new_resource_server(resource_server_id()) -> resource_server(). +new_resource_server(ResourceServerId) -> + #resource_server{ + id = ResourceServerId, + resource_server_type = undefined, + verify_aud = true, + scope_prefix = erlang:iolist_to_binary([ResourceServerId, <<".">>]), + additional_scopes_key = undefined, + preferred_username_claims = ?DEFAULT_PREFERRED_USERNAME_CLAIMS, + scope_aliases = undefined, + oauth_provider_id = root + }. + +-spec resolve_resource_server_from_audience(binary() | list() | none) -> + {ok, resource_server()} | + {error, aud_matched_many_resource_servers_only_one_allowed} | + {error, no_matching_aud_found} | + {error, no_aud_found} | + {error, no_aud_found_cannot_pick_one_from_too_many_resource_servers} | + {error, too_many_resources_with_verify_aud_false}. +resolve_resource_server_from_audience(none) -> + translate_error_if_any( + find_unique_resource_server_without_verify_aud(), false); + +resolve_resource_server_from_audience(Audience) -> + RootResourseServerId = get_root_resource_server_id(), + ResourceServers = get_env(resource_servers, #{}), + ResourceServerIds = maps:fold(fun(K, V, List) -> List ++ + [proplists:get_value(id, V, K)] end, [], ResourceServers), + AllowedResourceServerIds = append(ResourceServerIds, RootResourseServerId), + + case find_audience(Audience, AllowedResourceServerIds) of + {error, aud_matched_many_resource_servers_only_one_allowed} = Error -> + Error; + {error, no_matching_aud_found} -> + translate_error_if_any( + find_unique_resource_server_without_verify_aud(), + true); + {ok, ResourceServerId} -> + {ok, get_resource_server(ResourceServerId)} + end. + +-spec get_root_resource_server_id() -> resource_server_id(). +get_root_resource_server_id() -> + get_env(resource_server_id, <<>>). + +-spec get_root_resource_server() -> resource_server(). +get_root_resource_server() -> + ResourceServerId = + get_root_resource_server_id(), + ScopeAliases = + get_env(scope_aliases), + PreferredUsernameClaims = + case get_env(preferred_username_claims) of + undefined -> ?DEFAULT_PREFERRED_USERNAME_CLAIMS; + Value -> + Value + end, + ResourceServerType = + get_env(resource_server_type), + VerifyAud = + get_boolean_env(verify_aud, true), + AdditionalScopesKey = + get_env(extra_scopes_source), + DefaultScopePrefix = + case ResourceServerId of + <<>> -> undefined; + _ -> erlang:iolist_to_binary([ResourceServerId, <<".">>]) + end, + ScopePrefix = + get_env(scope_prefix, DefaultScopePrefix), + OAuthProviderId = + case get_env(default_oauth_provider) of + undefined -> root; + DefaultOauthProviderId -> DefaultOauthProviderId + end, + + #resource_server{ + id = ResourceServerId, + resource_server_type = ResourceServerType, + verify_aud = VerifyAud, + scope_prefix = ScopePrefix, + additional_scopes_key = AdditionalScopesKey, + preferred_username_claims = PreferredUsernameClaims, + scope_aliases = ScopeAliases, + oauth_provider_id = OAuthProviderId + }. + +-spec get_resource_server(resource_server_id()) -> resource_server() | undefined. +get_resource_server(ResourceServerId) -> + RootResourseServer = get_root_resource_server(), + RootResourseServerId = RootResourseServer#resource_server.id, + case ResourceServerId of + <<>> -> undefined; + RootResourseServerId -> RootResourseServer; + _ -> get_resource_server(ResourceServerId, RootResourseServer) + end. + +-spec get_resource_server(ResourceServerId :: resource_server_id(), + DefaultResourceServerSettings :: resource_server()) -> resource_server(). +get_resource_server(ResourceServerId, RootResourseServer) when + ResourceServerId == RootResourseServer#resource_server.id -> + RootResourseServer; +get_resource_server(ResourceServerId, RootResourseServer) when + ResourceServerId =/= RootResourseServer#resource_server.id -> + ResourceServerProps = + maps:get(ResourceServerId, get_env(resource_servers, #{}), []), + ScopeAliases = + proplists:get_value(scope_aliases, ResourceServerProps, + RootResourseServer#resource_server.scope_aliases), + PreferredUsernameClaims = + proplists:get_value(preferred_username_claims, ResourceServerProps, + RootResourseServer#resource_server.preferred_username_claims), + ResourceServerType = + proplists:get_value(resource_server_type, ResourceServerProps, + RootResourseServer#resource_server.resource_server_type), + VerifyAud = + proplists:get_value(verify_aud, ResourceServerProps, + RootResourseServer#resource_server.verify_aud), + AdditionalScopesKey = + proplists:get_value(extra_scopes_source, ResourceServerProps, + RootResourseServer#resource_server.additional_scopes_key), + RootScopePrefix = get_env(scope_prefix, undefined), + ScopePrefix = + proplists:get_value(scope_prefix, ResourceServerProps, + case RootScopePrefix of + undefined -> erlang:iolist_to_binary([ResourceServerId, <<".">>]); + Prefix -> Prefix + end), + OAuthProviderId = + proplists:get_value(oauth_provider_id, ResourceServerProps, + RootResourseServer#resource_server.oauth_provider_id), + + #resource_server{ + id = ResourceServerId, + resource_server_type = ResourceServerType, + verify_aud = VerifyAud, + scope_prefix = ScopePrefix, + additional_scopes_key = AdditionalScopesKey, + preferred_username_claims = PreferredUsernameClaims, + scope_aliases = ScopeAliases, + oauth_provider_id = OAuthProviderId + }. + +-spec find_audience(binary() | list(), list()) -> + {ok, resource_server_id()} | + {error, aud_matched_many_resource_servers_only_one_allowed} | + {error, no_matching_aud_found}. +find_audience(Audience, ResourceIdList) when is_binary(Audience) -> + AudList = binary:split(Audience, <<" ">>, [global, trim_all]), + find_audience(AudList, ResourceIdList); +find_audience(AudList, ResourceIdList) when is_list(AudList) -> + case intersection(AudList, ResourceIdList) of + [One] -> {ok, One}; + [_One|_Tail] -> {error, aud_matched_many_resource_servers_only_one_allowed}; + [] -> {error, no_matching_aud_found} + end. + +-spec translate_error_if_any( + {ok, resource_server()} | + {error, not_found} | + {error, found_many}, boolean()) -> + {ok, resource_server()} | + {error, no_aud_found} | + {error, no_aud_found_cannot_pick_one_from_too_many_resource_servers} | + {error, no_matching_aud_found} | + {error, too_many_resources_with_verify_aud_false}. +translate_error_if_any(ResourceServerOrError, HasAudience) -> + case {ResourceServerOrError, HasAudience} of + {{ok, _} = Ok, _} -> + Ok; + {{error, not_found}, false} -> + {error, no_aud_found}; + {{error, not_found}, _} -> + {error, no_matching_aud_found}; + {{error, found_many}, false} -> + {error, no_aud_found_cannot_pick_one_from_too_many_resource_servers}; + {{error, found_many}, _} -> + {error, too_many_resources_with_verify_aud_false} + end. +-spec find_unique_resource_server_without_verify_aud() -> + {ok, resource_server()} | + {error, not_found} | + {error, found_many}. +find_unique_resource_server_without_verify_aud() -> + Root = get_root_resource_server(), + Map0 = maps:filter(fun(_K,V) -> not get_boolean_value(verify_aud, V, + Root#resource_server.verify_aud) end, get_env(resource_servers, #{})), + Map = case {Root#resource_server.id, Root#resource_server.verify_aud} of + {<<>>, _} -> Map0; + {_, true} -> Map0; + {Id, false} -> maps:put(Id, Root, Map0) + end, + case maps:size(Map) of + 0 -> {error, not_found}; + 1 -> {ok, get_resource_server(lists:last(maps:keys(Map)), Root)}; + _ -> {error, found_many} + end. + +append(List, Value) -> + case Value of + <<>> -> List; + _ -> List ++ [Value] + end. +get_env(Par) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par, undefined). +get_env(Par, Def) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par, Def). +-spec get_boolean_env(atom(), boolean()) -> boolean(). +get_boolean_env(Par, Def) -> + case get_env(Par, Def) of + true -> true; + false -> false; + _ -> true + end. +-spec get_boolean_value(term(), list(), boolean()) -> boolean(). +get_boolean_value(Key, Proplist, Def) -> + case proplists:get_value(Key, Proplist, Def) of + true -> true; + false -> false; + _ -> true + end. +intersection(List1, List2) -> + [I || I <- List1, lists:member(I, List2)]. diff --git a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_schema.erl b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_schema.erl index 6c1e251dacb6..aa003b570a7e 100644 --- a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_schema.erl +++ b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_schema.erl @@ -7,6 +7,7 @@ -module(rabbit_oauth2_schema). +<<<<<<< HEAD -export([ translate_oauth_providers/1, translate_resource_servers/1, @@ -17,17 +18,147 @@ "additional_scopes_key" => "extra_scopes_source" }). +======= +-define(AUTH_OAUTH2, "auth_oauth2"). +-define(SCOPE_ALIASES, "scope_aliases"). +-define(RESOURCE_SERVERS, "resource_servers"). +-define(OAUTH_PROVIDERS, "oauth_providers"). +-define(SIGNING_KEYS, "signing_keys"). +-define(AUTH_OAUTH2_SCOPE_ALIASES, ?AUTH_OAUTH2 ++ "." ++ ?SCOPE_ALIASES). +-define(AUTH_OAUTH2_RESOURCE_SERVERS, ?AUTH_OAUTH2 ++ "." ++ ?RESOURCE_SERVERS). +-define(AUTH_OAUTH2_OAUTH_PROVIDERS, ?AUTH_OAUTH2 ++ "." ++ ?OAUTH_PROVIDERS). +-define(AUTH_OAUTH2_SIGNING_KEYS, ?AUTH_OAUTH2 ++ "." ++ ?SIGNING_KEYS). +-define(RESOURCE_SERVERS_SYNONYMS, #{ + "additional_scopes_key" => "extra_scopes_source" +}). + +-export([ + translate_oauth_providers/1, + translate_resource_servers/1, + translate_signing_keys/1, + translate_endpoint_params/2, + translate_scope_aliases/1 +]). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) resource_servers_key_synonym(Key) -> maps:get(Key, ?RESOURCE_SERVERS_SYNONYMS, Key). extract_key_as_binary({Name,_}) -> list_to_binary(Name). extract_value({_Name,V}) -> V. +<<<<<<< HEAD -spec translate_resource_servers([{list(), binary()}]) -> map(). translate_resource_servers(Conf) -> Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.resource_servers", Conf), Map = merge_list_of_maps([ extract_resource_server_properties(Settings), extract_resource_server_preferred_username_claims(Settings) +======= +-spec translate_scope_aliases([{list(), binary()}]) -> map(). +translate_scope_aliases(Conf) -> + Settings = cuttlefish_variable:filter_by_prefix( + ?AUTH_OAUTH2_SCOPE_ALIASES, Conf), + maps:merge(extract_scope_alias_as_map(Settings), + extract_scope_aliases_as_list_of_alias_scope_props(Settings)). + +convert_space_separated_string_to_list_of_binaries(String) -> + [ list_to_binary(V) || V <- string:tokens(String, " ")]. + +extract_scope_alias_as_map(Settings) -> + maps:from_list([{ + list_to_binary(Alias), + convert_space_separated_string_to_list_of_binaries(Scope) + } + || {[?AUTH_OAUTH2, ?SCOPE_ALIASES, Alias], Scope} <- Settings ]). + +extract_scope_aliases_as_list_of_alias_scope_props(Settings) -> + KeyFun = fun extract_key_as_binary/1, + ValueFun = fun extract_value/1, + + List0 = [{Index, {list_to_atom(Attr), V}} + || {[?AUTH_OAUTH2, ?SCOPE_ALIASES, Index, Attr], V} <- Settings ], + List1 = maps:to_list(maps:groups_from_list(KeyFun, ValueFun, List0)), + List2 = [extract_scope_alias_mapping(Proplist) || {_, Proplist} <- List1], + maps:from_list([ V || V <- List2, V =/= {}]). + +extract_scope_alias_mapping(Proplist) -> + Alias = + case proplists:get_value(alias, Proplist) of + undefined -> {error, missing_alias_attribute}; + A -> list_to_binary(A) + end, + Scope = + case proplists:get_value(scope, Proplist) of + undefined -> {error, missing_scope_attribute}; + S -> convert_space_separated_string_to_list_of_binaries(S) + end, + case {Alias, Scope} of + {{error, _}, _} -> + cuttlefish:warn( + "Skipped scope_aliases due to missing alias attribute"), + {}; + {_, {error, _}} -> + cuttlefish:warn( + "Skipped scope_aliases due to missing scope attribute"), + {}; + _ = V -> V + end. + +extract_resource_server_scope_aliases_as_list_of_props(Settings) -> + KeyFun = fun extract_key_as_binary/1, + ValueFun = fun extract_value/1, + + List0 = [ + { + Name, + {Index, {list_to_atom(Attr), V}} + } || + {[ + ?AUTH_OAUTH2, ?RESOURCE_SERVERS, Name, ?SCOPE_ALIASES, + Index, Attr + ], V + } <- Settings ], + Map0 = maps:groups_from_list(KeyFun, ValueFun, List0), + + Map4 = maps:map(fun (_, L) -> + Map2 = maps:map(fun (_, L2) -> extract_scope_alias_mapping(L2) end, + maps:groups_from_list(KeyFun, ValueFun, L)), + Map3 = maps:filter(fun (_,V) -> V =/= {} end, Map2), + [{scope_aliases, maps:from_list([ V || {_, V} <- maps:to_list(Map3)])}] + end, Map0), + + Map4. + +extract_resource_server_scope_aliases_as_map(Settings) -> + KeyFun = fun extract_key_as_binary/1, + ValueFun = fun extract_value/1, + + List0 = [ + { + Name, + { + list_to_binary(Alias), + convert_space_separated_string_to_list_of_binaries(Scope) + } + } || + {[ + ?AUTH_OAUTH2, ?RESOURCE_SERVERS, Name, ?SCOPE_ALIASES, + Alias + ], Scope + } <- Settings ], + Map0 = maps:groups_from_list(KeyFun, ValueFun, List0), + maps:map(fun (_, L) -> [{scope_aliases, maps:from_list(L)}] end, Map0). + +-spec translate_resource_servers([{list(), binary()}]) -> map(). +translate_resource_servers(Conf) -> + Settings = cuttlefish_variable:filter_by_prefix( + ?AUTH_OAUTH2_RESOURCE_SERVERS, Conf), + Map = merge_list_of_maps([ + extract_resource_server_properties(Settings), + extract_resource_server_preferred_username_claims(Settings), + extract_resource_server_scope_aliases_as_list_of_props(Settings), + extract_resource_server_scope_aliases_as_map(Settings) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]), Map0 = maps:map(fun(K,V) -> case proplists:get_value(id, V) of @@ -35,6 +166,7 @@ translate_resource_servers(Conf) -> _ -> V end end, Map), ResourceServers = maps:values(Map0), +<<<<<<< HEAD lists:foldl(fun(Elem,AccMap)-> maps:put(proplists:get_value(id, Elem), Elem, AccMap) end, #{}, ResourceServers). @@ -52,6 +184,31 @@ translate_oauth_providers(Conf) -> translate_signing_keys(Conf) -> Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.signing_keys", Conf), ListOfKidPath = lists:map(fun({Id, Path}) -> {list_to_binary(lists:last(Id)), Path} end, Settings), +======= + lists:foldl(fun(Elem,AccMap)-> maps:put(proplists:get_value(id, Elem), + Elem, AccMap) end, #{}, ResourceServers). + +-spec translate_oauth_providers([{list(), binary()}]) -> map(). +translate_oauth_providers(Conf) -> + Settings = cuttlefish_variable:filter_by_prefix( + ?AUTH_OAUTH2_OAUTH_PROVIDERS, Conf), + + merge_list_of_maps([ + extract_oauth_providers_properties(Settings), + extract_oauth_providers_endpoint_params(discovery_endpoint_params, + Settings), + extract_oauth_providers_algorithm(Settings), + extract_oauth_providers_https(Settings), + extract_oauth_providers_signing_keys(Settings) + ]). + +-spec translate_signing_keys([{list(), binary()}]) -> map(). +translate_signing_keys(Conf) -> + Settings = cuttlefish_variable:filter_by_prefix( + ?AUTH_OAUTH2_SIGNING_KEYS, Conf), + ListOfKidPath = lists:map(fun({Id, Path}) -> { + list_to_binary(lists:last(Id)), Path} end, Settings), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) translate_list_of_signing_keys(ListOfKidPath). -spec translate_list_of_signing_keys([{list(), list()}]) -> map(). @@ -62,17 +219,36 @@ translate_list_of_signing_keys(ListOfKidPath) -> {ok, Bin} -> string:trim(Bin, trailing, "\n"); _Error -> +<<<<<<< HEAD %% this throws and makes Cuttlefish treak the key as invalid cuttlefish:invalid("file does not exist or cannot be read by the node") end end, maps:map(fun(_K, Path) -> {pem, TryReadingFileFun(Path)} end, maps:from_list(ListOfKidPath)). +======= + cuttlefish:invalid(io_lib:format( + "File ~p does not exist or cannot be read by the node", + [Path])) + end + end, + maps:map(fun(_K, Path) -> {pem, TryReadingFileFun(Path)} end, + maps:from_list(ListOfKidPath)). + +-spec translate_endpoint_params(list(), [{list(), binary()}]) -> + [{binary(), binary()}]. +translate_endpoint_params(Variable, Conf) -> + Params0 = cuttlefish_variable:filter_by_prefix("auth_oauth2." ++ Variable, + Conf), + [{list_to_binary(Param), list_to_binary(V)} || {["auth_oauth2", _, Param], V} + <- Params0]. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) validator_file_exists(Attr, Filename) -> case file:read_file(Filename) of {ok, _} -> Filename; _Error -> +<<<<<<< HEAD %% this throws and makes Cuttlefish treak the key as invalid cuttlefish:invalid(io_lib:format( "Invalid attribute (~p) value: file ~p does not exist or cannot be read by the node", [Attr, Filename])) @@ -81,31 +257,77 @@ validator_https_uri(Attr, Uri) when is_binary(Uri) -> list_to_binary(validator_https_uri(Attr, binary_to_list(Uri))); validator_https_uri(Attr, Uri) -> +======= + cuttlefish:invalid(io_lib:format( + "Invalid attribute (~p) value: file ~p does not exist or " ++ + "cannot be read by the node", [Attr, Filename])) + end. + +validator_uri(Attr, Uri) when is_binary(Uri) -> + validator_uri(Attr, binary_to_list(Uri)); +validator_uri(Attr, Uri) when is_list(Uri) -> + case uri_string:parse(Uri) of + {error, _, _} = Error -> + cuttlefish:invalid(io_lib:format( + "Invalid attribute (~p) value: ~p (~p)", [Attr, Uri, Error])); + _ -> Uri + end. + +validator_https_uri(Attr, Uri) when is_binary(Uri) -> + validator_https_uri(Attr, binary_to_list(Uri)); + +validator_https_uri(Attr, Uri) when is_list(Uri) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case string:nth_lexeme(Uri, 1, "://") == "https" of true -> Uri; false -> cuttlefish:invalid(io_lib:format( +<<<<<<< HEAD "Invalid attribute (~p) value: uri ~p must be a valid https uri", [Attr, Uri])) end. merge_list_of_maps(ListOfMaps) -> lists:foldl(fun(Elem, AccIn) -> maps:merge_with(fun(_K,V1,V2) -> V1 ++ V2 end, Elem, AccIn) end, #{}, ListOfMaps). +======= + "Invalid attribute (~p) value: uri ~p must be a valid " ++ + "https uri", [Attr, Uri])) + end. + +merge_list_of_maps(ListOfMaps) -> + lists:foldl(fun(Elem, AccIn) -> maps:merge_with( + fun(_K,V1,V2) -> V1 ++ V2 end, Elem, AccIn) end, #{}, ListOfMaps). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) extract_oauth_providers_properties(Settings) -> KeyFun = fun extract_key_as_binary/1, ValueFun = fun extract_value/1, +<<<<<<< HEAD OAuthProviders = [{Name, mapOauthProviderProperty({list_to_atom(Key), list_to_binary(V)})} || {["auth_oauth2","oauth_providers", Name, Key], V} <- Settings ], maps:groups_from_list(KeyFun, ValueFun, OAuthProviders). +======= + OAuthProviders = [{Name, mapOauthProviderProperty( + { + list_to_atom(Key), + list_to_binary(V)}) + } || {[?AUTH_OAUTH2, ?OAUTH_PROVIDERS, Name, Key], V} <- Settings ], + maps:groups_from_list(KeyFun, ValueFun, OAuthProviders). + + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) extract_resource_server_properties(Settings) -> KeyFun = fun extract_key_as_binary/1, ValueFun = fun extract_value/1, OAuthProviders = [{Name, {list_to_atom(resource_servers_key_synonym(Key)), list_to_binary(V)}} +<<<<<<< HEAD || {["auth_oauth2","resource_servers", Name, Key], V} <- Settings ], +======= + || {[?AUTH_OAUTH2, ?RESOURCE_SERVERS, Name, Key], V} <- Settings ], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) maps:groups_from_list(KeyFun, ValueFun, OAuthProviders). mapOauthProviderProperty({Key, Value}) -> @@ -115,6 +337,14 @@ mapOauthProviderProperty({Key, Value}) -> jwks_uri -> validator_https_uri(Key, Value); end_session_endpoint -> validator_https_uri(Key, Value); authorization_endpoint -> validator_https_uri(Key, Value); +<<<<<<< HEAD +======= + discovery_endpoint_path -> validator_uri(Key, Value); + discovery_endpoint_params -> + cuttlefish:invalid(io_lib:format( + "Invalid attribute (~p) value: should be a map of Key,Value pairs", + [Key])); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) _ -> Value end}. @@ -122,10 +352,18 @@ extract_oauth_providers_https(Settings) -> ExtractProviderNameFun = fun extract_key_as_binary/1, AttributesPerProvider = [{Name, mapHttpProperty({list_to_atom(Key), V})} || +<<<<<<< HEAD {["auth_oauth2","oauth_providers", Name, "https", Key], V} <- Settings ], maps:map(fun(_K,V)-> [{https, V}] end, maps:groups_from_list(ExtractProviderNameFun, fun({_, V}) -> V end, AttributesPerProvider)). +======= + {[?AUTH_OAUTH2, ?OAUTH_PROVIDERS, Name, "https", Key], V} <- Settings ], + + maps:map(fun(_K,V)-> [{https, V}] end, + maps:groups_from_list(ExtractProviderNameFun, fun({_, V}) -> V end, + AttributesPerProvider)). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) mapHttpProperty({Key, Value}) -> {Key, case Key of @@ -137,8 +375,15 @@ extract_oauth_providers_algorithm(Settings) -> KeyFun = fun extract_key_as_binary/1, IndexedAlgorithms = [{Name, {Index, list_to_binary(V)}} || +<<<<<<< HEAD {["auth_oauth2","oauth_providers", Name, "algorithms", Index], V} <- Settings ], SortedAlgorithms = lists:sort(fun({_,{AI,_}},{_,{BI,_}}) -> AI < BI end, IndexedAlgorithms), +======= + {[?AUTH_OAUTH2, ?OAUTH_PROVIDERS, Name, "algorithms", Index], V} + <- Settings ], + SortedAlgorithms = lists:sort(fun({_,{AI,_}},{_,{BI,_}}) -> AI < BI end, + IndexedAlgorithms), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Algorithms = [{Name, V} || {Name, {_I, V}} <- SortedAlgorithms], maps:map(fun(_K,V)-> [{algorithms, V}] end, maps:groups_from_list(KeyFun, fun({_, V}) -> V end, Algorithms)). @@ -147,16 +392,40 @@ extract_resource_server_preferred_username_claims(Settings) -> KeyFun = fun extract_key_as_binary/1, IndexedClaims = [{Name, {Index, list_to_binary(V)}} || +<<<<<<< HEAD {["auth_oauth2","resource_servers", Name, "preferred_username_claims", Index], V} <- Settings ], SortedClaims = lists:sort(fun({_,{AI,_}},{_,{BI,_}}) -> AI < BI end, IndexedClaims), +======= + {[?AUTH_OAUTH2, ?RESOURCE_SERVERS, Name, "preferred_username_claims", + Index], V} <- Settings ], + SortedClaims = lists:sort(fun({_,{AI,_}},{_,{BI,_}}) -> AI < BI end, + IndexedClaims), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Claims = [{Name, V} || {Name, {_I, V}} <- SortedClaims], maps:map(fun(_K,V)-> [{preferred_username_claims, V}] end, maps:groups_from_list(KeyFun, fun({_, V}) -> V end, Claims)). +<<<<<<< HEAD +======= +extract_oauth_providers_endpoint_params(Variable, Settings) -> + KeyFun = fun extract_key_as_binary/1, + + IndexedParams = [{Name, {list_to_binary(ParamName), list_to_binary(V)}} || + {["auth_oauth2","oauth_providers", Name, EndpointVar, ParamName], V} + <- Settings, EndpointVar == atom_to_list(Variable) ], + maps:map(fun(_K,V)-> [{Variable, V}] end, + maps:groups_from_list(KeyFun, fun({_, V}) -> V end, IndexedParams)). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) extract_oauth_providers_signing_keys(Settings) -> KeyFun = fun extract_key_as_binary/1, IndexedSigningKeys = [{Name, {list_to_binary(Kid), list_to_binary(V)}} || +<<<<<<< HEAD {["auth_oauth2","oauth_providers", Name, "signing_keys", Kid], V} <- Settings ], +======= + {[?AUTH_OAUTH2, ?OAUTH_PROVIDERS, Name, ?SIGNING_KEYS, Kid], V} + <- Settings ], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) maps:map(fun(_K,V)-> [{signing_keys, translate_list_of_signing_keys(V)}] end, maps:groups_from_list(KeyFun, fun({_, V}) -> V end, IndexedSigningKeys)). diff --git a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_scope.erl b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_scope.erl index d81c7ded0c8f..354b041baf79 100644 --- a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_scope.erl +++ b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_scope.erl @@ -7,7 +7,15 @@ -module(rabbit_oauth2_scope). +<<<<<<< HEAD -export([vhost_access/2, resource_access/3, topic_access/4, concat_scopes/2]). +======= +-export([vhost_access/2, + resource_access/3, + topic_access/4, + concat_scopes/2, + filter_matching_scope_prefix_and_drop_it/2]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -include_lib("rabbit_common/include/rabbit.hrl"). @@ -88,3 +96,23 @@ parse_resource_pattern(Pattern, Permission) -> {VhostPattern, NamePattern, RoutingKeyPattern, Permission}; _Other -> ignore end. +<<<<<<< HEAD +======= + +-spec filter_matching_scope_prefix_and_drop_it(list(), binary()|list()) -> list(). +filter_matching_scope_prefix_and_drop_it(Scopes, <<"">>) -> Scopes; +filter_matching_scope_prefix_and_drop_it(Scopes, PrefixPattern) -> + PatternLength = byte_size(PrefixPattern), + lists:filtermap( + fun(ScopeEl) -> + case binary:match(ScopeEl, PrefixPattern) of + {0, PatternLength} -> + ElLength = byte_size(ScopeEl), + {true, + binary:part(ScopeEl, + {PatternLength, ElLength - PatternLength})}; + _ -> false + end + end, + Scopes). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwks.erl b/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwks.erl index edd81902da15..d58eb8fd3943 100644 --- a/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwks.erl +++ b/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwks.erl @@ -1,7 +1,11 @@ -module(uaa_jwks). -export([get/2]). +<<<<<<< HEAD -spec get(string() | binary(), term()) -> {ok, term()} | {error, term()}. +======= +-spec get(uri_string:uri_string(), list()) -> {ok, term()} | {error, term()}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) get(JwksUrl, SslOptions) -> Options = [{timeout, 60000}] ++ [{ssl, SslOptions}], httpc:request(get, {JwksUrl, []}, Options, []). diff --git a/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt.erl b/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt.erl index cf14486c6ead..75028d9e17a5 100644 --- a/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt.erl +++ b/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt.erl @@ -7,6 +7,7 @@ -module(uaa_jwt). -export([add_signing_key/3, +<<<<<<< HEAD decode_and_verify/1, get_jwk/2, resolve_resource_server_id/1, @@ -18,6 +19,30 @@ -include_lib("oauth2_client/include/oauth2_client.hrl"). -define(APP, rabbitmq_auth_backend_oauth2). +======= + decode_and_verify/3, + get_jwk/2, + verify_signing_key/2, + resolve_resource_server/1]). + +-export([client_id/1, sub/1, client_id/2, sub/2, get_scope/1, set_scope/2]). + +-include("oauth2.hrl"). +-include_lib("jose/include/jose_jwk.hrl"). + +-import(rabbit_data_coercion, [ + to_map/1]). +-import(oauth2_client, [ + format_ssl_options/1, + format_oauth_provider_id/1, + get_oauth_provider/2]). +-import(rabbit_oauth2_resource_server, [ + resolve_resource_server_from_audience/1]). +-import(rabbit_oauth2_provider, [ + add_signing_key/2, get_signing_key/2, + get_internal_oauth_provider/1, + replace_signing_keys/2]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -type key_type() :: json | pem | map. @@ -25,7 +50,11 @@ add_signing_key(KeyId, Type, Value) -> case verify_signing_key(Type, Value) of ok -> +<<<<<<< HEAD {ok, rabbit_oauth2_config:add_signing_key(KeyId, {Type, Value})}; +======= + {ok, add_signing_key(KeyId, {Type, Value})}; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {error, _} = Err -> Err end. @@ -33,20 +62,31 @@ add_signing_key(KeyId, Type, Value) -> -spec update_jwks_signing_keys(oauth_provider()) -> ok | {error, term()}. update_jwks_signing_keys(#oauth_provider{id = Id, jwks_uri = JwksUrl, ssl_options = SslOptions}) -> +<<<<<<< HEAD rabbit_log:debug("OAuth 2 JWT: downloading keys from ~tp (TLS options: ~p)", [JwksUrl, SslOptions]), +======= + rabbit_log:debug("Downloading signing keys from ~tp (TLS options: ~p)", + [JwksUrl, format_ssl_options(SslOptions)]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case uaa_jwks:get(JwksUrl, SslOptions) of {ok, {_, _, JwksBody}} -> KeyList = maps:get(<<"keys">>, jose:decode(erlang:iolist_to_binary(JwksBody)), []), Keys = maps:from_list(lists:map(fun(Key) -> {maps:get(<<"kid">>, Key, undefined), {json, Key}} end, KeyList)), +<<<<<<< HEAD rabbit_log:debug("OAuth 2 JWT: downloaded keys ~tp", [Keys]), case rabbit_oauth2_config:replace_signing_keys(Keys, Id) of +======= + rabbit_log:debug("Downloaded ~p signing keys", [maps:size(Keys)]), + case replace_signing_keys(Keys, Id) of +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {error, _} = Err -> Err; _ -> ok end; {error, _} = Err -> +<<<<<<< HEAD rabbit_log:error("OAuth 2 JWT: failed to download keys: ~tp", [Err]), Err end. @@ -113,11 +153,81 @@ get_jwk(KeyId, OAuthProviderId, AllowUpdateJwks) -> ok -> get_jwk(KeyId, OAuthProviderId, false); {error, no_jwks_url} -> +======= + rabbit_log:error("Failed to download signing keys: ~tp", [Err]), + Err + end. + +-spec decode_and_verify(binary(), resource_server(), internal_oauth_provider()) + -> {boolean(), map()} | {error, term()}. +decode_and_verify(Token, ResourceServer, InternalOAuthProvider) -> + OAuthProviderId = InternalOAuthProvider#internal_oauth_provider.id, + rabbit_log:debug("Decoding token for resource_server: ~p using oauth_provider_id: ~p", + [ResourceServer#resource_server.id, + format_oauth_provider_id(OAuthProviderId)]), + Result = case uaa_jwt_jwt:get_key_id(Token) of + undefined -> InternalOAuthProvider#internal_oauth_provider.default_key; + {ok, KeyId0} -> KeyId0; + {error, _} = Err -> Err + end, + case Result of + {error, _} = Err2 -> + Err2; + KeyId -> + case get_jwk(KeyId, InternalOAuthProvider) of + {ok, JWK} -> + Algorithms = InternalOAuthProvider#internal_oauth_provider.algorithms, + rabbit_log:debug("Verifying signature using signing_key_id : '~tp' and algorithms: ~p", + [KeyId, Algorithms]), + uaa_jwt_jwt:decode_and_verify(Algorithms, JWK, Token); + {error, _} = Err3 -> + Err3 + end + end. + +-spec resolve_resource_server(binary()|map()) -> {error, term()} | + {resource_server(), internal_oauth_provider()}. +resolve_resource_server(DecodedToken) when is_map(DecodedToken) -> + Aud = maps:get(?AUD_JWT_FIELD, DecodedToken, none), + resolve_resource_server_given_audience(Aud); +resolve_resource_server(Token) -> + case uaa_jwt_jwt:get_aud(Token) of + {error, _} = Error -> Error; + {ok, Audience} -> resolve_resource_server_given_audience(Audience) + end. +resolve_resource_server_given_audience(Audience) -> + case resolve_resource_server_from_audience(Audience) of + {error, _} = Error -> + Error; + {ok, ResourceServer} -> + {ResourceServer, get_internal_oauth_provider( + ResourceServer#resource_server.oauth_provider_id)} + end. + +-spec get_jwk(binary(), internal_oauth_provider()) -> {ok, map()} | {error, term()}. +get_jwk(KeyId, InternalOAuthProvider) -> + get_jwk(KeyId, InternalOAuthProvider, true). + +get_jwk(KeyId, InternalOAuthProvider, AllowUpdateJwks) -> + OAuthProviderId = InternalOAuthProvider#internal_oauth_provider.id, + case get_signing_key(KeyId, OAuthProviderId) of + undefined -> + case AllowUpdateJwks of + true -> + rabbit_log:debug("Signing key '~tp' not found. Downloading it... ", [KeyId]), + case get_oauth_provider(OAuthProviderId, [jwks_uri]) of + {ok, OAuthProvider} -> + case update_jwks_signing_keys(OAuthProvider) of + ok -> + get_jwk(KeyId, InternalOAuthProvider, false); + {error, no_jwks_uri} -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {error, key_not_found}; {error, _} = Err -> Err end; {error, _} = Error -> +<<<<<<< HEAD rabbit_log:debug("OAuth 2 JWT: unable to download keys due to ~p", [Error]), Error end; @@ -127,6 +237,17 @@ get_jwk(KeyId, OAuthProviderId, AllowUpdateJwks) -> end; {Type, Value} -> rabbit_log:debug("OAuth 2 JWT: signing key found: '~tp', '~tp'", [Type, Value]), +======= + rabbit_log:debug("Unable to download signing keys due to ~p", [Error]), + Error + end; + false -> + rabbit_log:debug("Signing key '~tp' not found. Downloading is not allowed", [KeyId]), + {error, key_not_found} + end; + {Type, Value} -> + rabbit_log:debug("Signing key ~p found", [KeyId]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case Type of json -> uaa_jwt_jwk:make_jwk(Value); pem -> uaa_jwt_jwk:from_pem(Value); @@ -153,6 +274,16 @@ verify_signing_key(Type, Value) -> Err -> Err end. +<<<<<<< HEAD +======= +-spec get_scope(map()) -> binary() | list(). +get_scope(#{?SCOPE_JWT_FIELD := Scope}) -> Scope; +get_scope(#{}) -> []. + +-spec set_scope(list(), map()) -> map(). +set_scope(Scopes, DecodedToken) -> + DecodedToken#{?SCOPE_JWT_FIELD => Scopes}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -spec client_id(map()) -> binary() | undefined. client_id(DecodedToken) -> diff --git a/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt_jwt.erl b/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt_jwt.erl index 7d8c37457028..f6317b44ccf5 100644 --- a/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt_jwt.erl +++ b/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt_jwt.erl @@ -6,29 +6,50 @@ %% -module(uaa_jwt_jwt). +<<<<<<< HEAD -export([decode_and_verify/3, get_key_id/2, get_aud/1]). +======= +-export([decode_and_verify/3, get_key_id/1, get_aud/1]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -include_lib("jose/include/jose_jwt.hrl"). -include_lib("jose/include/jose_jws.hrl"). +<<<<<<< HEAD decode_and_verify(OauthProviderId, Jwk, Token) -> Verify = case rabbit_oauth2_config:get_algorithms(OauthProviderId) of undefined -> jose_jwt:verify(Jwk, Token); Algs -> jose_jwt:verify_strict(Jwk, Algs, Token) end, +======= +-spec decode_and_verify(list() | undefined, map(), binary()) -> {boolean(), map()}. +decode_and_verify(Algs, Jwk, Token) -> + Verify = case Algs of + undefined -> jose_jwt:verify(Jwk, Token); + _ -> jose_jwt:verify_strict(Jwk, Algs, Token) + end, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) case Verify of {true, #jose_jwt{fields = Fields}, _} -> {true, Fields}; {false, #jose_jwt{fields = Fields}, _} -> {false, Fields} end. +<<<<<<< HEAD get_key_id(DefaultKey, Token) -> try case jose_jwt:peek_protected(Token) of #jose_jws{fields = #{<<"kid">> := Kid}} -> {ok, Kid}; #jose_jws{} -> DefaultKey +======= +get_key_id(Token) -> + try + case jose_jwt:peek_protected(Token) of + #jose_jws{fields = #{<<"kid">> := Kid}} -> {ok, Kid}; + #jose_jws{} -> undefined +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end catch Type:Err:Stacktrace -> {error, {invalid_token, Type, Err, Stacktrace}} diff --git a/deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets b/deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets index 08ecdb9dec77..652aa0e6e22f 100644 --- a/deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets +++ b/deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets @@ -11,6 +11,10 @@ auth_oauth2.default_key = id1 auth_oauth2.signing_keys.id1 = test/config_schema_SUITE_data/certs/key.pem auth_oauth2.signing_keys.id2 = test/config_schema_SUITE_data/certs/cert.pem +<<<<<<< HEAD +======= + auth_oauth2.jwks_uri = https://my-jwt-issuer/jwks.json +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) auth_oauth2.jwks_url = https://my-jwt-issuer/jwks.json auth_oauth2.issuer = https://my-jwt-issuer auth_oauth2.https.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem @@ -18,6 +22,11 @@ auth_oauth2.https.depth = 5 auth_oauth2.https.fail_if_no_peer_cert = false auth_oauth2.https.hostname_verification = wildcard +<<<<<<< HEAD +======= + auth_oauth2.discovery_endpoint_path = /.well-known/openid-configuration + auth_oauth2.discovery_endpoint_params.param1 = value1 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) auth_oauth2.https.crl_check = true auth_oauth2.algorithms.1 = HS256 auth_oauth2.algorithms.2 = RS256", @@ -30,6 +39,14 @@ {preferred_username_claims, [<<"user_name">>, <<"username">>, <<"email">>]}, {verify_aud, true}, {issuer, "https://my-jwt-issuer"}, +<<<<<<< HEAD +======= + {discovery_endpoint_path, "/.well-known/openid-configuration"}, + {discovery_endpoint_params, [ + {<<"param1">>, <<"value1">>} + ]}, + {jwks_uri, "https://my-jwt-issuer/jwks.json"}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {key_config, [ {default_key, <<"id1">>}, {signing_keys, @@ -63,6 +80,10 @@ auth_oauth2.default_key = id1 auth_oauth2.signing_keys.id1 = test/config_schema_SUITE_data/certs/key.pem auth_oauth2.signing_keys.id2 = test/config_schema_SUITE_data/certs/cert.pem +<<<<<<< HEAD +======= + auth_oauth2.jwks_uri = https://my-jwt-issuer/jwks.json +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) auth_oauth2.jwks_url = https://my-jwt-issuer/jwks.json auth_oauth2.https.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem auth_oauth2.https.peer_verification = verify_none @@ -84,6 +105,10 @@ {extra_scopes_source, <<"my_custom_scope_key">>}, {preferred_username_claims, [<<"user_name">>, <<"username">>, <<"email">>]}, {verify_aud, true}, +<<<<<<< HEAD +======= + {jwks_uri, "https://my-jwt-issuer/jwks.json"}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {resource_servers, #{ <<"rabbitmq-operations">> => [ @@ -136,6 +161,11 @@ auth_oauth2.oauth_providers.keycloak.https.depth = 2 auth_oauth2.oauth_providers.keycloak.default_key = token-key auth_oauth2.oauth_providers.keycloak.signing_keys.id1 = test/config_schema_SUITE_data/certs/key.pem +<<<<<<< HEAD +======= + auth_oauth2.oauth_providers.keycloak.discovery_endpoint_path = /.well-known/openid-configuration + auth_oauth2.oauth_providers.keycloak.discovery_endpoint_params.param1 = value1 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) auth_oauth2.oauth_providers.keycloak.algorithms.1 = HS256 auth_oauth2.oauth_providers.keycloak.algorithms.2 = RS256", [ @@ -160,6 +190,7 @@ {cacertfile, "test/config_schema_SUITE_data/certs/cacert.pem"} ]}, {algorithms, [<<"HS256">>, <<"RS256">>]}, +<<<<<<< HEAD {default_key, <<"token-key">>}, {end_session_endpoint, <<"https://keycloak/logout">>}, {authorization_endpoint, <<"https://keycloak/authorize">>}, @@ -168,6 +199,20 @@ ], <<"uaa">> => [ {issuer, <<"https://uaa">>} +======= + {discovery_endpoint_params, [ + {<<"param1">>, <<"value1">>} + ]}, + {discovery_endpoint_path, "/.well-known/openid-configuration"}, + {default_key, <<"token-key">>}, + {end_session_endpoint, "https://keycloak/logout"}, + {authorization_endpoint, "https://keycloak/authorize"}, + {jwks_uri, "https://keycloak/keys"}, + {token_endpoint, "https://keycloak/token"} + ], + <<"uaa">> => [ + {issuer, "https://uaa"} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ] } @@ -184,5 +229,124 @@ {scope_prefix,<<>>} ]} ],[] +<<<<<<< HEAD +======= + }, + {scope_aliases_1, + "auth_oauth2.resource_server_id = new_resource_server_id + auth_oauth2.scope_aliases.admin = rabbitmq.tag:administrator + auth_oauth2.scope_aliases.developer = rabbitmq.tag:management rabbitmq.read:*/*", + [ + {rabbitmq_auth_backend_oauth2, [ + {resource_server_id,<<"new_resource_server_id">>}, + {scope_aliases, #{ + <<"admin">> => [ + <<"rabbitmq.tag:administrator">> + ], + <<"developer">> => [ + <<"rabbitmq.tag:management">>, + <<"rabbitmq.read:*/*">> + ] + }} + ]} + ], [] + }, + {scope_aliases_2, + "auth_oauth2.resource_server_id = new_resource_server_id + auth_oauth2.scope_aliases.1.alias = admin + auth_oauth2.scope_aliases.1.scope = rabbitmq.tag:administrator + auth_oauth2.scope_aliases.2.alias = developer + auth_oauth2.scope_aliases.2.scope = rabbitmq.tag:management rabbitmq.read:*/*", + [ + {rabbitmq_auth_backend_oauth2, [ + {resource_server_id,<<"new_resource_server_id">>}, + {scope_aliases, #{ + <<"admin">> => [ + <<"rabbitmq.tag:administrator">> + ], + <<"developer">> => [ + <<"rabbitmq.tag:management">>, + <<"rabbitmq.read:*/*">> + ] + }} + ]} + ], [] + }, + {scope_aliases_3, + "auth_oauth2.resource_server_id = new_resource_server_id + auth_oauth2.resource_servers.a.scope_aliases.admin = rabbitmq.tag:administrator + auth_oauth2.resource_servers.a.scope_aliases.developer = rabbitmq.tag:management rabbitmq.read:*/* + auth_oauth2.resource_servers.b.scope_aliases.admin_b = rabbitmq.tag:administrator + auth_oauth2.resource_servers.b.scope_aliases.developer_b = rabbitmq.tag:management rabbitmq.read:*/*", + [ + {rabbitmq_auth_backend_oauth2, [ + {resource_server_id,<<"new_resource_server_id">>}, + {resource_servers, #{ + <<"a">> => [ + {scope_aliases, #{ + <<"admin">> => [ + <<"rabbitmq.tag:administrator">> + ], + <<"developer">> => [ + <<"rabbitmq.tag:management">>, + <<"rabbitmq.read:*/*">> + ] + }}, + {id, <<"a">>} + ], + <<"b">> => [ + {scope_aliases, #{ + <<"admin_b">> => [ + <<"rabbitmq.tag:administrator">> + ], + <<"developer_b">> => [ + <<"rabbitmq.tag:management">>, + <<"rabbitmq.read:*/*">> + ] + }}, + {id, <<"b">>} + ] + } + } + ]} + ], [] + }, + {scope_aliases_4, + "auth_oauth2.resource_server_id = new_resource_server_id + auth_oauth2.resource_servers.b.scope_aliases.1.alias = admin_b + auth_oauth2.resource_servers.b.scope_aliases.1.scope = rabbitmq.tag:administrator + auth_oauth2.resource_servers.a.scope_aliases.1.alias = admin + auth_oauth2.resource_servers.a.scope_aliases.1.scope = rabbitmq.tag:administrator + auth_oauth2.resource_servers.a.scope_aliases.2.alias = developer + auth_oauth2.resource_servers.a.scope_aliases.2.scope = rabbitmq.tag:management rabbitmq.read:*/*", + [ + {rabbitmq_auth_backend_oauth2, [ + {resource_server_id,<<"new_resource_server_id">>}, + {resource_servers, #{ + <<"a">> => [ + {scope_aliases, #{ + <<"admin">> => [ + <<"rabbitmq.tag:administrator">> + ], + <<"developer">> => [ + <<"rabbitmq.tag:management">>, + <<"rabbitmq.read:*/*">> + ] + }}, + {id, <<"a">>} + ], + <<"b">> => [ + {scope_aliases, #{ + <<"admin_b">> => [ + <<"rabbitmq.tag:administrator">> + ] + }}, + {id, <<"b">>} + ] + } + } + ]} + ], [] +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) } ]. diff --git a/deps/rabbitmq_auth_backend_oauth2/test/jwks_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/jwks_SUITE.erl index bc1256da8b9d..bac2f863d6cd 100644 --- a/deps/rabbitmq_auth_backend_oauth2/test/jwks_SUITE.erl +++ b/deps/rabbitmq_auth_backend_oauth2/test/jwks_SUITE.erl @@ -13,14 +13,33 @@ -include_lib("amqp_client/include/amqp_client.hrl"). -include_lib("eunit/include/eunit.hrl"). +<<<<<<< HEAD -import(rabbit_ct_client_helpers, [close_connection/1, close_channel/1, open_unmanaged_connection/4, open_unmanaged_connection/5, close_connection_and_channel/2]). -import(rabbit_mgmt_test_util, [amqp_port/1]). +======= +-import(rabbit_ct_client_helpers, [ + close_connection/1, + close_channel/1, + open_unmanaged_connection/4, + open_unmanaged_connection/5, + close_connection_and_channel/2 +]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -import(rabbit_ct_helpers, [ set_config/2, get_config/2, get_config/3 ]). +<<<<<<< HEAD +======= +-import(rabbit_ct_broker_helpers, [ + rpc/5 +]). +-import(rabbit_mgmt_test_util, [ + amqp_port/1 +]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) all() -> [ @@ -164,6 +183,7 @@ end_per_suite(Config) -> ] ++ rabbit_ct_broker_helpers:teardown_steps()). init_per_group(no_peer_verification, Config) -> +<<<<<<< HEAD KeyConfig = rabbit_ct_helpers:set_config(?config(key_config, Config), [{jwks_url, ?config(non_strict_jwks_url, Config)}, {peer_verification, verify_none}]), ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, key_config, KeyConfig]), rabbit_ct_helpers:set_config(Config, {key_config, KeyConfig}); @@ -180,6 +200,23 @@ init_per_group(with_resource_servers_rabbitmq1_with_oauth_provider_A, Config) -> ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, resource_servers, ResourceServersConfig1]); +======= + KeyConfig = set_config(?config(key_config, Config), [ + {jwks_url, ?config(non_strict_jwks_uri, Config)}, + {peer_verification, verify_none} + ]), + ok = rpc_set_env(Config, key_config, KeyConfig), + set_config(Config, {key_config, KeyConfig}); +init_per_group(without_kid, Config) -> + set_config(Config, [{include_kid, false}]); +init_per_group(with_resource_servers_rabbitmq1_with_oauth_provider_A, Config) -> + ResourceServersConfig0 = rpc_get_env(Config, resource_servers, #{}), + Resource0 = maps:get(<<"rabbitmq1">>, ResourceServersConfig0, + [{id, <<"rabbitmq1">>}]), + ResourceServersConfig1 = maps:put(<<"rabbitmq1">>, + [{oauth_provider_id, <<"A">>} | Resource0], ResourceServersConfig0), + ok = rpc_set_env(Config, resource_servers, ResourceServersConfig1); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) init_per_group(with_oauth_providers_A_B_and_C, Config) -> OAuthProviders = #{ <<"A">> => [ @@ -195,6 +232,7 @@ init_per_group(with_oauth_providers_A_B_and_C, Config) -> {https, [{verify, verify_none}]} ] }, +<<<<<<< HEAD ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders]), Config; @@ -235,11 +273,44 @@ init_per_group(with_resource_servers_rabbitmq2, Config) -> init_per_group(with_oauth_providers_B_with_default_key_static_key, Config) -> {ok, OAuthProviders0} = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, [rabbitmq_auth_backend_oauth2, oauth_providers]), +======= + ok = rpc_set_env(Config, oauth_providers, OAuthProviders), + Config; +init_per_group(with_default_oauth_provider_B, Config) -> + ok = rpc_set_env(Config, default_oauth_provider, <<"B">>); +init_per_group(with_oauth_providers_A_with_default_key, Config) -> + {ok, OAuthProviders0} = rpc_get_env(Config, oauth_providers), + OAuthProvider = maps:get(<<"A">>, OAuthProviders0, []), + OAuthProviders1 = maps:put(<<"A">>, [ + {default_key, ?UTIL_MOD:token_key(?config(fixture_jwksA, Config))} + | OAuthProvider], OAuthProviders0), + ok = rpc_set_env(Config, oauth_providers, OAuthProviders1), + Config; +init_per_group(with_oauth_provider_A_with_jwks_with_one_signing_key, Config) -> + {ok, OAuthProviders0} = rpc_get_env(Config, oauth_providers), + OAuthProvider = maps:get(<<"A">>, OAuthProviders0, []), + OAuthProviders1 = maps:put(<<"A">>, [ + {jwks_uri, strict_jwks_uri(Config, "/jwksA")} | OAuthProvider], + OAuthProviders0), + ok = rpc_set_env(Config, oauth_providers, OAuthProviders1), + Config; +init_per_group(with_resource_servers_rabbitmq2, Config) -> + ResourceServersConfig0 = rpc_get_env(Config, resource_servers, #{}), + Resource0 = maps:get(<<"rabbitmq2">>, ResourceServersConfig0, + [{id, <<"rabbitmq2">>}]), + ResourceServersConfig1 = maps:put(<<"rabbitmq2">>, Resource0, + ResourceServersConfig0), + ok = rpc_set_env(Config, resource_servers, ResourceServersConfig1), + Config; +init_per_group(with_oauth_providers_B_with_default_key_static_key, Config) -> + {ok, OAuthProviders0} = rpc_get_env(Config, oauth_providers), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) OAuthProvider = maps:get(<<"B">>, OAuthProviders0, []), OAuthProviders1 = maps:put(<<"B">>, [ {default_key, ?UTIL_MOD:token_key(?config(fixture_staticB, Config))} | proplists:delete(default_key, OAuthProvider)], OAuthProviders0), +<<<<<<< HEAD ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders1]), @@ -247,6 +318,12 @@ init_per_group(with_oauth_providers_B_with_default_key_static_key, Config) -> init_per_group(with_oauth_provider_C_with_two_static_keys, Config) -> {ok, OAuthProviders0} = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, [rabbitmq_auth_backend_oauth2, oauth_providers]), +======= + ok = rpc_set_env(Config,oauth_providers, OAuthProviders1), + Config; +init_per_group(with_oauth_provider_C_with_two_static_keys, Config) -> + {ok, OAuthProviders0} = rpc_get_env(Config, oauth_providers), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) OAuthProvider = maps:get(<<"C">>, OAuthProviders0, []), Jwks1 = ?config(fixture_staticC_1, Config), Jwks2 = ?config(fixture_staticC_2, Config), @@ -254,6 +331,7 @@ init_per_group(with_oauth_provider_C_with_two_static_keys, Config) -> ?UTIL_MOD:token_key(Jwks1) => {json, Jwks1}, ?UTIL_MOD:token_key(Jwks2) => {json, Jwks2} }, +<<<<<<< HEAD OAuthProviders1 = maps:put(<<"C">>, [{signing_keys, SigningKeys} | OAuthProvider], OAuthProviders0), @@ -264,6 +342,15 @@ init_per_group(with_oauth_provider_C_with_two_static_keys, Config) -> init_per_group(with_root_oauth_provider_with_two_static_keys_and_one_jwks_key, Config) -> KeyConfig = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, [rabbitmq_auth_backend_oauth2, key_config, []]), +======= + OAuthProviders1 = maps:put(<<"C">>, [ + {signing_keys, SigningKeys} | OAuthProvider], OAuthProviders0), + + ok = rpc_set_env(Config, oauth_providers, OAuthProviders1), + Config; +init_per_group(with_root_oauth_provider_with_two_static_keys_and_one_jwks_key, Config) -> + KeyConfig = rpc_get_env(Config, key_config, []), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Jwks1 = ?config(fixture_static_1, Config), Jwks2 = ?config(fixture_static_2, Config), SigningKeys = #{ @@ -271,6 +358,7 @@ init_per_group(with_root_oauth_provider_with_two_static_keys_and_one_jwks_key, C ?UTIL_MOD:token_key(Jwks2) => {json, Jwks2} }, KeyConfig1 = [{signing_keys, SigningKeys}, +<<<<<<< HEAD {jwks_url, strict_jwks_url(Config, "/jwks")}| KeyConfig], ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, key_config, KeyConfig1]), @@ -294,6 +382,27 @@ init_per_group(with_root_oauth_provider_with_default_jwks_key, Config) -> init_per_group(with_oauth_provider_B_with_one_static_key_and_jwks_with_two_signing_keys, Config) -> {ok, OAuthProviders0} = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, [rabbitmq_auth_backend_oauth2, oauth_providers]), +======= + {jwks_url, strict_jwks_uri(Config, "/jwks")}| KeyConfig], + ok = rpc_set_env(Config, key_config, KeyConfig1), + Config; +init_per_group(with_root_oauth_provider_with_default_key_1, Config) -> + KeyConfig = rpc_get_env(Config, key_config, []), + KeyConfig1 = [ + {default_key, ?UTIL_MOD:token_key(?config(fixture_static_1, Config))} + | KeyConfig], + ok = rpc_set_env(Config, key_config, KeyConfig1), + Config; +init_per_group(with_root_oauth_provider_with_default_jwks_key, Config) -> + KeyConfig = rpc_get_env(Config, key_config, []), + KeyConfig1 = [ + {default_key, ?UTIL_MOD:token_key(?config(fixture_jwk, Config))} + | KeyConfig], + ok = rpc_set_env(Config, key_config, KeyConfig1), + Config; +init_per_group(with_oauth_provider_B_with_one_static_key_and_jwks_with_two_signing_keys, Config) -> + {ok, OAuthProviders0} = rpc_get_env(Config, oauth_providers), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) OAuthProvider = maps:get(<<"B">>, OAuthProviders0, []), Jwks = ?config(fixture_staticB, Config), SigningKeys = #{ @@ -301,6 +410,7 @@ init_per_group(with_oauth_provider_B_with_one_static_key_and_jwks_with_two_signi }, OAuthProviders1 = maps:put(<<"B">>, [ {signing_keys, SigningKeys}, +<<<<<<< HEAD {jwks_uri, strict_jwks_url(Config, "/jwksB")} | OAuthProvider], OAuthProviders0), @@ -321,11 +431,28 @@ init_per_group(with_resource_servers_rabbitmq3_with_oauth_provider_C, Config) -> init_per_group(with_oauth_providers_C_with_default_key_static_key_1, Config) -> {ok, OAuthProviders0} = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, [rabbitmq_auth_backend_oauth2, oauth_providers]), +======= + {jwks_uri, strict_jwks_uri(Config, "/jwksB")} | OAuthProvider], + OAuthProviders0), + + ok = rpc_set_env(Config, oauth_providers, OAuthProviders1), + Config; +init_per_group(with_resource_servers_rabbitmq3_with_oauth_provider_C, Config) -> + ResourceServersConfig0 = rpc_get_env(Config, resource_servers, #{}), + Resource0 = maps:get(<<"rabbitmq3">>, ResourceServersConfig0, [ + {id, <<"rabbitmq3">>},{oauth_provider_id, <<"C">>}]), + ResourceServersConfig1 = maps:put(<<"rabbitmq3">>, Resource0, + ResourceServersConfig0), + ok = rpc_set_env(Config, resource_servers, ResourceServersConfig1); +init_per_group(with_oauth_providers_C_with_default_key_static_key_1, Config) -> + {ok, OAuthProviders0} = rpc_get_env(Config, oauth_providers), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) OAuthProvider = maps:get(<<"C">>, OAuthProviders0, []), Jwks = ?config(fixture_staticC_1, Config), OAuthProviders1 = maps:put(<<"C">>, [ {default_key, ?UTIL_MOD:token_key(Jwks)} | OAuthProvider], OAuthProviders0), +<<<<<<< HEAD ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders1]), @@ -334,12 +461,19 @@ init_per_group(with_oauth_providers_C_with_default_key_static_key_1, Config) -> init_per_group(_Group, Config) -> ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, resource_server_id, ?RESOURCE_SERVER_ID]), +======= + ok = rpc_set_env(Config, oauth_providers, OAuthProviders1), + Config; +init_per_group(_Group, Config) -> + ok = rpc_set_env(Config, resource_server_id, ?RESOURCE_SERVER_ID), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Config. end_per_group(without_kid, Config) -> rabbit_ct_helpers:delete_config(Config, include_kid); end_per_group(no_peer_verification, Config) -> +<<<<<<< HEAD KeyConfig = rabbit_ct_helpers:set_config(?config(key_config, Config), [{jwks_url, ?config(strict_jwks_url, Config)}, {peer_verification, verify_peer}]), ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, key_config, KeyConfig]), rabbit_ct_helpers:set_config(Config, {key_config, KeyConfig}); @@ -361,6 +495,26 @@ end_per_group(with_root_oauth_provider_with_default_jwks_key, Config) -> KeyConfig1 = proplists:delete(default_key, KeyConfig), ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, key_config, KeyConfig1]), +======= + KeyConfig = set_config(?config(key_config, Config), [ + {jwks_uri, ?config(strict_jwks_uri, Config)}, + {peer_verification, verify_peer}]), + ok = rpc_set_env(Config, key_config, KeyConfig), + set_config(Config, {key_config, KeyConfig}); + +end_per_group(with_default_oauth_provider_B, Config) -> + ok = rpc_unset_env(Config, default_oauth_provider); + +end_per_group(with_root_oauth_provider_with_default_key_1, Config) -> + KeyConfig = rpc_get_env(Config, key_config, []), + KeyConfig1 = proplists:delete(default_key, KeyConfig), + ok = rpc_set_env(Config, key_config, KeyConfig1), + Config; +end_per_group(with_root_oauth_provider_with_default_jwks_key, Config) -> + KeyConfig = rpc_get_env(Config, key_config, []), + KeyConfig1 = proplists:delete(default_key, KeyConfig), + ok = rpc_set_env(Config, key_config, KeyConfig1), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Config; end_per_group(_Group, Config) -> @@ -368,27 +522,50 @@ end_per_group(_Group, Config) -> add_vhosts(Config) -> %% The broker is managed by {init,end}_per_testcase(). +<<<<<<< HEAD lists:foreach(fun(Value) -> rabbit_ct_broker_helpers:add_vhost(Config, Value) end, [<<"vhost1">>, <<"vhost2">>, <<"vhost3">>, <<"vhost4">>]). +======= + lists:foreach(fun(Value) -> + rabbit_ct_broker_helpers:add_vhost(Config, Value) end, + [<<"vhost1">>, <<"vhost2">>, <<"vhost3">>, <<"vhost4">>]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %rabbit_ct_helpers:set_config(Config, []). delete_vhosts(Config) -> %% The broker is managed by {init,end}_per_testcase(). +<<<<<<< HEAD lists:foreach(fun(Value) -> rabbit_ct_broker_helpers:delete_vhost(Config, Value) end, [<<"vhost1">>, <<"vhost2">>, <<"vhost3">>, <<"vhost4">>]). init_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost orelse Testcase =:= test_successful_token_refresh -> +======= + lists:foreach(fun(Value) -> + rabbit_ct_broker_helpers:delete_vhost(Config, Value) end, + [<<"vhost1">>, <<"vhost2">>, <<"vhost3">>, <<"vhost4">>]). + +init_per_testcase(Testcase, Config) when + Testcase =:= test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost orelse + Testcase =:= test_successful_token_refresh -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_broker_helpers:add_vhost(Config, <<"vhost1">>), rabbit_ct_helpers:testcase_started(Config, Testcase), Config; +<<<<<<< HEAD init_per_testcase(Testcase, Config) when Testcase =:= test_failed_token_refresh_case1 orelse Testcase =:= test_failed_token_refresh_case2 -> +======= +init_per_testcase(Testcase, Config) when + Testcase =:= test_failed_token_refresh_case1 orelse + Testcase =:= test_failed_token_refresh_case2 -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_broker_helpers:add_vhost(Config, <<"vhost4">>), rabbit_ct_helpers:testcase_started(Config, Testcase), Config; +<<<<<<< HEAD init_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_complex_claim_as_a_map orelse Testcase =:= test_successful_connection_with_complex_claim_as_a_list orelse Testcase =:= test_successful_connection_with_complex_claim_as_a_binary -> @@ -406,6 +583,27 @@ init_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection init_per_testcase(Testcase, Config) when Testcase =:= test_failed_connection_with_algorithm_restriction -> KeyConfig = ?config(key_config, Config), ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, key_config, [{algorithms, [<<"RS256">>]} | KeyConfig]]), +======= +init_per_testcase(Testcase, Config) when + Testcase =:= test_successful_connection_with_complex_claim_as_a_map orelse + Testcase =:= test_successful_connection_with_complex_claim_as_a_list orelse + Testcase =:= test_successful_connection_with_complex_claim_as_a_binary -> + ok = rpc_set_env(Config, extra_scopes_source, ?EXTRA_SCOPES_SOURCE), + rabbit_ct_helpers:testcase_started(Config, Testcase), + Config; + +init_per_testcase(Testcase, Config) when + Testcase =:= test_successful_connection_with_algorithm_restriction -> + KeyConfig = ?config(key_config, Config), + ok = rpc_set_env(Config, key_config, [{algorithms, [<<"HS256">>]} | KeyConfig]), + rabbit_ct_helpers:testcase_started(Config, Testcase), + Config; + +init_per_testcase(Testcase, Config) when + Testcase =:= test_failed_connection_with_algorithm_restriction -> + KeyConfig = ?config(key_config, Config), + ok = rpc_set_env(Config, key_config, [{algorithms, [<<"RS256">>]} | KeyConfig]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_helpers:testcase_started(Config, Testcase), Config; @@ -413,25 +611,46 @@ init_per_testcase(Testcase, Config) -> rabbit_ct_helpers:testcase_started(Config, Testcase), Config. +<<<<<<< HEAD end_per_testcase(Testcase, Config) when Testcase =:= test_failed_token_refresh_case1 orelse Testcase =:= test_failed_token_refresh_case2 -> +======= +end_per_testcase(Testcase, Config) when + Testcase =:= test_failed_token_refresh_case1 orelse + Testcase =:= test_failed_token_refresh_case2 -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost4">>), rabbit_ct_helpers:testcase_started(Config, Testcase), Config; +<<<<<<< HEAD end_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_complex_claim_as_a_map orelse Testcase =:= test_successful_connection_with_complex_claim_as_a_list orelse Testcase =:= test_successful_connection_with_complex_claim_as_a_binary -> +======= +end_per_testcase(Testcase, Config) when + Testcase =:= test_successful_connection_with_complex_claim_as_a_map orelse + Testcase =:= test_successful_connection_with_complex_claim_as_a_list orelse + Testcase =:= test_successful_connection_with_complex_claim_as_a_binary -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost1">>), ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, unset_env, [rabbitmq_auth_backend_oauth2, extra_scopes_source]), rabbit_ct_helpers:testcase_started(Config, Testcase), Config; +<<<<<<< HEAD end_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_algorithm_restriction orelse Testcase =:= test_failed_connection_with_algorithm_restriction -> rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost1">>), ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, key_config, ?config(key_config, Config)]), +======= +end_per_testcase(Testcase, Config) when + Testcase =:= test_successful_connection_with_algorithm_restriction orelse + Testcase =:= test_failed_connection_with_algorithm_restriction -> + rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost1">>), + ok = rpc_set_env(Config, key_config, ?config(key_config, Config)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_helpers:testcase_finished(Config, Testcase), Config; @@ -441,10 +660,16 @@ end_per_testcase(Testcase, Config) -> Config. preconfigure_node(Config) -> +<<<<<<< HEAD ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbit, auth_backends, [rabbit_auth_backend_oauth2]]), ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, resource_server_id, ?RESOURCE_SERVER_ID]), +======= + ok = rpc(Config, 0, application, set_env, + [rabbit, auth_backends, [rabbit_auth_backend_oauth2]]), + ok = rpc_set_env(Config, resource_server_id, ?RESOURCE_SERVER_ID), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) add_vhosts(Config), Config. @@ -461,12 +686,21 @@ start_jwks_server(Config0) -> %% Assume we don't have more than 100 ports allocated for tests PortBase = rabbit_ct_broker_helpers:get_node_config(Config0, 0, tcp_ports_base), JwksServerPort = PortBase + 100, +<<<<<<< HEAD Config = rabbit_ct_helpers:set_config(Config0, [{jwksServerPort, JwksServerPort}]), %% Both URLs direct to the same JWKS server %% The NonStrictJwksUrl identity cannot be validated while StrictJwksUrl identity can be validated NonStrictJwksUrl = non_strict_jwks_url(Config), StrictJwksUrl = strict_jwks_url(Config), +======= + Config = set_config(Config0, [{jwksServerPort, JwksServerPort}]), + + %% Both URLs direct to the same JWKS server + %% The NonStrictJwksUrl identity cannot be validated while StrictJwksUrl identity can be validated + NonStrictJwksUri = non_strict_jwks_uri(Config), + StrictJwksUri = strict_jwks_uri(Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(cowboy), @@ -479,6 +713,7 @@ start_jwks_server(Config0) -> {"/jwks1", [Jwk1, Jwk3]}, {"/jwks2", [Jwk2]} ]), +<<<<<<< HEAD KeyConfig = [{jwks_url, StrictJwksUrl}, {peer_verification, verify_peer}, {cacertfile, filename:join([CertsDir, "testca", "cacert.pem"])}], @@ -508,6 +743,35 @@ strict_jwks_url(Config, Path) -> non_strict_jwks_url(Config) -> non_strict_jwks_url(Config, "/jwks"). non_strict_jwks_url(Config, Path) -> +======= + KeyConfig = [{jwks_url, StrictJwksUri}, + {peer_verification, verify_peer}, + {cacertfile, filename:join([CertsDir, "testca", "cacert.pem"])}], + ok = rpc_set_env(Config, key_config, KeyConfig), + set_config(Config, [ + {non_strict_jwks_uri, NonStrictJwksUri}, + {strict_jwks_uri, StrictJwksUri}, + {key_config, KeyConfig}, + {fixture_static_1, Jwk7}, + {fixture_static_2, Jwk8}, + {fixture_staticB, Jwk4}, + {fixture_staticC_1, Jwk5}, + {fixture_staticC_2, Jwk6}, + {fixture_jwksB_1, Jwk1}, + {fixture_jwksB_2, Jwk3}, + {fixture_jwksA, Jwk}, + {fixture_jwk, Jwk}, + {fixture_jwks_1, [Jwk1, Jwk3]}, + {fixture_jwks_2, [Jwk2]} + ]). +strict_jwks_uri(Config) -> + strict_jwks_uri(Config, "/jwks"). +strict_jwks_uri(Config, Path) -> + "https://localhost:" ++ integer_to_list(?config(jwksServerPort, Config)) ++ Path. +non_strict_jwks_uri(Config) -> + non_strict_jwks_uri(Config, "/jwks"). +non_strict_jwks_uri(Config, Path) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "https://127.0.0.1:" ++ integer_to_list(?config(jwksServerPort, Config)) ++ Path. @@ -522,16 +786,31 @@ generate_valid_token(Config, Scopes) -> generate_valid_token(Config, Scopes, undefined). generate_valid_token(Config, Scopes, Audience) -> +<<<<<<< HEAD Jwk = case rabbit_ct_helpers:get_config(Config, fixture_jwk) of +======= + Jwk = + case get_config(Config, fixture_jwk) of +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) undefined -> ?UTIL_MOD:fixture_jwk(); Value -> Value end, generate_valid_token(Config, Jwk, Scopes, Audience). generate_valid_token(Config, Jwk, Scopes, Audience) -> +<<<<<<< HEAD Token = case Audience of undefined -> ?UTIL_MOD:fixture_token_with_scopes(Scopes); DefinedAudience -> maps:put(<<"aud">>, DefinedAudience, ?UTIL_MOD:fixture_token_with_scopes(Scopes)) +======= + Token = + case Audience of + undefined -> + ?UTIL_MOD:fixture_token_with_scopes(Scopes); + DefinedAudience -> + maps:put(<<"aud">>, DefinedAudience, + ?UTIL_MOD:fixture_token_with_scopes(Scopes)) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end, IncludeKid = rabbit_ct_helpers:get_config(Config, include_kid, true), ?UTIL_MOD:sign_token_hs(Token, Jwk, IncludeKid). @@ -542,28 +821,50 @@ generate_valid_token_with_sub(Config, Jwk, Scopes, Sub) -> ?UTIL_MOD:sign_token_hs(Token, Jwk, IncludeKid). generate_valid_token_with_extra_fields(Config, ExtraFields) -> +<<<<<<< HEAD Jwk = case rabbit_ct_helpers:get_config(Config, fixture_jwk) of undefined -> ?UTIL_MOD:fixture_jwk(); Value -> Value end, Token = maps:merge(?UTIL_MOD:fixture_token_with_scopes([]), ExtraFields), ?UTIL_MOD:sign_token_hs(Token, Jwk, rabbit_ct_helpers:get_config(Config, include_kid, true)). +======= + Jwk = + case rabbit_ct_helpers:get_config(Config, fixture_jwk) of + undefined -> ?UTIL_MOD:fixture_jwk(); + Value -> Value + end, + Token = maps:merge(?UTIL_MOD:fixture_token_with_scopes([]), ExtraFields), + ?UTIL_MOD:sign_token_hs(Token, Jwk, + rabbit_ct_helpers:get_config(Config, include_kid, true)). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) generate_expired_token(Config) -> generate_expired_token(Config, ?UTIL_MOD:full_permission_scopes()). generate_expired_token(Config, Scopes) -> +<<<<<<< HEAD Jwk = case rabbit_ct_helpers:get_config(Config, fixture_jwk) of undefined -> ?UTIL_MOD:fixture_jwk(); Value -> Value end, ?UTIL_MOD:sign_token_hs(?UTIL_MOD:expired_token_with_scopes(Scopes), Jwk, rabbit_ct_helpers:get_config(Config, include_kid, true)). +======= + Jwk = + case get_config(Config, fixture_jwk) of + undefined -> ?UTIL_MOD:fixture_jwk(); + Value -> Value + end, + ?UTIL_MOD:sign_token_hs(?UTIL_MOD:expired_token_with_scopes(Scopes), Jwk, + get_config(Config, include_kid, true)). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) generate_expirable_token(Config, Seconds) -> generate_expirable_token(Config, ?UTIL_MOD:full_permission_scopes(), Seconds). generate_expirable_token(Config, Scopes, Seconds) -> +<<<<<<< HEAD Jwk = case rabbit_ct_helpers:get_config(Config, fixture_jwk) of undefined -> ?UTIL_MOD:fixture_jwk(); Value -> Value @@ -575,6 +876,20 @@ generate_expirable_token(Config, Scopes, Seconds) -> preconfigure_token(Config) -> Token = generate_valid_token(Config), rabbit_ct_helpers:set_config(Config, {fixture_jwt, Token}). +======= + Jwk = + case get_config(Config, fixture_jwk) of + undefined -> ?UTIL_MOD:fixture_jwk(); + Value -> Value + end, + Expiration = os:system_time(seconds) + Seconds, + ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_scopes_and_expiration( + Scopes, Expiration), Jwk, get_config(Config, include_kid, true)). + +preconfigure_token(Config) -> + Token = generate_valid_token(Config), + set_config(Config, {fixture_jwt, Token}). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% @@ -692,7 +1007,11 @@ test_unsuccessful_connection_for_rabbitmq_audience_signed_by_root_oauth_provider ?assertMatch({error, {auth_failure, _}}, open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, Token)). test_successful_connection_with_a_full_permission_token_and_all_defaults(Config) -> +<<<<<<< HEAD {_Algo, Token} = rabbit_ct_helpers:get_config(Config, fixture_jwt), +======= + {_Algo, Token} = get_config(Config, fixture_jwt), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) verify_queue_declare_with_token(Config, Token). verify_queue_declare_with_token(Config, Token) -> @@ -744,10 +1063,19 @@ test_successful_queue_declaration_using_multiple_keys_and_audiences(Config) -> test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost(Config) -> +<<<<<<< HEAD {_Algo, Token} = generate_valid_token(Config, [<<"rabbitmq.configure:vhost1/*">>, <<"rabbitmq.write:vhost1/*">>, <<"rabbitmq.read:vhost1/*">>]), Conn = open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, Token), +======= + {_Algo, Token} = generate_valid_token(Config, [ + <<"rabbitmq.configure:vhost1/*">>, + <<"rabbitmq.write:vhost1/*">>, + <<"rabbitmq.read:vhost1/*">>]), + Conn = open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, + Token), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {ok, Ch} = amqp_connection:open_channel(Conn), #'queue.declare_ok'{queue = _} = amqp_channel:call(Ch, #'queue.declare'{exclusive = true}), @@ -768,7 +1096,17 @@ test_successful_connection_with_simple_strings_for_aud_and_scope(Config) -> test_successful_connection_with_complex_claim_as_a_map(Config) -> {_Algo, Token} = generate_valid_token_with_extra_fields( Config, +<<<<<<< HEAD #{<<"additional_rabbitmq_scopes">> => #{<<"rabbitmq">> => [<<"configure:*/*">>, <<"read:*/*">>, <<"write:*/*">>]}} +======= + #{<<"additional_rabbitmq_scopes">> => #{ + <<"rabbitmq">> => [ + <<"configure:*/*">>, + <<"read:*/*">>, + <<"write:*/*">> + ]} + } +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ), Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token), {ok, Ch} = amqp_connection:open_channel(Conn), @@ -779,7 +1117,15 @@ test_successful_connection_with_complex_claim_as_a_map(Config) -> test_successful_connection_with_complex_claim_as_a_list(Config) -> {_Algo, Token} = generate_valid_token_with_extra_fields( Config, +<<<<<<< HEAD #{<<"additional_rabbitmq_scopes">> => [<<"rabbitmq.configure:*/*">>, <<"rabbitmq.read:*/*">>, <<"rabbitmq.write:*/*">>]} +======= + #{<<"additional_rabbitmq_scopes">> => [ + <<"rabbitmq.configure:*/*">>, + <<"rabbitmq.read:*/*">>, + <<"rabbitmq.write:*/*">> + ]} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ), Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token), {ok, Ch} = amqp_connection:open_channel(Conn), @@ -790,7 +1136,12 @@ test_successful_connection_with_complex_claim_as_a_list(Config) -> test_successful_connection_with_complex_claim_as_a_binary(Config) -> {_Algo, Token} = generate_valid_token_with_extra_fields( Config, +<<<<<<< HEAD #{<<"additional_rabbitmq_scopes">> => <<"rabbitmq.configure:*/* rabbitmq.read:*/* rabbitmq.write:*/*">>} +======= + #{<<"additional_rabbitmq_scopes">> => + <<"rabbitmq.configure:*/* rabbitmq.read:*/* rabbitmq.write:*/*">>} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ), Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token), {ok, Ch} = amqp_connection:open_channel(Conn), @@ -825,6 +1176,7 @@ test_successful_connection_with_keycloak_token(Config) -> test_successful_token_refresh(Config) -> Duration = 5, +<<<<<<< HEAD {_Algo, Token} = generate_expirable_token(Config, [<<"rabbitmq.configure:vhost1/*">>, <<"rabbitmq.write:vhost1/*">>, <<"rabbitmq.read:vhost1/*">>], @@ -844,11 +1196,36 @@ test_successful_token_refresh(Config) -> amqp_channel:call(Ch, #'queue.declare'{exclusive = true}), #'queue.declare_ok'{queue = _} = amqp_channel:call(Ch2, #'queue.declare'{exclusive = true}), +======= + {_Algo, Token} = generate_expirable_token(Config, [ + <<"rabbitmq.configure:vhost1/*">>, + <<"rabbitmq.write:vhost1/*">>, + <<"rabbitmq.read:vhost1/*">> + ], Duration), + Conn = open_unmanaged_connection(Config, 0, <<"vhost1">>, + <<"username">>, Token), + {ok, Ch} = amqp_connection:open_channel(Conn), + + {_Algo2, Token2} = generate_valid_token(Config, [ + <<"rabbitmq.configure:vhost1/*">>, + <<"rabbitmq.write:vhost1/*">>, + <<"rabbitmq.read:vhost1/*">>]), + ?UTIL_MOD:wait_for_token_to_expire(timer:seconds(Duration)), + ?assertEqual(ok, amqp_connection:update_secret(Conn, Token2, + <<"token refresh">>)), + {ok, Ch2} = amqp_connection:open_channel(Conn), + + #'queue.declare_ok'{queue = _} = amqp_channel:call(Ch, + #'queue.declare'{exclusive = true}), + #'queue.declare_ok'{queue = _} = amqp_channel:call(Ch2, + #'queue.declare'{exclusive = true}), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) amqp_channel:close(Ch2), close_connection_and_channel(Conn, Ch). test_successful_connection_with_algorithm_restriction(Config) -> +<<<<<<< HEAD {_Algo, Token} = rabbit_ct_helpers:get_config(Config, fixture_jwt), Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token), {ok, Ch} = amqp_connection:open_channel(Conn), @@ -889,15 +1266,77 @@ test_failed_token_refresh_case1(Config) -> <<"rabbitmq.write:vhost4/*">>, <<"rabbitmq.read:vhost4/*">>]), Conn = open_unmanaged_connection(Config, 0, <<"vhost4">>, <<"username">>, Token), +======= + {_Algo, Token} = get_config(Config, fixture_jwt), + Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token), + {ok, Ch} = amqp_connection:open_channel(Conn), + #'queue.declare_ok'{queue = _} = amqp_channel:call(Ch, + #'queue.declare'{exclusive = true}), + close_connection_and_channel(Conn, Ch). + +test_failed_connection_with_expired_token(Config) -> + {_Algo, Token} = generate_expired_token(Config, [ + <<"rabbitmq.configure:vhost1/*">>, + <<"rabbitmq.write:vhost1/*">>, + <<"rabbitmq.read:vhost1/*">>]), + ?assertMatch({error, {auth_failure, _}}, + open_unmanaged_connection(Config, 0, <<"vhost1">>, + <<"username">>, Token)). + +test_failed_connection_with_a_non_token(Config) -> + ?assertMatch({error, {auth_failure, _}}, + open_unmanaged_connection(Config, 0, <<"vhost1">>, + <<"username">>, <<"a-non-token-value">>)). + +test_failed_connection_with_a_token_with_insufficient_vhost_permission(Config) -> + {_Algo, Token} = generate_valid_token(Config, [ + <<"rabbitmq.configure:alt-vhost/*">>, + <<"rabbitmq.write:alt-vhost/*">>, + <<"rabbitmq.read:alt-vhost/*">>]), + ?assertEqual({error, not_allowed}, + open_unmanaged_connection(Config, 0, <<"off-limits-vhost">>, + <<"username">>, Token)). + +test_failed_connection_with_a_token_with_insufficient_resource_permission(Config) -> + {_Algo, Token} = generate_valid_token(Config, [ + <<"rabbitmq.configure:vhost2/jwt*">>, + <<"rabbitmq.write:vhost2/jwt*">>, + <<"rabbitmq.read:vhost2/jwt*">>]), + Conn = open_unmanaged_connection(Config, 0, <<"vhost2">>, <<"username">>, + Token), + {ok, Ch} = amqp_connection:open_channel(Conn), + ?assertExit({{shutdown, {server_initiated_close, 403, _}}, _}, + amqp_channel:call(Ch, #'queue.declare'{queue = <<"alt-prefix.eq.1">>, + exclusive = true})), + close_connection(Conn). + +test_failed_token_refresh_case1(Config) -> + {_Algo, Token} = generate_valid_token(Config, [ + <<"rabbitmq.configure:vhost4/*">>, + <<"rabbitmq.write:vhost4/*">>, + <<"rabbitmq.read:vhost4/*">>]), + Conn = open_unmanaged_connection(Config, 0, <<"vhost4">>, <<"username">>, + Token), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {ok, Ch} = amqp_connection:open_channel(Conn), #'queue.declare_ok'{queue = _} = amqp_channel:call(Ch, #'queue.declare'{exclusive = true}), +<<<<<<< HEAD {_Algo2, Token2} = generate_expired_token(Config, [<<"rabbitmq.configure:vhost4/*">>, <<"rabbitmq.write:vhost4/*">>, <<"rabbitmq.read:vhost4/*">>]), %% the error is communicated asynchronously via a connection-level error ?assertEqual(ok, amqp_connection:update_secret(Conn, Token2, <<"token refresh">>)), +======= + {_Algo2, Token2} = generate_expired_token(Config, [ + <<"rabbitmq.configure:vhost4/*">>, + <<"rabbitmq.write:vhost4/*">>, + <<"rabbitmq.read:vhost4/*">>]), + %% the error is communicated asynchronously via a connection-level error + ?assertEqual(ok, amqp_connection:update_secret(Conn, Token2, + <<"token refresh">>)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {ok, Ch2} = amqp_connection:open_channel(Conn), ?assertExit({{shutdown, {server_initiated_close, 403, _}}, _}, @@ -906,16 +1345,30 @@ test_failed_token_refresh_case1(Config) -> close_connection(Conn). test_failed_token_refresh_case2(Config) -> +<<<<<<< HEAD {_Algo, Token} = generate_valid_token(Config, [<<"rabbitmq.configure:vhost4/*">>, <<"rabbitmq.write:vhost4/*">>, <<"rabbitmq.read:vhost4/*">>]), Conn = open_unmanaged_connection(Config, 0, <<"vhost4">>, <<"username">>, Token), +======= + {_Algo, Token} = generate_valid_token(Config, [ + <<"rabbitmq.configure:vhost4/*">>, + <<"rabbitmq.write:vhost4/*">>, + <<"rabbitmq.read:vhost4/*">>]), + Conn = open_unmanaged_connection(Config, 0, <<"vhost4">>, + <<"username">>, Token), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {ok, Ch} = amqp_connection:open_channel(Conn), #'queue.declare_ok'{queue = _} = amqp_channel:call(Ch, #'queue.declare'{exclusive = true}), %% the error is communicated asynchronously via a connection-level error +<<<<<<< HEAD ?assertEqual(ok, amqp_connection:update_secret(Conn, <<"not-a-token-^^^^5%">>, <<"token refresh">>)), +======= + ?assertEqual(ok, amqp_connection:update_secret(Conn, <<"not-a-token-^^^^5%">>, + <<"token refresh">>)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?assertExit({{shutdown, {connection_closing, {server_initiated_close, 530, _}}}, _}, amqp_connection:open_channel(Conn)), @@ -946,6 +1399,26 @@ cannot_change_username_on_refreshed_token(Config) -> test_failed_connection_with_algorithm_restriction(Config) -> +<<<<<<< HEAD {_Algo, Token} = rabbit_ct_helpers:get_config(Config, fixture_jwt), ?assertMatch({error, {auth_failure, _}}, open_unmanaged_connection(Config, 0, <<"username">>, Token)). +======= + {_Algo, Token} = get_config(Config, fixture_jwt), + ?assertMatch({error, {auth_failure, _}}, + open_unmanaged_connection(Config, 0, <<"username">>, Token)). + +%%% HELPERS +rpc_unset_env(Config, Par) -> + rpc(Config, 0, application, unset_env, + [rabbitmq_auth_backend_oauth2, Par]). +rpc_set_env(Config, Par, Val) -> + rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, Par, Val]). +rpc_get_env(Config, Par) -> + rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, Par]). +rpc_get_env(Config, Par, Default) -> + rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, Par, Default]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_provider_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_provider_SUITE.erl new file mode 100644 index 000000000000..ac3ca2b67e89 --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_provider_SUITE.erl @@ -0,0 +1,523 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(rabbit_oauth2_provider_SUITE). + +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include("oauth2.hrl"). + +-define(RABBITMQ,<<"rabbitmq">>). +-define(RABBITMQ_RESOURCE_ONE,<<"rabbitmq1">>). +-define(RABBITMQ_RESOURCE_TWO,<<"rabbitmq2">>). +-define(AUTH_PORT, 8000). + +-import(rabbit_oauth2_provider, [ + get_internal_oauth_provider/0,get_internal_oauth_provider/1, + add_signing_key/2, add_signing_key/3, replace_signing_keys/1, + replace_signing_keys/2, + get_signing_keys/0, get_signing_keys/1, get_signing_key/1, get_signing_key/2 +]). +-import(oauth2_client, [get_oauth_provider/2]). + +all() -> [ + {group, with_rabbitmq_node}, + {group, verify_oauth_provider_A}, + {group, verify_oauth_provider_root} +]. +groups() -> [ + {with_rabbitmq_node, [], [ + add_signing_keys_for_specific_oauth_provider, + add_signing_keys_for_root_oauth_provider, + + replace_signing_keys_for_root_oauth_provider, + replace_signing_keys_for_specific_oauth_provider, + {with_root_static_signing_keys, [], [ + replace_merge_root_static_keys_with_newly_added_keys, + replace_override_root_static_keys_with_newly_added_keys + ]}, + {with_static_signing_keys_for_specific_oauth_provider, [], [ + replace_merge_static_keys_with_newly_added_keys, + replace_override_static_keys_with_newly_added_keys + ]} + ]}, + {verify_oauth_provider_A, [], verify_provider()}, + {verify_oauth_provider_root, [], verify_provider()} +]. + +verify_provider() -> [ + internal_oauth_provider_has_no_default_key, + {oauth_provider_with_default_key, [], [ + internal_oauth_provider_has_default_key + ]}, + internal_oauth_provider_has_no_algorithms, + {oauth_provider_with_algorithms, [], [ + internal_oauth_provider_has_algorithms + ]}, + get_oauth_provider_with_jwks_uri_returns_error, + {oauth_provider_with_jwks_uri, [], [ + get_oauth_provider_has_jwks_uri + ]}, + {oauth_provider_with_issuer, [], [ + get_oauth_provider_has_jwks_uri + ]} +]. + +init_per_suite(Config) -> + rabbit_ct_helpers:log_environment(), + rabbit_ct_helpers:run_setup_steps(Config). + +end_per_suite(Config) -> + rabbit_ct_helpers:run_teardown_steps(Config). + +init_per_group(with_rabbitmq_node, Config) -> + Config1 = rabbit_ct_helpers:set_config(Config, [ + {rmq_nodename_suffix, with_rabbitmq_node}, + {rmq_nodes_count, 1} + ]), + rabbit_ct_helpers:run_steps(Config1, rabbit_ct_broker_helpers:setup_steps()); + +init_per_group(with_root_static_signing_keys, Config) -> + KeyConfig = call_get_env(Config, key_config, []), + SigningKeys = #{ + <<"mykey-root-1">> => <<"some key root-1">>, + <<"mykey-root-2">> => <<"some key root-2">> + }, + call_set_env(Config, key_config, + proplists:delete(default_key, KeyConfig) ++ [{signing_keys,SigningKeys}]), + Config; + +init_per_group(with_static_signing_keys_for_specific_oauth_provider, Config) -> + OAuthProviders = call_get_env(Config, oauth_providers, #{}), + OAuthProvider = maps:get(<<"A">>, OAuthProviders, []), + SigningKeys = #{ + <<"mykey-root-1">> => <<"some key root-1">>, + <<"mykey-root-2">> => <<"some key root-2">> + }, + OAuthProvider1 = proplists:delete(signing_keys, OAuthProvider) ++ + [{signing_keys, SigningKeys}], + + call_set_env(Config, oauth_providers, maps:put(<<"A">>, OAuthProvider1, + OAuthProviders)), + Config; + +init_per_group(oauth_provider_with_jwks_uri, Config) -> + URL = case ?config(oauth_provider_id, Config) of + root -> + RootUrl = build_url_to_oauth_provider(<<"/keys">>), + set_env(jwks_uri, RootUrl), + RootUrl; + <<"A">> -> + AUrl = build_url_to_oauth_provider(<<"/A/keys">>), + set_oauth_provider_properties(<<"A">>, [{jwks_uri, AUrl}]), + AUrl + end, + [{jwks_uri, URL} | Config]; + +init_per_group(oauth_provider_with_issuer, Config) -> + {ok, _} = application:ensure_all_started(inets), + {ok, _} = application:ensure_all_started(ssl), + application:ensure_all_started(cowboy), + CertsDir = ?config(rmq_certsdir, Config), + CaCertFile = filename:join([CertsDir, "testca", "cacert.pem"]), + SslOptions = ssl_options(verify_peer, false, CaCertFile), + + HttpOauthServerExpectations = get_openid_configuration_expectations(), + ListOfExpectations = maps:values(proplists:to_map(HttpOauthServerExpectations)), + + start_https_oauth_server(?AUTH_PORT, CertsDir, ListOfExpectations), + set_env(use_global_locks, false), + {Issuer, JwksUri} = case ?config(oauth_provider_id, Config) of + root -> + Url = build_url_to_oauth_provider(<<"/">>), + set_env(issuer, Url), + set_env(key_config, SslOptions), + {Url, build_url_to_oauth_provider(<<"/keys">>)}; + <<"A">> -> + Url = build_url_to_oauth_provider(<<"/A">>), + set_oauth_provider_properties(<<"A">>, [{issuer, Url}, {https, SslOptions}]), + {Url, build_url_to_oauth_provider(<<"/A/keys">>)} + end, + [{issuer, Issuer}, {jwks_uri, JwksUri}] ++ Config; + +init_per_group(with_resource_server_id, Config) -> + set_env(resource_server_id, ?RABBITMQ), + Config; + +init_per_group(with_algorithms, Config) -> + KeyConfig = get_env(key_config, []), + set_env(key_config, KeyConfig ++ [{algorithms, [<<"HS256">>, <<"RS256">>]}]), + [{algorithms, [<<"HS256">>, <<"RS256">>]} | Config]; + +init_per_group(with_algorithms_for_provider_A, Config) -> + OAuthProviders = get_env(oauth_providers, #{}), + OAuthProvider = maps:get(<<"A">>, OAuthProviders, []), + set_env(oauth_providers, maps:put(<<"A">>, + [{algorithms, [<<"HS256">>, <<"RS256">>]} | OAuthProvider], OAuthProviders)), + [{algorithms, [<<"HS256">>, <<"RS256">>]} | Config]; + +init_per_group(with_different_oauth_provider_for_each_resource, Config) -> + {ok, ResourceServers} = get_env(resource_servers), + Rabbit1 = maps:get(?RABBITMQ_RESOURCE_ONE, ResourceServers) ++ + [ {oauth_provider_id, <<"A">>} ], + Rabbit2 = maps:get(?RABBITMQ_RESOURCE_TWO, ResourceServers) ++ + [ {oauth_provider_id, <<"B">>} ], + ResourceServers1 = maps:update(?RABBITMQ_RESOURCE_ONE, Rabbit1, ResourceServers), + set_env(resource_servers, maps:update(?RABBITMQ_RESOURCE_TWO, Rabbit2, + ResourceServers1)), + Config; + + +init_per_group(verify_oauth_provider_A, Config) -> + set_env(oauth_providers, + #{ <<"A">> => [ + {id, <<"A">>} + ] + }), + [{oauth_provider_id, <<"A">>} |Config]; + +init_per_group(verify_oauth_provider_root, Config) -> + [{oauth_provider_id, root} |Config]; + +init_per_group(_any, Config) -> + Config. + +end_per_group(with_rabbitmq_node, Config) -> + rabbit_ct_helpers:run_steps(Config, rabbit_ct_broker_helpers:teardown_steps()); + +end_per_group(with_root_static_signing_keys, Config) -> + KeyConfig = call_get_env(Config, key_config, []), + call_set_env(Config, key_config, KeyConfig), + Config; + +end_per_group(with_resource_server_id, Config) -> + unset_env(resource_server_id), + Config; + +end_per_group(oauth_provider_with_issuer, Config) -> + case ?config(oauth_provider_id, Config) of + root -> + unset_env(issuer), + unset_env(https); + Id -> + unset_oauth_provider_properties(Id, [issuer, https]) + end, + stop_http_auth_server(), + Config; +end_per_group(oauth_provider_with_jwks_uri, Config) -> + case ?config(oauth_provider_id, Config) of + root -> unset_env(jwks_uri); + Id -> unset_oauth_provider_properties(Id, [jwks_uri]) + end, + Config; + +end_per_group(oauth_provider_with_default_key, Config) -> +case ?config(oauth_provider_id, Config) of + root -> unset_env(default_key); + Id -> unset_oauth_provider_properties(Id, [default_key]) + end, + Config; + +end_per_group(_any, Config) -> + Config. + +%% ----- Utility functions + +call_set_env(Config, Par, Value) -> + rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, Par, Value]). + +call_get_env(Config, Par, Def) -> + rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, Par, Def]). + +call_add_signing_key(Config, Args) -> + rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_provider, + add_signing_key, Args). + +call_get_signing_keys(Config, Args) -> + rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_provider, + get_signing_keys, Args). + +call_get_signing_keys(Config) -> + call_get_signing_keys(Config, []). + +call_get_signing_key(Config, Args) -> + rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_provider, + get_signing_key, Args). + +call_add_signing_keys(Config, Args) -> + rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_provider, + add_signing_keys, Args). + +call_replace_signing_keys(Config, Args) -> + rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_provider, + replace_signing_keys, Args). + +%% ----- Test cases + +add_signing_keys_for_root_oauth_provider(Config) -> + #{<<"mykey-1">> := <<"some key 1">>} = + call_add_signing_key(Config, [<<"mykey-1">>, <<"some key 1">>]), + #{<<"mykey-1">> := <<"some key 1">>} = + call_get_signing_keys(Config), + + #{<<"mykey-1">> := <<"some key 1">>, <<"mykey-2">> := <<"some key 2">>} = + call_add_signing_key(Config, [<<"mykey-2">>, <<"some key 2">>]), + #{<<"mykey-1">> := <<"some key 1">>, <<"mykey-2">> := <<"some key 2">>} = + call_get_signing_keys(Config), + + ?assertEqual(<<"some key 1">>, + call_get_signing_key(Config, [<<"mykey-1">>])). + +add_signing_keys_for_specific_oauth_provider(Config) -> + #{<<"mykey-3-1">> := <<"some key 3-1">>} = + call_add_signing_key(Config, + [<<"mykey-3-1">>, <<"some key 3-1">>, <<"my-oauth-provider-3">>]), + #{<<"mykey-4-1">> := <<"some key 4-1">>} = + call_add_signing_key(Config, + [<<"mykey-4-1">>, <<"some key 4-1">>, <<"my-oauth-provider-4">>]), + #{<<"mykey-3-1">> := <<"some key 3-1">>} = + call_get_signing_keys(Config, [<<"my-oauth-provider-3">>]), + #{<<"mykey-4-1">> := <<"some key 4-1">>} = + call_get_signing_keys(Config, [<<"my-oauth-provider-4">>]), + + #{<<"mykey-3-1">> := <<"some key 3-1">>, + <<"mykey-3-2">> := <<"some key 3-2">>} = + call_add_signing_key(Config, [ + <<"mykey-3-2">>, <<"some key 3-2">>, <<"my-oauth-provider-3">>]), + + #{<<"mykey-1">> := <<"some key 1">>} = + call_add_signing_key(Config, [<<"mykey-1">>, <<"some key 1">>]), + #{<<"mykey-1">> := <<"some key 1">>} = + call_get_signing_keys(Config, []), + + ?assertEqual(<<"some key 3-1">>, + call_get_signing_key(Config, [<<"mykey-3-1">> , <<"my-oauth-provider-3">>])). + +replace_merge_root_static_keys_with_newly_added_keys(Config) -> + NewKeys = #{<<"key-2">> => <<"some key 2">>, <<"key-3">> => <<"some key 3">>}, + call_replace_signing_keys(Config, [NewKeys]), + #{ <<"mykey-root-1">> := <<"some key root-1">>, + <<"mykey-root-2">> := <<"some key root-2">>, + <<"key-2">> := <<"some key 2">>, + <<"key-3">> := <<"some key 3">> + } = call_get_signing_keys(Config). + +replace_merge_static_keys_with_newly_added_keys(Config) -> + NewKeys = #{<<"key-2">> => <<"some key 2">>, <<"key-3">> => <<"some key 3">>}, + call_replace_signing_keys(Config, [NewKeys, <<"A">>]), + #{ <<"mykey-root-1">> := <<"some key root-1">>, + <<"mykey-root-2">> := <<"some key root-2">>, + <<"key-2">> := <<"some key 2">>, + <<"key-3">> := <<"some key 3">> + } = call_get_signing_keys(Config, [<<"A">>]). + +replace_override_root_static_keys_with_newly_added_keys(Config) -> + NewKeys = #{<<"mykey-root-1">> => <<"new key root-1">>, + <<"key-3">> => <<"some key 3">>}, + call_replace_signing_keys(Config, [NewKeys]), + #{ <<"mykey-root-1">> := <<"new key root-1">>, + <<"mykey-root-2">> := <<"some key root-2">>, + <<"key-3">> := <<"some key 3">> + } = call_get_signing_keys(Config). +replace_override_static_keys_with_newly_added_keys(Config) -> + NewKeys = #{<<"mykey-root-1">> => <<"new key root-1">>, + <<"key-3">> => <<"some key 3">>}, + call_replace_signing_keys(Config, [NewKeys, <<"A">>]), + #{ <<"mykey-root-1">> := <<"new key root-1">>, + <<"mykey-root-2">> := <<"some key root-2">>, + <<"key-3">> := <<"some key 3">> + } = call_get_signing_keys(Config, [<<"A">>]). + +replace_signing_keys_for_root_oauth_provider(Config) -> + call_add_signing_key(Config, [<<"mykey-1">>, <<"some key 1">>]), + NewKeys = #{<<"key-2">> => <<"some key 2">>, <<"key-3">> => <<"some key 3">>}, + call_replace_signing_keys(Config, [NewKeys]), + #{<<"key-2">> := <<"some key 2">>, <<"key-3">> := <<"some key 3">>} = + call_get_signing_keys(Config). + +replace_signing_keys_for_specific_oauth_provider(Config) -> + OAuthProviderId = <<"my-oauth-provider-3">>, + #{<<"mykey-3-1">> := <<"some key 3-1">>} = + call_add_signing_key(Config, + [<<"mykey-3-1">>, <<"some key 3-1">>, OAuthProviderId]), + NewKeys = #{<<"key-2">> => <<"some key 2">>, + <<"key-3">> => <<"some key 3">>}, + call_replace_signing_keys(Config, [NewKeys, OAuthProviderId]), + #{<<"key-2">> := <<"some key 2">>, <<"key-3">> := <<"some key 3">>} = + call_get_signing_keys(Config, [OAuthProviderId]). + + +get_algorithms_should_return_undefined(_Config) -> + OAuthProvider = get_internal_oauth_provider(), + undefined = OAuthProvider#internal_oauth_provider.algorithms. + +get_algorithms(Config) -> + OAuthProvider = get_internal_oauth_provider(), + Algorithms = OAuthProvider#internal_oauth_provider.algorithms, + ?assertEqual(?config(algorithms, Config), Algorithms). + +get_algorithms_for_provider_A_should_return_undefined(_Config) -> + OAuthProvider = get_internal_oauth_provider(<<"A">>), + undefined = OAuthProvider#internal_oauth_provider.algorithms. + +get_algorithms_for_provider_A(Config) -> + OAuthProvider = get_internal_oauth_provider(<<"A">>), + Algorithms = OAuthProvider#internal_oauth_provider.algorithms, + ?assertEqual(?config(algorithms, Config), Algorithms). + +append_paths(Path1, Path2) -> + erlang:iolist_to_binary([Path1, Path2]). + + + +internal_oauth_provider_has_no_default_key(Config) -> + InternalOAuthProvider = get_internal_oauth_provider( + ?config(oauth_provider_id, Config)), + ?assertEqual(undefined, + InternalOAuthProvider#internal_oauth_provider.default_key). + +internal_oauth_provider_has_default_key(Config) -> + InternalOAuthProvider = get_internal_oauth_provider( + ?config(oauth_provider_id, Config)), + ?assertEqual(?config(default_key, Config), + InternalOAuthProvider#internal_oauth_provider.default_key). + +internal_oauth_provider_has_no_algorithms(Config) -> + InternalOAuthProvider = get_internal_oauth_provider( + ?config(oauth_provider_id, Config)), + ?assertEqual(undefined, + InternalOAuthProvider#internal_oauth_provider.algorithms). + +internal_oauth_provider_has_algorithms(Config) -> + InternalOAuthProvider = get_internal_oauth_provider( + ?config(oauth_provider_id, Config)), + ?assertEqual(?config(algorithms, Config), + InternalOAuthProvider#internal_oauth_provider.algorithms). + +get_oauth_provider_with_jwks_uri_returns_error(Config) -> + {error, _} = get_oauth_provider( + ?config(oauth_provider_id, Config), [jwks_uri]). + +get_oauth_provider_has_jwks_uri(Config) -> + {ok, OAuthProvider} = get_oauth_provider( + ?config(oauth_provider_id, Config), [jwks_uri]), + ct:log("OAuthProvider: ~p", [OAuthProvider]), + ?assertEqual(?config(jwks_uri, Config), OAuthProvider#oauth_provider.jwks_uri). + + +%% ---- Utility functions + +get_env(Par) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par). +get_env(Par, Def) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par, Def). +set_env(Par, Val) -> + application:set_env(rabbitmq_auth_backend_oauth2, Par, Val). +unset_env(Par) -> + application:unset_env(rabbitmq_auth_backend_oauth2, Par). + +get_openid_configuration_expectations() -> + [ {get_root_openid_configuration, + + #{request => #{ + method => <<"GET">>, + path => <<"/.well-known/openid-configuration">> + }, + response => [ + {code, 200}, + {content_type, ?CONTENT_JSON}, + {payload, [ + {issuer, build_url_to_oauth_provider(<<"/">>) }, + {jwks_uri, build_url_to_oauth_provider(<<"/keys">>)} + ]} + ] + } + }, + {get_A_openid_configuration, + + #{request => #{ + method => <<"GET">>, + path => <<"/A/.well-known/openid-configuration">> + }, + response => [ + {code, 200}, + {content_type, ?CONTENT_JSON}, + {payload, [ + {issuer, build_url_to_oauth_provider(<<"/A">>) }, + {jwks_uri, build_url_to_oauth_provider(<<"/A/keys">>)} + ]} + ] + } + }, + {get_B_openid_configuration, + + #{request => #{ + method => <<"GET">>, + path => <<"/B/.well-known/openid-configuration">> + }, + response => [ + {code, 200}, + {content_type, ?CONTENT_JSON}, + {payload, [ + {issuer, build_url_to_oauth_provider(<<"/B">>) }, + {jwks_uri, build_url_to_oauth_provider(<<"/B/keys">>)} + ]} + ] + } + } + ]. + +start_https_oauth_server(Port, CertsDir, Expectations) when is_list(Expectations) -> + Dispatch = cowboy_router:compile([ + {'_', [{Path, oauth2_http_mock, Expected} || + #{request := #{path := Path}} = Expected <- Expectations ]} + ]), + {ok, Pid} = cowboy:start_tls( + mock_http_auth_listener, + [{port, Port}, + {certfile, filename:join([CertsDir, "server", "cert.pem"])}, + {keyfile, filename:join([CertsDir, "server", "key.pem"])} + ], + #{env => #{dispatch => Dispatch}}). + +build_url_to_oauth_provider(Path) -> + uri_string:recompose(#{scheme => "https", + host => "localhost", + port => rabbit_data_coercion:to_integer(?AUTH_PORT), + path => Path}). + +stop_http_auth_server() -> + cowboy:stop_listener(mock_http_auth_listener). + +set_oauth_provider_properties(OAuthProviderId, Proplist) -> + OAuthProviders = get_env(oauth_providers, #{}), + CurProplist = maps:get(OAuthProviderId, OAuthProviders), + CurMap = proplists:to_map(CurProplist), + Map = proplists:to_map(Proplist), + set_env(oauth_providers, maps:put(OAuthProviderId, + maps:to_list(maps:merge(CurMap, Map)), OAuthProviders)). + +unset_oauth_provider_properties(OAuthProviderId, PropertyNameList) -> + OAuthProviders = get_env(oauth_providers, #{}), + CurProplist = maps:get(OAuthProviderId, OAuthProviders), + CurMap = proplists:to_map(CurProplist), + set_env(oauth_providers, maps:put(OAuthProviderId, + maps:to_list(maps:filter(fun(K,_V) -> + not proplists:is_defined(K, PropertyNameList) end, CurMap)), + OAuthProviders)). + +-spec ssl_options(ssl:verify_type(), boolean(), file:filename()) -> list(). +ssl_options(PeerVerification, FailIfNoPeerCert, CaCertFile) -> + [{verify, PeerVerification}, + {depth, 10}, + {fail_if_no_peer_cert, FailIfNoPeerCert}, + {crl_check, false}, + {crl_cache, {ssl_crl_cache, {internal, [{http, 10000}]}}}, + {cacertfile, CaCertFile}]. diff --git a/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_resource_server_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_resource_server_SUITE.erl new file mode 100644 index 000000000000..3e1fb745b6ec --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_resource_server_SUITE.erl @@ -0,0 +1,452 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(rabbit_oauth2_resource_server_SUITE). + +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include("oauth2.hrl"). + +-define(RABBITMQ,<<"rabbitmq">>). +-define(RABBITMQ_RESOURCE_ONE,<<"rabbitmq1">>). +-define(RABBITMQ_RESOURCE_TWO,<<"rabbitmq2">>). +-define(OAUTH_PROVIDER_A,<<"A">>). +-define(OAUTH_PROVIDER_B,<<"B">>). + +-import(oauth2_client, [get_oauth_provider/2]). +-import(rabbit_oauth2_resource_server, [resolve_resource_server_from_audience/1]). + + +all() -> [ + {group, without_resource_server_id}, + {group, with_rabbitmq_as_resource_server_id}, + {group, with_two_resource_servers} + %{group, with_two_resource_servers_and_rabbitmq_as_resource_server_id} +]. +groups() -> [ + {with_rabbitmq_as_resource_server_id, [], [ + resolve_resource_server_for_rabbitmq_audience, + resolve_resource_server_for_rabbitmq_plus_unknown_audience, + resolve_resource_server_for_none_audience_returns_no_aud_found, + resolve_resource_server_for_unknown_audience_returns_no_matching_aud_found, + {with_verify_aud_false, [], [ + resolve_resource_server_for_none_audience_returns_rabbitmq, + resolve_resource_server_for_unknown_audience_returns_rabbitmq + ]}, + {verify_get_rabbitmq_server_configuration, [], + verify_get_rabbitmq_server_configuration()} + ]}, + {without_resource_server_id, [], [ + resolve_resource_server_id_for_any_audience_returns_no_matching_aud_found + ]}, + + {with_two_resource_servers, [], [ + resolve_resource_server_id_for_rabbitmq1, + resolve_resource_server_id_for_rabbitmq2, + resolve_resource_server_id_for_both_resources_returns_error, + resolve_resource_server_for_none_audience_returns_no_aud_found, + resolve_resource_server_for_unknown_audience_returns_no_matching_aud_found, + {with_verify_aud_false, [], [ + resolve_resource_server_for_none_audience_returns_rabbitmq2, + resolve_resource_server_for_unknown_audience_returns_rabbitmq2, + {with_rabbitmq1_verify_aud_false, [], [ + resolve_resource_server_for_none_audience_returns_error + ]} + ]}, + verify_rabbitmq1_server_configuration, + {verify_configuration_inheritance_with_rabbitmq2, [], + verify_configuration_inheritance_with_rabbitmq2()}, + {with_rabbitmq_as_resource_server_id, [], [ + resolve_resource_server_for_rabbitmq_audience, + resolve_resource_server_id_for_rabbitmq1, + resolve_resource_server_id_for_rabbitmq2 + ]} + ]} +]. + +verify_get_rabbitmq_server_configuration() -> [ + rabbitmq_verify_aud_is_true, + {with_verify_aud_false, [], [ + rabbitmq_verify_aud_is_false + ]}, + rabbitmq_has_default_scope_prefix, + {with_scope_prefix, [], [ + rabbitmq_has_scope_prefix + ]}, + {with_empty_scope_prefix, [], [ + rabbitmq_has_empty_scope_prefix + ]}, + rabbitmq_oauth_provider_id_is_root, + {with_default_oauth_provider_A, [], [ + rabbitmq_oauth_provider_id_is_A + ]}, + rabbitmq_has_no_additional_scopes_key, + {with_additional_scopes_key, [], [ + rabbitmq_has_additional_scopes_key + ]}, + rabbitmq_has_no_preferred_username_claims_but_gets_default, + {with_preferred_username_claims, [], [ + rabbitmq_has_preferred_username_claims + ]}, + rabbitmq_has_no_scope_aliases, + {with_scope_aliases, [], [ + rabbitmq_has_scope_aliases + ]} +]. + +verify_configuration_inheritance_with_rabbitmq2() -> [ + rabbitmq2_verify_aud_is_true, + {with_verify_aud_false, [], [ + rabbitmq2_verify_aud_is_false + ]}, + rabbitmq2_has_default_scope_prefix, + {with_scope_prefix, [], [ + rabbitmq2_has_scope_prefix + ]}, + rabbitmq2_oauth_provider_id_is_root, + {with_default_oauth_provider_A, [], [ + rabbitmq2_oauth_provider_id_is_A + ]}, + rabbitmq2_has_no_additional_scopes_key, + {with_additional_scopes_key, [], [ + rabbitmq2_has_additional_scopes_key + ]}, + rabbitmq2_has_no_preferred_username_claims_but_gets_default, + {with_preferred_username_claims, [], [ + rabbitmq2_has_preferred_username_claims_plus_default + ]}, + rabbitmq2_has_no_scope_aliases, + {with_scope_aliases, [], [ + rabbitmq2_has_scope_aliases + ]} +]. + +init_per_suite(Config) -> + rabbit_ct_helpers:log_environment(), + rabbit_ct_helpers:run_setup_steps(Config). + +end_per_suite(Config) -> + rabbit_ct_helpers:run_teardown_steps(Config). + +init_per_group(with_default_oauth_provider_A, Config) -> + set_env(default_oauth_provider, ?OAUTH_PROVIDER_A), + Config; + +init_per_group(with_default_oauth_provider_B, Config) -> + set_env(default_oauth_provider, ?OAUTH_PROVIDER_B), + Config; + +init_per_group(with_rabbitmq_as_resource_server_id, Config) -> + set_env(resource_server_id, ?RABBITMQ), + Config; + +init_per_group(with_scope_prefix, Config) -> + Prefix = <<"some-prefix:">>, + set_env(scope_prefix, Prefix), + [{scope_prefix, Prefix} | Config]; + +init_per_group(with_empty_scope_prefix, Config) -> + Prefix = <<"">>, + set_env(scope_prefix, Prefix), + Config; + +init_per_group(with_additional_scopes_key, Config) -> + Key = <<"roles">>, + set_env(extra_scopes_source, Key), + [{additional_scopes_key, Key} | Config]; + +init_per_group(with_preferred_username_claims, Config) -> + Claims = [<<"new-user">>, <<"new-email">>], + set_env(preferred_username_claims, Claims), + [{preferred_username_claims, Claims} | Config]; + +init_per_group(with_scope_aliases, Config) -> + Aliases = #{ + <<"admin">> => [<<"rabbitmq.tag:administrator">>] + }, + set_env(scope_aliases, Aliases), + [{scope_aliases, Aliases} | Config]; + +init_per_group(with_verify_aud_false, Config) -> + set_env(verify_aud, false), + Config; + +init_per_group(with_rabbitmq1_verify_aud_false, Config) -> + RabbitMQServers = get_env(resource_servers, #{}), + Resource0 = maps:get(?RABBITMQ_RESOURCE_ONE, RabbitMQServers, []), + Resource = [{verify_aud, false} | Resource0], + set_env(resource_servers, maps:put(?RABBITMQ_RESOURCE_ONE, Resource, + RabbitMQServers)), + Config; + +init_per_group(with_two_resource_servers, Config) -> + RabbitMQ1 = [ + {id, ?RABBITMQ_RESOURCE_ONE}, + {resource_server_type, <<"some-type">>}, + {verify_aud, true}, + {scope_prefix, <<"some-prefix">>}, + {additional_scopes_key, <<"roles">>}, + {preferred_username_claims, [<<"x-username">>, <<"x-email">>]}, + {scope_aliases, #{ <<"admin">> => [<<"rabbitmq.tag:administrator">>]}}, + {oauth_provider_id, ?OAUTH_PROVIDER_A} + ], + RabbitMQ2 = [ + {id, ?RABBITMQ_RESOURCE_TWO} + ], + set_env(resource_servers, #{ + ?RABBITMQ_RESOURCE_ONE => RabbitMQ1, + ?RABBITMQ_RESOURCE_TWO => RabbitMQ2 + }), + [{?RABBITMQ_RESOURCE_ONE, RabbitMQ1}, {?RABBITMQ_RESOURCE_TWO, RabbitMQ2}] + ++ Config; + +init_per_group(_any, Config) -> + Config. + +end_per_group(with_default_oauth_provider_A, Config) -> + unset_env(default_oauth_provider), + Config; + +end_per_group(with_default_oauth_provider_B, Config) -> + unset_env(default_oauth_provider), + Config; + +end_per_group(with_rabbitmq_as_resource_server_id, Config) -> + unset_env(resource_server_id), + Config; + +end_per_group(with_empty_scope_prefix, Config) -> + unset_env(scope_prefix), + Config; + +end_per_group(with_verify_aud_false, Config) -> + unset_env(verify_aud), + Config; + +end_per_group(with_two_resource_servers, Config) -> + unset_env(resource_servers), + Config; + +end_per_group(with_scope_prefix, Config) -> + unset_env(scope_prefix), + Config; + +end_per_group(with_rabbitmq1_verify_aud_false, Config) -> + RabbitMQServers = get_env(resource_servers, #{}), + Resource = maps:get(?RABBITMQ_RESOURCE_ONE, RabbitMQServers, []), + set_env(resource_servers, maps:put(?RABBITMQ_RESOURCE_ONE, + proplists:delete(verify_aud, Resource), + RabbitMQServers)), + Config; + +end_per_group(with_additional_scopes_key, Config) -> + unset_env(extra_scopes_source), + Config; + +end_per_group(with_preferred_username_claims, Config) -> + unset_env(preferred_username_claims), + Config; + +end_per_group(with_scope_aliases, Config) -> + unset_env(scope_aliases), + Config; + +end_per_group(_any, Config) -> + Config. + + +%% --- Test cases + +resolve_resource_server_for_rabbitmq_audience(_) -> + assert_resource_server_id(?RABBITMQ, ?RABBITMQ). + +resolve_resource_server_for_rabbitmq_plus_unknown_audience(_) -> + assert_resource_server_id(?RABBITMQ, [?RABBITMQ, <<"unknown">>]). + +resolve_resource_server_for_none_audience_returns_no_aud_found(_) -> + assert_resource_server_id({error, no_aud_found}, none). + +resolve_resource_server_for_none_audience_returns_rabbitmq2(_) -> + assert_resource_server_id(?RABBITMQ_RESOURCE_TWO, none). + +resolve_resource_server_for_unknown_audience_returns_no_matching_aud_found(_) -> + assert_resource_server_id({error, no_matching_aud_found}, <<"unknown">>). + +resolve_resource_server_for_none_audience_returns_rabbitmq(_) -> + assert_resource_server_id(?RABBITMQ, none). + +resolve_resource_server_for_unknown_audience_returns_rabbitmq(_) -> + assert_resource_server_id(?RABBITMQ, <<"unknown">>). + +resolve_resource_server_for_unknown_audience_returns_rabbitmq2(_) -> + assert_resource_server_id(?RABBITMQ_RESOURCE_TWO, <<"unknown">>). + +resolve_resource_server_for_none_audience_returns_error(_) -> + assert_resource_server_id( + {error, no_aud_found_cannot_pick_one_from_too_many_resource_servers}, + none). +resolve_resource_server_id_for_any_audience_returns_no_matching_aud_found(_) -> + assert_resource_server_id({error, no_matching_aud_found}, ?RABBITMQ), + assert_resource_server_id({error, no_matching_aud_found}, <<"unknown">>). + +resolve_resource_server_id_for_rabbitmq1(_) -> + assert_resource_server_id(?RABBITMQ_RESOURCE_ONE, ?RABBITMQ_RESOURCE_ONE). + +resolve_resource_server_id_for_rabbitmq2(_) -> + assert_resource_server_id(?RABBITMQ_RESOURCE_TWO, ?RABBITMQ_RESOURCE_TWO). + +resolve_resource_server_id_for_both_resources_returns_error(_) -> + assert_resource_server_id({error, aud_matched_many_resource_servers_only_one_allowed}, + [?RABBITMQ_RESOURCE_TWO, ?RABBITMQ_RESOURCE_ONE]). + +rabbitmq_verify_aud_is_true(_) -> + assert_verify_aud(true, ?RABBITMQ). + +rabbitmq_verify_aud_is_false(_) -> + assert_verify_aud(false, ?RABBITMQ). + +rabbitmq2_verify_aud_is_true(_) -> + assert_verify_aud(true, ?RABBITMQ_RESOURCE_TWO). + +both_resources_oauth_provider_id_is_root(_) -> + assert_oauth_provider_id(root, ?RABBITMQ_RESOURCE_ONE), + assert_oauth_provider_id(root, ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_verify_aud_is_false(_) -> + assert_verify_aud(false, ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_has_default_scope_prefix(_) -> + assert_scope_prefix(erlang:iolist_to_binary([?RABBITMQ_RESOURCE_TWO, <<".">>]), + ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_has_scope_prefix(Config) -> + assert_scope_prefix(?config(scope_prefix, Config), ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_oauth_provider_id_is_root(_) -> + assert_oauth_provider_id(root, ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_oauth_provider_id_is_A(_) -> + assert_oauth_provider_id(?OAUTH_PROVIDER_A, ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_has_no_additional_scopes_key(_) -> + assert_additional_scopes_key(undefined, ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_has_additional_scopes_key(Config) -> + assert_additional_scopes_key(?config(additional_scopes_key, Config), + ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_has_no_preferred_username_claims_but_gets_default(_) -> + assert_preferred_username_claims(?DEFAULT_PREFERRED_USERNAME_CLAIMS, + ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_has_preferred_username_claims_plus_default(Config) -> + assert_preferred_username_claims(?config(preferred_username_claims, Config) + , ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_has_no_scope_aliases(_) -> + assert_scope_aliases(undefined, ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_has_scope_aliases(Config) -> + assert_scope_aliases(?config(scope_aliases, Config), ?RABBITMQ_RESOURCE_TWO). + +rabbitmq_oauth_provider_id_is_root(_) -> + assert_oauth_provider_id(root, ?RABBITMQ). + +rabbitmq_oauth_provider_id_is_A(_) -> + assert_oauth_provider_id(?OAUTH_PROVIDER_A, ?RABBITMQ). + +rabbitmq_has_default_scope_prefix(_) -> + assert_scope_prefix(erlang:iolist_to_binary([?RABBITMQ, <<".">>]), ?RABBITMQ). + +rabbitmq_has_scope_prefix(Config) -> + assert_scope_prefix(?config(scope_prefix, Config), ?RABBITMQ). + +rabbitmq_has_empty_scope_prefix(_) -> + assert_scope_prefix(<<"">>, ?RABBITMQ). + +rabbitmq_has_no_additional_scopes_key(_) -> + assert_additional_scopes_key(undefined, ?RABBITMQ). + +rabbitmq_has_additional_scopes_key(Config) -> + assert_additional_scopes_key(?config(additional_scopes_key, Config), + ?RABBITMQ). + +rabbitmq_has_no_preferred_username_claims_but_gets_default(_) -> + assert_preferred_username_claims(?DEFAULT_PREFERRED_USERNAME_CLAIMS, ?RABBITMQ). + +rabbitmq_has_preferred_username_claims(Config) -> + assert_preferred_username_claims(?config(preferred_username_claims, Config), + ?RABBITMQ). + +rabbitmq_has_no_scope_aliases(_) -> + assert_scope_aliases(undefined, ?RABBITMQ). + +rabbitmq_has_scope_aliases(Config) -> + assert_scope_aliases(?config(scope_aliases, Config), ?RABBITMQ). + +verify_rabbitmq1_server_configuration(Config) -> + ConfigRabbitMQ = ?config(?RABBITMQ_RESOURCE_ONE, Config), + {ok, ActualRabbitMQ} = resolve_resource_server_from_audience(?RABBITMQ_RESOURCE_ONE), + ?assertEqual(proplists:get_value(id, ConfigRabbitMQ), + ActualRabbitMQ#resource_server.id), + ?assertEqual(proplists:get_value(resource_server_type, ConfigRabbitMQ), + ActualRabbitMQ#resource_server.resource_server_type), + ?assertEqual(proplists:get_value(verify_aud, ConfigRabbitMQ), + ActualRabbitMQ#resource_server.verify_aud), + ?assertEqual(proplists:get_value(scope_prefix, ConfigRabbitMQ), + ActualRabbitMQ#resource_server.scope_prefix), + ?assertEqual(proplists:get_value(extract_scopes_source, ConfigRabbitMQ), + ActualRabbitMQ#resource_server.additional_scopes_key), + ?assertEqual(proplists:get_value(preferred_username_claims, ConfigRabbitMQ), + ActualRabbitMQ#resource_server.preferred_username_claims), + ?assertEqual(proplists:get_value(scope_aliases, ConfigRabbitMQ), + ActualRabbitMQ#resource_server.scope_aliases), + ?assertEqual(proplists:get_value(oauth_provider_id, ConfigRabbitMQ), + ActualRabbitMQ#resource_server.oauth_provider_id). + +%% ----- + +assert_resource_server_id({error, ExpectedError}, Audience) -> + {error, ExpectedError} = resolve_resource_server_from_audience(Audience); +assert_resource_server_id(Expected, Audience) -> + {ok, Actual} = resolve_resource_server_from_audience(Audience), + ?assertEqual(Expected, Actual#resource_server.id). + +assert_verify_aud(Expected, Audience) -> + {ok, Actual} = resolve_resource_server_from_audience(Audience), + ?assertEqual(Expected, Actual#resource_server.verify_aud). + +assert_oauth_provider_id(Expected, Audience) -> + {ok, Actual} = resolve_resource_server_from_audience(Audience), + ct:log("Actual:~p", [Actual]), + ?assertEqual(Expected, Actual#resource_server.oauth_provider_id). + +assert_scope_prefix(Expected, Audience) -> + {ok, Actual} = resolve_resource_server_from_audience(Audience), + ?assertEqual(Expected, Actual#resource_server.scope_prefix). + +assert_additional_scopes_key(Expected, Audience) -> + {ok, Actual} = resolve_resource_server_from_audience(Audience), + ?assertEqual(Expected, Actual#resource_server.additional_scopes_key). + +assert_preferred_username_claims(Expected, Audience) -> + {ok, Actual} = resolve_resource_server_from_audience(Audience), + ?assertEqual(Expected, Actual#resource_server.preferred_username_claims). + +assert_scope_aliases(Expected, Audience) -> + {ok, Actual} = resolve_resource_server_from_audience(Audience), + ?assertEqual(Expected, Actual#resource_server.scope_aliases). + +get_env(Par) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par). +get_env(Par, Def) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par, Def). +set_env(Par, Val) -> + application:set_env(rabbitmq_auth_backend_oauth2, Par, Val). +unset_env(Par) -> + application:unset_env(rabbitmq_auth_backend_oauth2, Par). diff --git a/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE.erl index 0e20f0844863..f85f6d9b2451 100644 --- a/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE.erl +++ b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE.erl @@ -12,6 +12,15 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). +<<<<<<< HEAD +======= +-import(rabbit_oauth2_schema, [ + translate_endpoint_params/2, + translate_oauth_providers/1, + translate_resource_servers/1, + translate_scope_aliases/1 +]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) all() -> [ @@ -24,15 +33,31 @@ all() -> test_oauth_providers_https, test_oauth_providers_https_with_missing_cacertfile, test_oauth_providers_signing_keys, +<<<<<<< HEAD test_without_resource_servers, test_with_one_resource_server, test_with_many_resource_servers, test_resource_servers_attributes +======= + test_without_endpoint_params, + test_with_endpoint_params, + test_with_invalid_endpoint_params, + test_without_resource_servers, + test_with_one_resource_server, + test_with_many_resource_servers, + test_resource_servers_attributes, + test_invalid_oauth_providers_endpoint_params, + test_without_oauth_providers_with_endpoint_params, + test_scope_aliases_configured_as_list_of_properties, + test_scope_aliases_configured_as_map, + test_scope_aliases_configured_as_list_of_missing_properties +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]. test_without_oauth_providers(_) -> +<<<<<<< HEAD #{} = rabbit_oauth2_schema:translate_oauth_providers([]). test_without_resource_servers(_) -> @@ -65,10 +90,105 @@ test_with_many_resource_servers(_) -> Conf = [{["auth_oauth2","resource_servers","rabbitmq1","id"],"rabbitmq1"}, {["auth_oauth2","resource_servers","rabbitmq2","id"],"rabbitmq2"} ], +======= + #{} = translate_oauth_providers([]). + +test_without_resource_servers(_) -> + #{} = translate_resource_servers([]). + +test_without_endpoint_params(_) -> + [] = translate_endpoint_params("oauth_discovery_endpoint_params", []). + +test_with_invalid_endpoint_params(_) -> + try translate_endpoint_params("discovery_endpoint_params", [ + {["auth_oauth2","discovery_endpoint_params"], "some-value1"}]) of + _ -> {throw, should_have_failed} + catch + _ -> ok + end. + +test_with_endpoint_params(_) -> + Conf = [ + {["auth_oauth2","discovery_endpoint_params","param1"], "some-value1"}, + {["auth_oauth2","discovery_endpoint_params","param2"], "some-value2"} + ], + [ {<<"param1">>, <<"some-value1">>}, {<<"param2">>, <<"some-value2">>} ] = + translate_endpoint_params("discovery_endpoint_params", Conf). + +test_invalid_oauth_providers_endpoint_params(_) -> + try translate_oauth_providers([ + {["auth_oauth2","oauth_providers", "X", "discovery_endpoint_params"], ""}]) of + _ -> {throw, should_have_failed} + catch + _ -> ok + end. + +test_without_oauth_providers_with_endpoint_params(_) -> + Conf = [ + {["auth_oauth2","oauth_providers", "A", "discovery_endpoint_params","param1"], + "some-value1"}, + {["auth_oauth2","oauth_providers", "A", "discovery_endpoint_params","param2"], + "some-value2"}, + {["auth_oauth2","oauth_providers", "B", "discovery_endpoint_params","param3"], + "some-value3"} + ], + #{ + <<"A">> := [{discovery_endpoint_params, [ + {<<"param1">>, <<"some-value1">>}, + {<<"param2">>, <<"some-value2">>} + ]}], + <<"B">> := [{discovery_endpoint_params, [ + {<<"param3">>, <<"some-value3">>} + ]}] + + } = translate_oauth_providers(Conf). + +test_with_one_oauth_provider(_) -> + Conf = [ + {["auth_oauth2","oauth_providers","keycloak","issuer"], + "https://rabbit"} + ], + #{<<"keycloak">> := [ + {issuer, "https://rabbit"}] + } = translate_oauth_providers(Conf). + +test_with_one_resource_server(_) -> + Conf = [ + {["auth_oauth2","resource_servers","rabbitmq1","id"],"rabbitmq1"} + ], + #{<<"rabbitmq1">> := [{id, <<"rabbitmq1">>}] + } = translate_resource_servers(Conf). + +test_with_many_oauth_providers(_) -> + Conf = [ + {["auth_oauth2","oauth_providers","keycloak","issuer"], + "https://keycloak"}, + {["auth_oauth2","oauth_providers","uaa","issuer"], + "https://uaa"}, + {["auth_oauth2","oauth_providers","uaa","discovery_endpoint_path"], + "/some-path"} + ], + #{<<"keycloak">> := [{issuer, "https://keycloak"} + ], + <<"uaa">> := [{issuer, "https://uaa"}, + {discovery_endpoint_path, "/some-path"} + ] + } = translate_oauth_providers(Conf). + + +test_with_many_resource_servers(_) -> + Conf = [ + {["auth_oauth2","resource_servers","rabbitmq1","id"], + "rabbitmq1"}, + {["auth_oauth2","resource_servers","rabbitmq2","id"], + "rabbitmq2"} + ], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) #{<<"rabbitmq1">> := [{id, <<"rabbitmq1">>} ], <<"rabbitmq2">> := [{id, <<"rabbitmq2">>} ] +<<<<<<< HEAD } = rabbit_oauth2_schema:translate_resource_servers(Conf). test_oauth_providers_attributes(_) -> @@ -100,10 +220,57 @@ test_resource_servers_attributes(_) -> {["auth_oauth2","resource_servers","rabbitmq1","preferred_username_claims","1"],"userid"}, {["auth_oauth2","resource_servers","rabbitmq1","preferred_username_claims","2"],"groupid"} ], +======= + } = translate_resource_servers(Conf). + +test_oauth_providers_attributes(_) -> + Conf = [ + {["auth_oauth2","oauth_providers","keycloak","issuer"], + "https://keycloak"}, + {["auth_oauth2","oauth_providers","keycloak","default_key"], + "token-key"} + ], + #{<<"keycloak">> := [{default_key, <<"token-key">>}, + {issuer, "https://keycloak"} + ] + } = sort_settings(translate_oauth_providers(Conf)). + +test_resource_servers_attributes(_) -> + Conf = [ + {["auth_oauth2","resource_servers","rabbitmq1","id"], + "rabbitmq1xxx"}, + {["auth_oauth2","resource_servers","rabbitmq1","scope_prefix"], + "somescope."}, + {["auth_oauth2","resource_servers","rabbitmq1","additional_scopes_key"], + "roles"}, + {["auth_oauth2","resource_servers","rabbitmq1","preferred_username_claims","1"], + "userid"}, + {["auth_oauth2","resource_servers","rabbitmq1","preferred_username_claims","2"], + "groupid"} + ], + #{<<"rabbitmq1xxx">> := [{extra_scopes_source, <<"roles">>}, + {id, <<"rabbitmq1xxx">>}, + {preferred_username_claims, [<<"userid">>, <<"groupid">>]}, + {scope_prefix, <<"somescope.">>} + ] + } = sort_settings(translate_resource_servers(Conf)), + + Conf2 = [ + {["auth_oauth2","resource_servers","rabbitmq1","scope_prefix"], + "somescope."}, + {["auth_oauth2","resource_servers","rabbitmq1","additional_scopes_key"], + "roles"}, + {["auth_oauth2","resource_servers","rabbitmq1","preferred_username_claims","1"], + "userid"}, + {["auth_oauth2","resource_servers","rabbitmq1","preferred_username_claims","2"], + "groupid"} + ], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) #{<<"rabbitmq1">> := [{extra_scopes_source, <<"roles">>}, {id, <<"rabbitmq1">>}, {preferred_username_claims, [<<"userid">>, <<"groupid">>]}, {scope_prefix, <<"somescope.">>} +<<<<<<< HEAD ] } = sort_settings(rabbit_oauth2_schema:translate_resource_servers(Conf2)). @@ -112,12 +279,26 @@ test_oauth_providers_attributes_with_invalid_uri(_) -> {["auth_oauth2","oauth_providers","keycloak","default_key"],"token-key"} ], try sort_settings(rabbit_oauth2_schema:translate_oauth_providers(Conf)) of +======= + ] + } = sort_settings(translate_resource_servers(Conf2)). + +test_oauth_providers_attributes_with_invalid_uri(_) -> + Conf = [ + {["auth_oauth2","oauth_providers","keycloak","issuer"], + "http://keycloak"}, + {["auth_oauth2","oauth_providers","keycloak","default_key"], + "token-key"} + ], + try sort_settings(translate_oauth_providers(Conf)) of +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) _ -> {throw, should_have_failed} catch _ -> ok end. test_oauth_providers_algorithms(_) -> +<<<<<<< HEAD Conf = [{["auth_oauth2","oauth_providers","keycloak","issuer"],"https://keycloak"}, {["auth_oauth2","oauth_providers","keycloak","algorithms","2"],"HS256"}, {["auth_oauth2","oauth_providers","keycloak","algorithms","1"],"RS256"} @@ -138,6 +319,41 @@ test_oauth_providers_https(Conf) -> {["auth_oauth2","oauth_providers","keycloak","https","fail_if_no_peer_cert"],true}, {["auth_oauth2","oauth_providers","keycloak","https","cacertfile"],cert_filename(Conf)} ], +======= + Conf = [ + {["auth_oauth2","oauth_providers","keycloak","issuer"], + "https://keycloak"}, + {["auth_oauth2","oauth_providers","keycloak","algorithms","2"], + "HS256"}, + {["auth_oauth2","oauth_providers","keycloak","algorithms","1"], + "RS256"} + ], + #{<<"keycloak">> := [{algorithms, [<<"RS256">>, <<"HS256">>]}, + {issuer, "https://keycloak"} + ] + } = sort_settings(translate_oauth_providers(Conf)). + +test_oauth_providers_https(Conf) -> + + CuttlefishConf = [ + {["auth_oauth2","oauth_providers","keycloak","issuer"], + "https://keycloak"}, + {["auth_oauth2","oauth_providers","keycloak","https","verify"], + verify_none}, + {["auth_oauth2","oauth_providers","keycloak","https","peer_verification"], + verify_peer}, + {["auth_oauth2","oauth_providers","keycloak","https","depth"], + 2}, + {["auth_oauth2","oauth_providers","keycloak","https","hostname_verification"], + wildcard}, + {["auth_oauth2","oauth_providers","keycloak","https","crl_check"], + false}, + {["auth_oauth2","oauth_providers","keycloak","https","fail_if_no_peer_cert"], + true}, + {["auth_oauth2","oauth_providers","keycloak","https","cacertfile"], + cert_filename(Conf)} + ], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) #{<<"keycloak">> := [{https, [{verify, verify_none}, {peer_verification, verify_peer}, {depth, 2}, @@ -146,6 +362,7 @@ test_oauth_providers_https(Conf) -> {fail_if_no_peer_cert, true}, {cacertfile, _CaCertFile} ]}, +<<<<<<< HEAD {issuer, <<"https://keycloak">>} ] } = sort_settings(rabbit_oauth2_schema:translate_oauth_providers(CuttlefishConf)). @@ -156,12 +373,28 @@ test_oauth_providers_https_with_missing_cacertfile(_) -> {["auth_oauth2","oauth_providers","keycloak","https","cacertfile"],"/non-existent.pem"} ], try sort_settings(rabbit_oauth2_schema:translate_oauth_providers(Conf)) of +======= + {issuer, "https://keycloak"} + ] + } = sort_settings(translate_oauth_providers(CuttlefishConf)). + +test_oauth_providers_https_with_missing_cacertfile(_) -> + + Conf = [ + {["auth_oauth2","oauth_providers","keycloak","issuer"], + "https://keycloak"}, + {["auth_oauth2","oauth_providers","keycloak","https","cacertfile"], + "/non-existent.pem"} + ], + try sort_settings(translate_oauth_providers(Conf)) of +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) _ -> {throw, should_have_failed} catch _ -> ok end. test_oauth_providers_signing_keys(Conf) -> +<<<<<<< HEAD CuttlefishConf = [{["auth_oauth2","oauth_providers","keycloak","issuer"],"https://keycloak"}, {["auth_oauth2","oauth_providers","keycloak","signing_keys","2"], cert_filename(Conf)}, {["auth_oauth2","oauth_providers","keycloak","signing_keys","1"], cert_filename(Conf)} @@ -174,10 +407,76 @@ test_oauth_providers_signing_keys(Conf) -> #{<<"1">> := {pem, <<"I'm not a certificate">>}, <<"2">> := {pem, <<"I'm not a certificate">>} } = SigningKeys. +======= + CuttlefishConf = [ + {["auth_oauth2","oauth_providers","keycloak","issuer"], + "https://keycloak"}, + {["auth_oauth2","oauth_providers","keycloak","signing_keys","2"], + cert_filename(Conf)}, + {["auth_oauth2","oauth_providers","keycloak","signing_keys","1"], + cert_filename(Conf)} + ], + #{<<"keycloak">> := [{issuer, "https://keycloak"}, + {signing_keys, SigningKeys} + ] + } = sort_settings(translate_oauth_providers(CuttlefishConf)), + ct:log("SigningKey: ~p", [SigningKeys]), + #{<<"1">> := {pem, <<"I'm not a certificate">>}, + <<"2">> := {pem, <<"I'm not a certificate">>} + } = SigningKeys. + +test_scope_aliases_configured_as_list_of_properties(_) -> + CuttlefishConf = [ + {["auth_oauth2","scope_aliases","1","alias"], + "admin"}, + {["auth_oauth2","scope_aliases","1","scope"], + "rabbitmq.tag:administrator"}, + {["auth_oauth2","scope_aliases","2","alias"], + "developer"}, + {["auth_oauth2","scope_aliases","2","scope"], + "rabbitmq.tag:management rabbitmq.read:*/*"} + ], + #{ + <<"admin">> := [<<"rabbitmq.tag:administrator">>], + <<"developer">> := [<<"rabbitmq.tag:management">>, <<"rabbitmq.read:*/*">>] + } = translate_scope_aliases(CuttlefishConf). + +test_scope_aliases_configured_as_list_of_missing_properties(_) -> + CuttlefishConf = [ + {["auth_oauth2","scope_aliases","1","alias"], + "admin"} + ], + #{} = rabbit_oauth2_schema:translate_scope_aliases(CuttlefishConf), + + CuttlefishConf2 = [ + {["auth_oauth2","scope_aliases","1","scope"], + "rabbitmq.tag:management rabbitmq.read:*/*"} + ], + #{} = rabbit_oauth2_schema:translate_scope_aliases(CuttlefishConf2). + + +test_scope_aliases_configured_as_map(_) -> + CuttlefishConf = [ + {["auth_oauth2","scope_aliases","admin"], + "rabbitmq.tag:administrator"}, + {["auth_oauth2","scope_aliases","developer"], + "rabbitmq.tag:management rabbitmq.read:*/*"} + ], + #{ + <<"admin">> := [<<"rabbitmq.tag:administrator">>], + <<"developer">> := [<<"rabbitmq.tag:management">>, <<"rabbitmq.read:*/*">>] + } = rabbit_oauth2_schema:translate_scope_aliases(CuttlefishConf). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) cert_filename(Conf) -> string:concat(?config(data_dir, Conf), "certs/cert.pem"). sort_settings(MapOfListOfSettings) -> maps:map(fun(_K,List) -> +<<<<<<< HEAD lists:sort(fun({K1,_}, {K2,_}) -> K1 < K2 end, List) end, MapOfListOfSettings). +======= + lists:sort(fun({K1,_}, {K2,_}) -> K1 < K2 end, List) end, + MapOfListOfSettings). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbitmq_auth_backend_oauth2/test/system_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/system_SUITE.erl index 692a9e2ab15d..af586bbddd92 100644 --- a/deps/rabbitmq_auth_backend_oauth2/test/system_SUITE.erl +++ b/deps/rabbitmq_auth_backend_oauth2/test/system_SUITE.erl @@ -11,6 +11,10 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("amqp_client/include/amqp_client.hrl"). +<<<<<<< HEAD +======= +-include_lib("amqp10_common/include/amqp10_framing.hrl"). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -include_lib("eunit/include/eunit.hrl"). -import(rabbit_ct_client_helpers, [close_connection/1, close_channel/1, @@ -46,8 +50,12 @@ groups() -> more_than_one_resource_server_id_not_allowed_in_one_token, mqtt_expired_token, mqtt_expirable_token, +<<<<<<< HEAD web_mqtt_expirable_token, amqp_expirable_token +======= + web_mqtt_expirable_token +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]}, {token_refresh, [], [ @@ -74,7 +82,18 @@ groups() -> ]}, {rich_authorization_requests, [], [ test_successful_connection_with_rich_authorization_request_token +<<<<<<< HEAD ]} +======= + ]}, + {amqp, [shuffle], + [ + amqp_token_expire, + amqp_token_refresh_expire, + amqp_token_refresh_vhost_permission, + amqp_token_refresh_revoked_permissions + ]} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]. %% @@ -101,7 +120,13 @@ init_per_suite(Config) -> end_per_suite(Config) -> rabbit_ct_helpers:run_teardown_steps(Config, rabbit_ct_broker_helpers:teardown_steps()). +<<<<<<< HEAD +======= +init_per_group(amqp, Config) -> + {ok, _} = application:ensure_all_started(rabbitmq_amqp_client), + Config; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) init_per_group(_Group, Config) -> %% The broker is managed by {init,end}_per_testcase(). lists:foreach(fun(Value) -> @@ -110,6 +135,11 @@ init_per_group(_Group, Config) -> [<<"vhost1">>, <<"vhost2">>, <<"vhost3">>, <<"vhost4">>]), Config. +<<<<<<< HEAD +======= +end_per_group(amqp, Config) -> + Config; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end_per_group(_Group, Config) -> %% The broker is managed by {init,end}_per_testcase(). lists:foreach(fun(Value) -> @@ -513,6 +543,7 @@ mqtt_expirable_token0(Port, AdditionalOpts, Connect, Config) -> after Millis * 2 -> ct:fail("missing DISCONNECT packet from server") end. +<<<<<<< HEAD amqp_expirable_token(Config) -> {ok, _} = application:ensure_all_started(rabbitmq_amqp_client), @@ -536,6 +567,22 @@ amqp_expirable_token(Config) -> {ok, Connection} = amqp10_client:open_connection(OpnConf), {ok, Session} = amqp10_client:begin_session_sync(Connection), {ok, LinkPair} = rabbitmq_amqp_client:attach_management_link_pair_sync(Session, <<"my link pair">>), +======= +%% Test that RabbitMQ closes the AMQP 1.0 connection when the token expires. +amqp_token_expire(Config) -> + Seconds = 3, + Millis = Seconds * 1000, + {_Algo, Token} = generate_expirable_token(Config, + [<<"rabbitmq.configure:%2F/*">>, + <<"rabbitmq.write:%2F/*">>, + <<"rabbitmq.read:%2F/*">>], + Seconds), + + %% Send and receive a message. + {Connection, Session, LinkPair} = amqp_init(Token, Config), + QName = atom_to_binary(?FUNCTION_NAME), + Address = rabbitmq_amqp_address:queue(QName), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName, #{}), {ok, Sender} = amqp10_client:attach_sender_link(Session, <<"my sender">>, Address), receive {amqp10_event, {link, Sender, credited}} -> ok @@ -548,7 +595,59 @@ amqp_expirable_token(Config) -> {ok, Msg} = amqp10_client:get_msg(Receiver), ?assertEqual([Body], amqp10_msg:body(Msg)), +<<<<<<< HEAD %% In 4 seconds from now, we expect that RabbitMQ disconnects us because our token expired. +======= + %% In 3 seconds from now, we expect that RabbitMQ disconnects us because our token expired. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) + receive {amqp10_event, + {connection, Connection, + {closed, {unauthorized_access, <<"credential expired">>}}}} -> + ok + after Millis * 2 -> + ct:fail("server did not close our connection") + end. + +<<<<<<< HEAD +======= +%% First, test the success case that an OAuth 2.0 token can be renewed via AMQP 1.0. +%% Second, test that the new token expires. +amqp_token_refresh_expire(Config) -> + Seconds = 3, + Millis = Seconds * 1000, + Scopes = [<<"rabbitmq.configure:%2F/*">>, + <<"rabbitmq.write:%2F/*">>, + <<"rabbitmq.read:%2F/*">>], + {_, Token1} = generate_expirable_token(Config, Scopes, Seconds), + + %% Send and receive a message. + {Connection, Session, LinkPair} = amqp_init(Token1, Config), + QName = atom_to_binary(?FUNCTION_NAME), + Address = rabbitmq_amqp_address:queue(QName), + {ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName, #{}), + {ok, Sender} = amqp10_client:attach_sender_link(Session, <<"my sender">>, Address), + receive {amqp10_event, {link, Sender, credited}} -> ok + after 5000 -> ct:fail({missing_event, ?LINE}) + end, + ok = amqp10_client:send_msg(Sender, amqp10_msg:new(<<"t1">>, <<"m1">>, true)), + {ok, Receiver} = amqp10_client:attach_receiver_link(Session, <<"my receiver">>, Address), + {ok, Msg1} = amqp10_client:get_msg(Receiver), + ?assertEqual([<<"m1">>], amqp10_msg:body(Msg1)), + + %% Renew token before the old one expires. + {_, Token2} = generate_expirable_token(Config, Scopes, Seconds * 2), + ok = rabbitmq_amqp_client:set_token(LinkPair, Token2), + + %% Wait until old token would have expired. + timer:sleep(Millis + 500), + + %% We should still be able to send and receive a message thanks to the new token. + ok = amqp10_client:send_msg(Sender, amqp10_msg:new(<<"t2">>, <<"m2">>, true)), + {ok, Msg2} = amqp10_client:get_msg(Receiver), + ?assertEqual([<<"m2">>], amqp10_msg:body(Msg2)), + + %% In 2.5 seconds from now, we expect that RabbitMQ + %% disconnects us because the new token should expire. receive {amqp10_event, {connection, Connection, {closed, {unauthorized_access, <<"credential expired">>}}}} -> @@ -557,6 +656,179 @@ amqp_expirable_token(Config) -> ct:fail("server did not close our connection") end. +%% Test that RabbitMQ closes the AMQP 1.0 connection if the client +%% submits a new token without any permission to the vhost. +amqp_token_refresh_vhost_permission(Config) -> + {_, Token1} = generate_valid_token(Config), + {Connection, _Session, LinkPair} = amqp_init(Token1, Config), + + {_, Token2} = generate_valid_token(Config, + [<<"rabbitmq.configure:wrongvhost/*">>, + <<"rabbitmq.write:wrongvhost/*">>, + <<"rabbitmq.read:wrongvhost/*">>]), + ok = rabbitmq_amqp_client:set_token(LinkPair, Token2), + receive {amqp10_event, + {connection, Connection, + {closed, {unauthorized_access, Reason}}}} -> + ?assertMatch(<<"access to vhost / failed for new credential:", _/binary>>, + Reason) + after 5000 -> ct:fail({missing_event, ?LINE}) + end. + +%% Test that RabbitMQ closes AMQP 1.0 sessions if the client +%% submits a new token with reduced permissions. +amqp_token_refresh_revoked_permissions(Config) -> + {_, Token1} = generate_expirable_token(Config, + [<<"rabbitmq.configure:%2F/*/*">>, + <<"rabbitmq.write:%2F/*/*">>, + <<"rabbitmq.read:%2F/*/*">>], + 30), + {Connection, Session1, LinkPair} = amqp_init(Token1, Config), + {ok, Session2} = amqp10_client:begin_session_sync(Connection), + {ok, Session3} = amqp10_client:begin_session_sync(Connection), + {ok, Session4} = amqp10_client:begin_session_sync(Connection), + {ok, Session5} = amqp10_client:begin_session_sync(Connection), + {ok, Session6} = amqp10_client:begin_session_sync(Connection), + + {ok, Sender2} = amqp10_client:attach_sender_link_sync( + Session2, <<"sender 2">>, + rabbitmq_amqp_address:exchange(<<"amq.fanout">>)), + receive {amqp10_event, {link, Sender2, credited}} -> ok + after 5000 -> ct:fail({missing_event, ?LINE}) + end, + + QName = <<"q1">>, + {ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName, #{}), + ok = rabbitmq_amqp_client:bind_queue(LinkPair, QName, <<"amq.topic">>, <<"#">>, #{}), + {ok, Receiver3} = amqp10_client:attach_receiver_link( + Session3, <<"receiver 3">>, rabbitmq_amqp_address:queue(QName)), + receive {amqp10_event, {link, Receiver3, attached}} -> ok + after 5000 -> ct:fail({missing_event, ?LINE}) + end, + + {ok, Sender4} = amqp10_client:attach_sender_link_sync(Session4, <<"sender 4">>, null), + receive {amqp10_event, {link, Sender4, credited}} -> ok + after 5000 -> ct:fail({missing_event, ?LINE}) + end, + ok = amqp10_client:send_msg( + Sender4, + amqp10_msg:set_properties( + #{to => rabbitmq_amqp_address:queue(QName)}, + amqp10_msg:new(<<"t4">>, <<"m4a">>))), + receive {amqp10_disposition, {accepted, <<"t4">>}} -> ok + after 5000 -> ct:fail({settled_timeout, <<"t4">>}) + end, + + {ok, Sender5} = amqp10_client:attach_sender_link_sync(Session5, <<"sender 5">>, null), + receive {amqp10_event, {link, Sender5, credited}} -> ok + after 5000 -> ct:fail({missing_event, ?LINE}) + end, + ok = amqp10_client:send_msg( + Sender5, + amqp10_msg:set_properties( + #{to => rabbitmq_amqp_address:exchange(<<"amq.topic">>, <<"topic-1">>)}, + amqp10_msg:new(<<"t5">>, <<"m5a">>))), + receive {amqp10_disposition, {accepted, <<"t5">>}} -> ok + after 5000 -> ct:fail({settled_timeout, <<"t5">>}) + end, + + XName = <<"e1">>, + ok = rabbitmq_amqp_client:declare_exchange(LinkPair, XName, #{type => <<"fanout">>}), + {ok, Sender6} = amqp10_client:attach_sender_link_sync( + Session6, <<"sender 6">>, + rabbitmq_amqp_address:exchange(XName)), + receive {amqp10_event, {link, Sender6, credited}} -> ok + after 5000 -> ct:fail({missing_event, ?LINE}) + end, + + %% Revoke the previous granted permissions on the default vhost. + {_, Token2} = generate_expirable_token( + Config, + [ + %% Set configure access on q1 and e1 so that we can delete this queue and exchange later. + <<"rabbitmq.configure:%2F/*1/nope">>, + %% Set write access on amq.topic so that we can test the revoked topic permission. + <<"rabbitmq.write:%2F/amq.topic/nope">>, + <<"rabbitmq.read:%2F/nope/nope">>], + 30), + flush(<<"setting token...">>), + ok = rabbitmq_amqp_client:set_token(LinkPair, Token2), + + %% We expect RabbitMQ to close Session2 because we are no longer allowed to write to exchange amq.fanout. + receive + {amqp10_event, + {session, Session2, + {ended, + #'v1_0.error'{ + condition = ?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, + description = {utf8, <<"write access to exchange 'amq.fanout' in vhost '/' refused", _/binary>>}}}}} -> ok + after 5000 -> ct:fail({missing_event, ?LINE}) + end, + + %% We expect RabbitMQ to close Session3 because we are no longer allowed to read from queue q1. + %% This complies with the user expectation in + %% https://github.com/rabbitmq/rabbitmq-server/discussions/11364 + receive + {amqp10_event, + {session, Session3, + {ended, + #'v1_0.error'{ + condition = ?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, + description = {utf8, <<"read access to queue 'q1' in vhost '/' refused", _/binary>>}}}}} -> ok + after 5000 -> ct:fail({missing_event, ?LINE}) + end, + + ok = amqp10_client:send_msg( + Sender4, + amqp10_msg:set_properties( + #{to => rabbitmq_amqp_address:queue(QName)}, + amqp10_msg:new(<<"t4">>, <<"m4b">>))), + %% We expect RabbitMQ to close Session4 because we are no longer allowed to write to the default exchange. + receive + {amqp10_event, + {session, Session4, + {ended, + #'v1_0.error'{ + condition = ?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, + description = {utf8, <<"write access to exchange 'amq.default' in vhost '/' refused", _/binary>>}}}}} -> ok + after 5000 -> ct:fail({missing_event, ?LINE}) + end, + + ok = amqp10_client:send_msg( + Sender5, + amqp10_msg:set_properties( + #{to => rabbitmq_amqp_address:exchange(<<"amq.topic">>, <<"topic-1">>)}, + amqp10_msg:new(<<"t5">>, <<"m5b">>))), + %% We expect RabbitMQ to close Session5 because we are no longer allowed to write to topic topic-1. + receive + {amqp10_event, + {session, Session5, + {ended, + #'v1_0.error'{ + condition = ?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, + description = {utf8, <<"write access to topic 'topic-1' in exchange" + " 'amq.topic' in vhost '/' refused", _/binary>>}}}}} -> ok + after 5000 -> ct:fail({missing_event, ?LINE}) + end, + + %% We expect RabbitMQ to close Session6 because we are no longer allowed to write to exchange e1. + receive + {amqp10_event, + {session, Session6, + {ended, + #'v1_0.error'{ + condition = ?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, + description = {utf8, <<"write access to exchange 'e1' in vhost '/' refused", _/binary>>}}}}} -> ok + after 5000 -> ct:fail({missing_event, ?LINE}) + end, + + ?assertMatch({ok, #{message_count := 2}}, + rabbitmq_amqp_client:delete_queue(LinkPair, QName)), + ok = rabbitmq_amqp_client:delete_exchange(LinkPair, XName), + ok = amqp10_client:end_session(Session1), + ok = amqp10_client:close_connection(Connection). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) test_successful_connection_with_complex_claim_as_a_map(Config) -> {_Algo, Token} = generate_valid_token_with_extra_fields( Config, @@ -787,3 +1059,33 @@ test_failed_connection_with_non_existent_scope_alias_in_scope_field(Config) -> more_than_one_resource_server_id_not_allowed_in_one_token(Config) -> {_Algo, Token} = generate_valid_token(Config, <<"rmq.configure:*/*">>, [<<"prod">>, <<"dev">>]), {error, _} = open_unmanaged_connection(Config, 0, <<"username">>, Token). +<<<<<<< HEAD +======= + +amqp_init(Token, Config) -> + OpnConf = amqp_connection_config(Token, Config), + {ok, Connection} = amqp10_client:open_connection(OpnConf), + receive {amqp10_event, {connection, Connection, opened}} -> ok + after 5000 -> ct:fail({missing_event, ?LINE}) + end, + {ok, Session} = amqp10_client:begin_session_sync(Connection), + {ok, LinkPair} = rabbitmq_amqp_client:attach_management_link_pair_sync(Session, <<"my link pair">>), + {Connection, Session, LinkPair}. + +amqp_connection_config(Token, Config) -> + Host = proplists:get_value(rmq_hostname, Config), + Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp), + #{address => Host, + port => Port, + container_id => <<"my container">>, + sasl => {plain, <<>>, Token}}. + +flush(Prefix) -> + receive + Msg -> + ct:pal("~p flushed: ~p~n", [Prefix, Msg]), + flush(Prefix) + after 1 -> + ok + end. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl index c8b3f296e213..d46f788c830f 100644 --- a/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl +++ b/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl @@ -11,6 +11,7 @@ -include_lib("rabbit_common/include/rabbit.hrl"). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). +<<<<<<< HEAD all() -> @@ -24,6 +25,29 @@ all() -> test_unsuccessful_access_without_scopes, test_successful_access_with_a_token_with_variables_in_scopes, test_successful_access_with_a_parsed_token, +======= +-include("oauth2.hrl"). + +-import(rabbit_auth_backend_oauth2, [ + user_login_authentication/2, + user_login_authorization/2, + normalize_token_scope/2, + check_vhost_access/3]). +-import(rabbit_oauth2_resource_server, [ + new_resource_server/1 +]). + +all() -> + [ + filter_matching_scope_prefix_and_drop_it, + normalize_token_scopes_with_scope_prefix, + normalize_token_scope_from_space_separated_list_in_scope_claim, + normalize_token_scope_without_scope_claim, + + unsuccessful_access_without_scopes, + successful_access_with_a_token_with_variables_in_scopes, + successful_access_with_a_parsed_token, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) test_successful_access_with_a_token_that_has_tag_scopes, test_unsuccessful_access_with_a_bogus_token, test_restricted_vhost_access_with_a_valid_token, @@ -31,10 +55,16 @@ all() -> test_token_expiration, test_invalid_signature, test_incorrect_kid, +<<<<<<< HEAD test_post_process_token_payload, test_post_process_token_payload_keycloak, test_post_process_payload_rich_auth_request, test_post_process_payload_rich_auth_request_using_regular_expression_with_cluster, +======= + normalize_token_scope_with_keycloak_scopes, + normalize_token_scope_with_rich_auth_request, + normalize_token_scope_with_rich_auth_request_using_regular_expression_with_cluster, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_scope_field, test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_extra_scope_source_field, test_username_from, @@ -57,7 +87,11 @@ groups() -> test_successful_authentication_without_scopes, test_successful_access_with_a_token_that_uses_single_scope_alias_in_extra_scope_source_field, test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_extra_scope_source_field, +<<<<<<< HEAD test_post_process_token_payload_complex_claims, +======= + normalize_token_scope_with_additional_scopes_complex_claims, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field_and_custom_scope_prefix ]} @@ -66,6 +100,7 @@ groups() -> init_per_suite(Config) -> application:load(rabbitmq_auth_backend_oauth2), Env = application:get_all_env(rabbitmq_auth_backend_oauth2), +<<<<<<< HEAD Config1 = rabbit_ct_helpers:set_config(Config, {env, Env}), rabbit_ct_helpers:run_setup_steps(Config1, []). @@ -76,6 +111,12 @@ end_per_suite(Config) -> application:set_env(rabbitmq_auth_backend_oauth2, K, V) end, Env), +======= + lists:foreach(fun({K, _V}) -> unset_env(K) end, Env), + rabbit_ct_helpers:run_setup_steps(Config, []). + +end_per_suite(Config) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_helpers:run_teardown_steps(Config). init_per_group(with_rabbitmq_node, Config) -> @@ -91,7 +132,11 @@ init_per_group(with_rabbitmq_node, Config) -> rabbit_ct_helpers:run_steps(Config2, rabbit_ct_broker_helpers:setup_steps()); init_per_group(with_resource_server_id, Config) -> +<<<<<<< HEAD application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>), +======= + set_env(resource_server_id, <<"rabbitmq">>), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Config; init_per_group(_, Config) -> @@ -104,6 +149,7 @@ end_per_group(_, Config) -> application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id), Config. +<<<<<<< HEAD init_per_testcase(test_post_process_token_payload_complex_claims, Config) -> application:set_env(rabbitmq_auth_backend_oauth2, extra_scopes_source, <<"additional_rabbitmq_scopes">>), application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq-resource">>), @@ -134,6 +180,8 @@ end_per_testcase(test_post_process_token_payload_complex_claims, Config) -> end_per_testcase(_, Config) -> Config. +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% @@ -145,6 +193,7 @@ end_per_testcase(_, Config) -> -define(RESOURCE_SERVER_TYPE, <<"rabbitmq-type">>). -define(DEFAULT_SCOPE_PREFIX, <<"rabbitmq.">>). +<<<<<<< HEAD test_post_process_token_payload(_) -> ArgumentsExpections = [ {{[<<"rabbitmq">>, <<"hare">>], [<<"read">>, <<"write">>, <<"configure">>]}, @@ -239,6 +288,70 @@ test_post_process_payload_rich_auth_request_using_regular_expression_with_cluste Pairs = [ +======= + +normalize_token_scope_with_keycloak_scopes(_) -> + Pairs = [ + %% common case + { + "common case", + #{<<"permissions">> => + [#{<<"rsid">> => <<"2c390fe4-02ad-41c7-98a2-cebb8c60ccf1">>, + <<"rsname">> => <<"allvhost">>, + <<"scopes">> => [<<"rabbitmq-resource.read:*/*">>]}, + #{<<"rsid">> => <<"e7f12e94-4c34-43d8-b2b1-c516af644cee">>, + <<"rsname">> => <<"vhost1">>, + <<"scopes">> => [<<"rabbitmq-resource.write:vhost1/*">>]}, + #{<<"rsid">> => <<"12ac3d1c-28c2-4521-8e33-0952eff10bd9">>, + <<"rsname">> => <<"Default Resource">>, + <<"scopes">> => [<<"unknown-resource.write:vhost1/*">>]} + ] + }, + [<<"read:*/*">>, <<"write:vhost1/*">>] + }, + { + "one scopes field with a string instead of an array", + #{<<"permissions">> => + [#{<<"rsid">> => <<"2c390fe4-02ad-41c7-98a2-cebb8c60ccf1">>, + <<"rsname">> => <<"allvhost">>, + <<"scopes">> => <<"rabbitmq-resource.read:*/*">>}, + #{<<"rsid">> => <<"e7f12e94-4c34-43d8-b2b1-c516af644cee">>, + <<"rsname">> => <<"vhost1">>, + <<"scopes">> => [<<"unknown-resource-read">>]}, + #{<<"rsid">> => <<"12ac3d1c-28c2-4521-8e33-0952eff10bd9">>, + <<"rsname">> => <<"Default Resource">>}]}, + [<<"read:*/*">>] + }, + { + "no scopes field in permissions", + #{<<"permissions">> => + [#{<<"rsid">> => <<"2c390fe4-02ad-41c7-98a2-cebb8c60ccf1">>, + <<"rsname">> => <<"allvhost">>}, + #{<<"rsid">> => <<"e7f12e94-4c34-43d8-b2b1-c516af644cee">>, + <<"rsname">> => <<"vhost1">>}, + #{<<"rsid">> => <<"12ac3d1c-28c2-4521-8e33-0952eff10bd9">>, + <<"rsname">> => <<"Default Resource">>}]}, + [] + }, + { + "no permissions", + #{<<"permissions">> => []}, + [] + }, + {"missing permissions key", #{}, []} + ], + + lists:foreach(fun({Case, Authorization, ExpectedScope}) -> + ResourceServer = new_resource_server(<<"rabbitmq-resource">>), + Token0 = #{<<"authorization">> => Authorization}, + Token = normalize_token_scope(ResourceServer, Token0), + ?assertEqual(ExpectedScope, uaa_jwt:get_scope(Token), Case) + end, Pairs). + +normalize_token_scope_with_rich_auth_request_using_regular_expression_with_cluster(_) -> + + Pairs = [ +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) { "should filter out those permisions whose locations do not refer to cluster : {resource_server_id}", [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, <<"locations">> => [<<"cluster:rabbitmq-test">>], @@ -249,7 +362,11 @@ test_post_process_payload_rich_auth_request_using_regular_expression_with_cluste <<"actions">> => [<<"read">>] } ], +<<<<<<< HEAD [<<"rabbitmq-test.read:*/*/*">> ] +======= + [<<"read:*/*/*">> ] +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }, { "can use regular expression on any location's attribute ", @@ -258,7 +375,11 @@ test_post_process_payload_rich_auth_request_using_regular_expression_with_cluste <<"actions">> => [<<"read">>] } ], +<<<<<<< HEAD [<<"rabbitmq-test.read:^finance-*/*/*">> ] +======= + [<<"read:^finance-*/*/*">> ] +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }, { "should filter out any location which does not match the cluster's pattern ", @@ -274,6 +395,7 @@ test_post_process_payload_rich_auth_request_using_regular_expression_with_cluste lists:foreach( fun({Case, Permissions, ExpectedScope}) -> +<<<<<<< HEAD Payload = post_process_payload_with_rich_auth_request(<<"rabbitmq-test">>, Permissions), ?assertEqual(lists:sort(ExpectedScope), lists:sort(maps:get(<<"scope">>, Payload)), Case) end, Pairs). @@ -674,6 +796,426 @@ test_successful_authorization_without_scopes(_) -> {ok, _ } = rabbit_auth_backend_oauth2:user_login_authorization(Username, [{password, Token}]). +======= + ResourceServer0 = new_resource_server(<<"rabbitmq-test">>), + ResourceServer = ResourceServer0#resource_server{ + resource_server_type = ?RESOURCE_SERVER_TYPE + }, + Token0 = #{<<"authorization_details">> => Permissions}, + Token = normalize_token_scope(ResourceServer, Token0), + ?assertEqual(lists:sort(ExpectedScope), + lists:sort(uaa_jwt:get_scope(Token)), Case) + end, Pairs). + +normalize_token_scope_with_rich_auth_request(_) -> + + Pairs = [ + { "should merge all permissions for the current cluster", + [ + #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:finance/vhost:primary-*">>], + <<"actions">> => [<<"configure">>] + }, + #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq">>], + <<"actions">> => [<<"management">> ] + }, + #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq">>], + <<"actions">> => [<<"administrator">> ] + } + ], + [ <<"tag:management">>, <<"tag:administrator">> ] + }, + { "should filter out those permisions whose type does not match ", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq">>], + <<"actions">> => [<<"read">>] + }, + #{<<"type">> => <<"unknown">>, + <<"locations">> => [<<"cluster:rabbitmq">>], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/*/*">> ] + }, + { "should filter out those permisions whose type is the empty string", + [ + #{<<"type">> => <<>>, + <<"locations">> => [<<"cluster:rabbitmq">>], + <<"actions">> => [<<"read">>] + } + ], + [ ] + }, + { "should filter out those permisions with empty string action", + [ + #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq">>], + <<"actions">> => <<>> + } + ], + [ ] + }, + { "should filter out those permisions whose locations do not refer to cluster : {resource_server_id}", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq">>], + <<"actions">> => [<<"read">>] + }, + #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq-other">>], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/*/*">> ] + }, + { "should filter out those permisions whose locations' regexpr do not match the cluster : {resource_server_id} ", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbit*">>], + <<"actions">> => [<<"read">>] + }, + #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:*">>], + <<"actions">> => [<<"write">>] + }, + #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq-other">>], + <<"actions">> => [<<"configure">>] + } + ], + [<<"read:*/*/*">>, <<"write:*/*/*">> ] + }, + { "should ignore permissions without actions", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq">>] + }, + #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbit*">>], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/*/*">>] + }, + { "should ignore permissions without locations", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"actions">> => [<<"read">>] + } + ], + [] + }, + { "should ignore unknown actions", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq">>], + <<"actions">> => [<<"read2">>, <<"read">>] + } + ], + [<<"read:*/*/*">> ] + }, + { "should filter out locations with permissions not meant for {resource_server_id}", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq">>, <<"cluster:unknown">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/*/*">> ] + }, + { "should produce a scope for every (action, location) permutation for all locations meant for {resource_server_id}", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [ + <<"cluster:rabbitmq/vhost:a">>, + <<"cluster:rabbitmq/vhost:b">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:a/*/*">>, <<"read:b/*/*">> ] + }, + { "should support all known user tags ", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [ + <<"cluster:rabbitmq/vhost:a">>, <<"cluster:rabbitmq/vhost:b">>, + <<"cluster:other">> ], + <<"actions">> => [ + <<"management">>, <<"policymaker">>, <<"management">>, + <<"monitoring">>] + } + ], + [<<"tag:management">>, <<"tag:policymaker">>, + <<"tag:management">>, <<"tag:monitoring">> ] + }, + { "should produce a scope for every user tag action but only for the clusters that match {resource_server_id}", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [ + <<"cluster:rabbitmq/vhost:a">>, <<"cluster:rabbitmq/vhost:b">>, + <<"cluster:other">> ], + <<"actions">> => [<<"management">>, <<"policymaker">>] + } + ], + [<<"tag:management">>, <<"tag:policymaker">> ] + }, + { "should produce as scope for every location meant for {resource_server_id} multiplied by actions", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [ + <<"cluster:rabbitmq/vhost:a">>, <<"cluster:rabbitmq/vhost:b">> ], + <<"actions">> => [<<"read">>, <<"write">>] + } + ], + [<<"read:a/*/*">>, <<"read:b/*/*">>, <<"write:a/*/*">>, <<"write:b/*/*">> ] + }, + { "should accept single value locations", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => <<"cluster:rabbitmq">>, + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/*/*">> ] + }, + { "should accept single value actions", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => <<"cluster:rabbitmq">>, + <<"actions">> => <<"read">> + } + ], + [<<"read:*/*/*">> ] + }, + { "should merge all scopes produced by each permission", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq/vhost:a">> ], + <<"actions">> => [<<"read">>] + }, + #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq/vhost:b">> ], + <<"actions">> => [<<"write">>] + } + ], + [<<"read:a/*/*">>, <<"write:b/*/*">> ] + }, + { "can grant permission to a queue in any virtual host", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq/queue:b">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/b/*">> ] + }, + { "can grant permission to an exchange in any virtual host", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq/exchange:b">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/b/*">> ] + }, + { "cannot specify both exchange and queue unless they have the same value", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq/queue:b/exchange:c">> ], + <<"actions">> => [<<"read">>] + } + ], + [] + }, + { "can specify exchange and queue when have same value", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq/queue:*/exchange:*">> ], + <<"actions">> => [<<"read">>] + } + ], + [ <<"read:*/*/*">> ] + }, + { "can specify routing-key only -> on any vhost and on any queue if that makes sense ", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq/routing-key:b">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/*/b">> ] + }, + { "can specify vhost, queue or exchange and routing-key that combine fixed values and wildcards", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [ + <<"cluster:rabbitmq/vhost:finance-*/queue:*-invoice/routing-key:r-*">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:finance-*/*-invoice/r-*">> ] + }, + { "should ignore any location's attribute other than the supported ones", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [ + <<"cluster:rabbitmq/unknown:finance-*/queue:*-invoice/routing-key:r-*">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/*-invoice/r-*">> ] + }, + { "should not matter the location's attributes order", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq/queue:invoices/vhost:finance/routing-key:r-*">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:finance/invoices/r-*">> ] + }, + { "should ignore locations like //", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq//routing-key:r-*">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/*/r-*">> ] + }, + { "should default to wildcard those attributes with empty value", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [ + <<"cluster:rabbitmq/queue:/vhost:/routing-key:r-*">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/*/r-*">> ] + }, + { "should ignore any location path element which is not compliant with : format", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [ + <<"some-prefix-value/cluster:rabbitmq/vhost:finance-*/queue:*-invoice/routing-key:r-*">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:finance-*/*-invoice/r-*">> ] + }, + { "can use regular expression on any location's attribute", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq/vhost:^finance-*">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:^finance-*/*/*">> ] + }, + { "can use single string value for location", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => <<"cluster:rabbitmq/vhost:^finance-*">>, + <<"actions">> => [<<"read">>] + } + ], + [<<"read:^finance-*/*/*">> ] + }, + { "can use single string value for action", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => <<"cluster:rabbitmq/vhost:^finance-*">>, + <<"actions">> => <<"read">> + } + ], + [<<"read:^finance-*/*/*">> ] + }, + { "should ignore empty permission lists", + [], + [] + } + ], + + lists:foreach(fun({Case, Permissions, ExpectedScope0}) -> + ResourceServer0 = new_resource_server(?RESOURCE_SERVER_ID), + ResourceServer = ResourceServer0#resource_server{ + resource_server_type = ?RESOURCE_SERVER_TYPE + }, + Token0 = #{<<"authorization_details">> => Permissions}, + Token = normalize_token_scope(ResourceServer, Token0), + ExpectedScopes = lists:sort(ExpectedScope0), + ActualScopes = lists:sort(uaa_jwt:get_scope(Token)), + ?assertEqual(ExpectedScopes, ActualScopes, Case) + end, Pairs). + +normalize_token_scope_with_additional_scopes_complex_claims(_) -> + Pairs = [ + { + "claims in form of binary", + <<"rabbitmq.rabbitmq-resource.read:*/* rabbitmq.rabbitmq-resource-read">>, + [<<"read:*/*">>] + }, + {"claims in form of binary - empty result", <<>>, []}, + { + "claims in form of list", + [<<"rabbitmq.rabbitmq-resource.read:*/*">>, + <<"rabbitmq2.rabbitmq-resource-read">>], + [<<"read:*/*">>] + }, + {"claims in form of list - empty result", [], []}, + { + "claims are map with list content", + #{<<"rabbitmq">> => + [<<"rabbitmq-resource.read:*/*">>, + <<"rabbitmq-resource-read">>], + <<"rabbitmq3">> => + [<<"rabbitmq-resource.write:*/*">>, + <<"rabbitmq-resource-write">>]}, + [<<"read:*/*">>, <<"rabbitmq.rabbitmq-resource-read">>] + }, + { + "claims are map with list content - empty result", + #{<<"rabbitmq2">> => + [<<"rabbitmq-resource.read:*/*">>, + <<"rabbitmq-resource-read">>]}, + [] + }, + { + "claims are map with binary content", + #{ <<"rabbitmq">> => <<"rabbitmq-resource.read:*/* rabbitmq-resource-read">>, + <<"rabbitmq3">> => <<"rabbitmq-resource.write:*/* rabbitmq-resource-write">>}, + [<<"rabbitmq.rabbitmq-resource.read:*/*">>, <<"rabbitmq.rabbitmq-resource-read">>] + }, + { + "claims are map with binary content - empty result", + #{<<"rabbitmq2">> => <<"rabbitmq-resource.read:*/* rabbitmq-resource-read">>}, [] + }, + { + "claims are map with empty binary content - empty result", + #{<<"rabbitmq">> => <<>>}, [] + }, + { + "claims are map with empty list content - empty result", + #{<<"rabbitmq">> => []}, [] + }, + { + "no extra claims provided", + [], [] + }, + { + "no extra claims provided", #{}, [] + }], + lists:foreach(fun({Case, Authorization, ExpectedScope0}) -> + ResourceServer0 = new_resource_server(?RESOURCE_SERVER_ID), + ResourceServer = ResourceServer0#resource_server{ + scope_prefix = <<"rabbitmq.rabbitmq-resource.">>, + additional_scopes_key = <<"custom-key">> + }, + Token0 = #{<<"custom-key">> => Authorization}, + Token = normalize_token_scope(ResourceServer, Token0), + ExpectedScopes = lists:sort(ExpectedScope0), + ActualScopes = lists:sort(uaa_jwt:get_scope(Token)), + ?assertEqual(ExpectedScopes, ActualScopes, Case) + end, Pairs). + +test_successful_authentication_without_scopes(_) -> + Jwk = ?UTIL_MOD:fixture_jwk(), + UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], + application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), + + Username = <<"username">>, + Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( + ?UTIL_MOD:fixture_token(), Username), Jwk), + + {ok, #auth_user{username = Username} } = + user_login_authentication(Username, [{password, Token}]). + +test_successful_authorization_without_scopes(_) -> + Jwk = ?UTIL_MOD:fixture_jwk(), + UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], + set_env(key_config, UaaEnv), + + Username = <<"username">>, + Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( + ?UTIL_MOD:fixture_token(), Username), Jwk), + + {ok, _ } = user_login_authorization(Username, [{password, Token}]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) test_successful_access_with_a_token(_) -> %% Generate a token with JOSE @@ -681,6 +1223,7 @@ test_successful_access_with_a_token(_) -> %% Check user access granted by token Jwk = ?UTIL_MOD:fixture_jwk(), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], +<<<<<<< HEAD application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), VHost = <<"vhost">>, @@ -693,24 +1236,49 @@ test_successful_access_with_a_token(_) -> % rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token}), ?assertEqual(true, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"vhost">>, none)), +======= + set_env(key_config, UaaEnv), + + VHost = <<"vhost">>, + Username = <<"username">>, + Token = ?UTIL_MOD:sign_token_hs( + ?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk), + + {ok, #auth_user{username = Username} = User} = + user_login_authentication(Username, [{password, Token}]), + + ?assertEqual(true, check_vhost_access(User, <<"vhost">>, none)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) assert_resource_access_granted(User, VHost, <<"foo">>, configure), assert_resource_access_granted(User, VHost, <<"foo">>, write), assert_resource_access_granted(User, VHost, <<"bar">>, read), assert_resource_access_granted(User, VHost, custom, <<"bar">>, read), +<<<<<<< HEAD assert_topic_access_granted(User, VHost, <<"bar">>, read, #{routing_key => <<"#/foo">>}). test_successful_access_with_a_token_with_variables_in_scopes(_) -> +======= + assert_topic_access_granted(User, VHost, <<"bar">>, read, + #{routing_key => <<"#/foo">>}). + +successful_access_with_a_token_with_variables_in_scopes(_) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% Generate a token with JOSE %% Check authorization with the token %% Check user access granted by token Jwk = ?UTIL_MOD:fixture_jwk(), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], +<<<<<<< HEAD application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), +======= + set_env(key_config, UaaEnv), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) VHost = <<"my-vhost">>, Username = <<"username">>, Token = ?UTIL_MOD:sign_token_hs( +<<<<<<< HEAD ?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token([<<"rabbitmq.read:{vhost}/*/{sub}">>]), Username), Jwk), {ok, #auth_user{username = Username} = User} = @@ -730,11 +1298,36 @@ test_successful_access_with_a_parsed_token(_) -> {ok, _ } = rabbit_auth_backend_oauth2:user_login_authentication(Username, [{rabbit_auth_backend_oauth2, Impl}]). +======= + ?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token( + [<<"rabbitmq.read:{vhost}/*/{sub}">>]), Username), + Jwk), + {ok, #auth_user{username = Username} = User} = + user_login_authentication(Username, #{password => Token}), + + assert_topic_access_granted(User, VHost, <<"bar">>, read, + #{routing_key => Username}). + +successful_access_with_a_parsed_token(_) -> + Jwk = ?UTIL_MOD:fixture_jwk(), + UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], + set_env(key_config, UaaEnv), + + Username = <<"username">>, + Token = ?UTIL_MOD:sign_token_hs( + ?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk), + {ok, #auth_user{impl = Impl} } = + user_login_authentication(Username, [{password, Token}]), + + {ok, _ } = + user_login_authentication(Username, [{rabbit_auth_backend_oauth2, Impl}]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) test_successful_access_with_a_token_that_has_tag_scopes(_) -> Jwk = ?UTIL_MOD:fixture_jwk(), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], +<<<<<<< HEAD application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), Username = <<"username">>, Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token( @@ -742,13 +1335,30 @@ test_successful_access_with_a_token_that_has_tag_scopes(_) -> {ok, #auth_user{username = Username, tags = [management, policymaker]}} = rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]). +======= + set_env(key_config, UaaEnv), + Username = <<"username">>, + Token = ?UTIL_MOD:sign_token_hs( + ?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token( + [<<"rabbitmq.tag:management">>, <<"rabbitmq.tag:policymaker">>]), + Username), Jwk), + + {ok, #auth_user{username = Username, tags = [management, policymaker]}} = + user_login_authentication(Username, [{password, Token}]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field(_) -> Jwk = ?UTIL_MOD:fixture_jwk(), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], +<<<<<<< HEAD application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), Alias = <<"client-alias-1">>, application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{ +======= + set_env(key_config, UaaEnv), + Alias = <<"client-alias-1">>, + set_env(scope_aliases, #{ +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Alias => [ <<"rabbitmq.configure:vhost/one">>, <<"rabbitmq.write:vhost/two">>, @@ -766,7 +1376,11 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field( ?UTIL_MOD:token_with_scope_alias_in_scope_field(Alias), Username), Jwk), {ok, #auth_user{username = Username} = AuthUser} = +<<<<<<< HEAD rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), +======= + user_login_authentication(Username, [{password, Token}]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) assert_vhost_access_granted(AuthUser, VHost), assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>), @@ -786,10 +1400,17 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field( test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field_and_custom_scope_prefix(_) -> Jwk = ?UTIL_MOD:fixture_jwk(), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], +<<<<<<< HEAD application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), application:set_env(rabbitmq_auth_backend_oauth2, scope_prefix, <<>>), Alias = <<"client-alias-1">>, application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{ +======= + set_env(key_config, UaaEnv), + set_env(scope_prefix, <<>>), + Alias = <<"client-alias-1">>, + set_env(scope_aliases, #{ +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Alias => [ <<"configure:vhost/one">>, <<"write:vhost/two">>, @@ -807,7 +1428,11 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field_ ?UTIL_MOD:token_with_scope_alias_in_scope_field(Alias), Username), Jwk), {ok, #auth_user{username = Username} = AuthUser} = +<<<<<<< HEAD rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), +======= + user_login_authentication(Username, [{password, Token}]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) assert_vhost_access_granted(AuthUser, VHost), assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>), @@ -827,11 +1452,19 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field_ test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_scope_field(_) -> Jwk = ?UTIL_MOD:fixture_jwk(), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], +<<<<<<< HEAD application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), Role1 = <<"client-aliases-1">>, Role2 = <<"client-aliases-2">>, Role3 = <<"client-aliases-3">>, application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{ +======= + set_env(key_config, UaaEnv), + Role1 = <<"client-aliases-1">>, + Role2 = <<"client-aliases-2">>, + Role3 = <<"client-aliases-3">>, + set_env(scope_aliases, #{ +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Role1 => [ <<"rabbitmq.configure:vhost/one">>, <<"rabbitmq.tag:management">> @@ -850,10 +1483,18 @@ test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_scope_fi VHost = <<"vhost">>, Username = <<"username">>, Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( +<<<<<<< HEAD ?UTIL_MOD:token_with_scope_alias_in_scope_field([Role1, Role2, Role3]), Username), Jwk), {ok, #auth_user{username = Username} = AuthUser} = rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), +======= + ?UTIL_MOD:token_with_scope_alias_in_scope_field([Role1, Role2, Role3]), + Username), Jwk), + + {ok, #auth_user{username = Username} = AuthUser} = + user_login_authentication(Username, [{password, Token}]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) assert_vhost_access_granted(AuthUser, VHost), assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>), @@ -872,10 +1513,17 @@ test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_scope_fi test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_scope_field(_) -> Jwk = ?UTIL_MOD:fixture_jwk(), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], +<<<<<<< HEAD application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>), Alias = <<"client-alias-33">>, application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{ +======= + set_env(key_config, UaaEnv), + set_env(resource_server_id, <<"rabbitmq">>), + Alias = <<"client-alias-33">>, + set_env(scope_aliases, #{ +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) <<"non-existent-alias-23948sdkfjsdof8">> => [ <<"rabbitmq.configure:vhost/one">>, <<"rabbitmq.write:vhost/two">>, @@ -890,7 +1538,11 @@ test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_scope_fie Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( ?UTIL_MOD:token_with_scope_alias_in_scope_field(Alias), Username), Jwk), +<<<<<<< HEAD {ok, AuthUser} = rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), +======= + {ok, AuthUser} = user_login_authentication(Username, [{password, Token}]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) assert_vhost_access_denied(AuthUser, VHost), assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>), @@ -909,10 +1561,17 @@ test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_scope_fie test_successful_access_with_a_token_that_uses_single_scope_alias_in_extra_scope_source_field(_) -> Jwk = ?UTIL_MOD:fixture_jwk(), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], +<<<<<<< HEAD application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), application:set_env(rabbitmq_auth_backend_oauth2, extra_scopes_source, <<"claims">>), Alias = <<"client-alias-1">>, application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{ +======= + set_env(key_config, UaaEnv), + set_env(extra_scopes_source, <<"claims">>), + Alias = <<"client-alias-1">>, + set_env(scope_aliases, #{ +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Alias => [ <<"rabbitmq.configure:vhost/one">>, <<"rabbitmq.write:vhost/two">>, @@ -925,9 +1584,16 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_extra_scope_ VHost = <<"vhost">>, Username = <<"username">>, Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( +<<<<<<< HEAD ?UTIL_MOD:token_with_scope_alias_in_claim_field(Alias, [<<"unrelated">>]), Username), Jwk), {ok, AuthUser} = rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), +======= + ?UTIL_MOD:token_with_scope_alias_in_claim_field(Alias, [<<"unrelated">>]), + Username), Jwk), + + {ok, AuthUser} = user_login_authentication(Username, [{password, Token}]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) assert_vhost_access_granted(AuthUser, VHost), assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>), @@ -946,12 +1612,21 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_extra_scope_ test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_extra_scope_source_field(_) -> Jwk = ?UTIL_MOD:fixture_jwk(), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], +<<<<<<< HEAD application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), application:set_env(rabbitmq_auth_backend_oauth2, extra_scopes_source, <<"claims">>), Role1 = <<"client-aliases-1">>, Role2 = <<"client-aliases-2">>, Role3 = <<"client-aliases-3">>, application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{ +======= + set_env(key_config, UaaEnv), + set_env(extra_scopes_source, <<"claims">>), + Role1 = <<"client-aliases-1">>, + Role2 = <<"client-aliases-2">>, + Role3 = <<"client-aliases-3">>, + set_env(scope_aliases, #{ +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Role1 => [ <<"rabbitmq.configure:vhost/one">> ], @@ -969,9 +1644,16 @@ test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_extra_sc Username = <<"username">>, Claims = [Role1, Role2, Role3], Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( +<<<<<<< HEAD ?UTIL_MOD:token_with_scope_alias_in_claim_field(Claims, [<<"unrelated">>]), Username), Jwk), {ok, AuthUser} = rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), +======= + ?UTIL_MOD:token_with_scope_alias_in_claim_field(Claims, [<<"unrelated">>]), + Username), Jwk), + + {ok, AuthUser} = user_login_authentication(Username, [{password, Token}]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) assert_vhost_access_granted(AuthUser, VHost), assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>), @@ -990,11 +1672,19 @@ test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_extra_sc test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_extra_scope_source_field(_) -> Jwk = ?UTIL_MOD:fixture_jwk(), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], +<<<<<<< HEAD application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), application:set_env(rabbitmq_auth_backend_oauth2, extra_scopes_source, <<"claims">>), application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>), Alias = <<"client-alias-11">>, application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{ +======= + set_env(key_config, UaaEnv), + set_env(extra_scopes_source, <<"claims">>), + set_env(resource_server_id, <<"rabbitmq">>), + Alias = <<"client-alias-11">>, + set_env(scope_aliases, #{ +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) <<"non-existent-client-alias-9238923789">> => [ <<"rabbitmq.configure:vhost/one">>, <<"rabbitmq.write:vhost/two">>, @@ -1007,9 +1697,16 @@ test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_extra_sco VHost = <<"vhost">>, Username = <<"username">>, Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( +<<<<<<< HEAD ?UTIL_MOD:token_with_scope_alias_in_claim_field(Alias, [<<"unrelated">>]), Username), Jwk), {ok, AuthUser} = rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), +======= + ?UTIL_MOD:token_with_scope_alias_in_claim_field(Alias, [<<"unrelated">>]), + Username), Jwk), + + {ok, AuthUser} = user_login_authentication(Username, [{password, Token}]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) assert_vhost_access_denied(AuthUser, VHost), assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>), @@ -1027,11 +1724,16 @@ test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_extra_sco test_unsuccessful_access_with_a_bogus_token(_) -> Username = <<"username">>, +<<<<<<< HEAD application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>), +======= + set_env(resource_server_id, <<"rabbitmq">>), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Jwk0 = ?UTIL_MOD:fixture_jwk(), Jwk = Jwk0#{<<"k">> => <<"bm90b2tlbmtleQ">>}, UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], +<<<<<<< HEAD application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), ?assertMatch({refused, _, _}, @@ -1048,11 +1750,31 @@ test_unsuccessful_access_without_scopes(_) -> {ok, #auth_user{username = Username, tags = [], impl = _CredentialsFun } = AuthUser} = rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), +======= + set_env(key_config, UaaEnv), + + ?assertMatch({refused, _, _}, user_login_authentication(Username, + [{password, <<"not a token">>}])). + +unsuccessful_access_without_scopes(_) -> + Username = <<"username">>, + set_env(resource_server_id, <<"rabbitmq">>), + + Jwk = ?UTIL_MOD:fixture_jwk(), + Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( + ?UTIL_MOD:token_without_scopes(), Username), Jwk), + UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], + set_env(key_config, UaaEnv), + + {ok, #auth_user{username = Username, tags = [], impl = _CredentialsFun } + = AuthUser} = user_login_authentication(Username, [{password, Token}]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) assert_vhost_access_denied(AuthUser, <<"vhost">>). test_restricted_vhost_access_with_a_valid_token(_) -> Username = <<"username">>, +<<<<<<< HEAD application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>), Jwk = ?UTIL_MOD:fixture_jwk(), @@ -1066,10 +1788,27 @@ test_restricted_vhost_access_with_a_valid_token(_) -> %% access to a different vhost ?assertEqual(false, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"different vhost">>, none)). +======= + set_env(resource_server_id, <<"rabbitmq">>), + + Jwk = ?UTIL_MOD:fixture_jwk(), + Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( + ?UTIL_MOD:fixture_token(), Username), Jwk), + UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], + set_env(key_config, UaaEnv), + + %% this user can authenticate successfully and access certain vhosts + {ok, #auth_user{username = Username, tags = []} = User} = + user_login_authentication(Username, [{password, Token}]), + + %% access to a different vhost + ?assertEqual(false, check_vhost_access(User, <<"different vhost">>, none)). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) test_insufficient_permissions_in_a_valid_token(_) -> VHost = <<"vhost">>, Username = <<"username">>, +<<<<<<< HEAD application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>), Jwk = ?UTIL_MOD:fixture_jwk(), @@ -1079,35 +1818,70 @@ test_insufficient_permissions_in_a_valid_token(_) -> {ok, #auth_user{username = Username} = User} = rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), +======= + set_env(resource_server_id, <<"rabbitmq">>), + + Jwk = ?UTIL_MOD:fixture_jwk(), + Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( + ?UTIL_MOD:fixture_token(), Username), Jwk), + UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], + set_env(key_config, UaaEnv), + + {ok, #auth_user{username = Username} = User} = + user_login_authentication(Username, [{password, Token}]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% access to these resources is not granted assert_resource_access_denied(User, VHost, <<"foo1">>, configure), assert_resource_access_denied(User, VHost, <<"bar">>, write), +<<<<<<< HEAD assert_topic_access_refused(User, VHost, <<"bar">>, read, #{routing_key => <<"foo/#">>}). +======= + assert_topic_access_refused(User, VHost, <<"bar">>, read, + #{routing_key => <<"foo/#">>}). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) test_invalid_signature(_) -> Username = <<"username">>, Jwk = ?UTIL_MOD:fixture_jwk(), WrongJwk = ?UTIL_MOD:fixture_jwk("wrong", <<"GawgguFyGrWKav7AX4VKUg">>), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, WrongJwk}}}], +<<<<<<< HEAD application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>), TokenData = ?UTIL_MOD:token_with_sub(?UTIL_MOD:expirable_token(), Username), Token = ?UTIL_MOD:sign_token_hs(TokenData, Jwk), ?assertMatch({refused, _, [signature_invalid]}, rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}])). +======= + set_env(key_config, UaaEnv), + set_env(resource_server_id, <<"rabbitmq">>), + TokenData = ?UTIL_MOD:token_with_sub(?UTIL_MOD:expirable_token(), Username), + Token = ?UTIL_MOD:sign_token_hs(TokenData, Jwk), + ?assertMatch({refused, _, [signature_invalid]}, + user_login_authentication(Username, [{password, Token}])). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) test_token_expiration(_) -> VHost = <<"vhost">>, Username = <<"username">>, Jwk = ?UTIL_MOD:fixture_jwk(), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], +<<<<<<< HEAD application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>), TokenData = ?UTIL_MOD:token_with_sub(?UTIL_MOD:expirable_token(), Username), Token = ?UTIL_MOD:sign_token_hs(TokenData, Jwk), {ok, #auth_user{username = Username} = User} = rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), +======= + set_env(key_config, UaaEnv), + set_env(resource_server_id, <<"rabbitmq">>), + TokenData = ?UTIL_MOD:token_with_sub(?UTIL_MOD:expirable_token(), Username), + Token = ?UTIL_MOD:sign_token_hs(TokenData, Jwk), + {ok, #auth_user{username = Username} = User} = + user_login_authentication(Username, [{password, Token}]), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) assert_resource_access_granted(User, VHost, <<"foo">>, configure), assert_resource_access_granted(User, VHost, <<"foo">>, write), @@ -1118,16 +1892,26 @@ test_token_expiration(_) -> ?UTIL_MOD:wait_for_token_to_expire(), #{<<"exp">> := Exp} = TokenData, +<<<<<<< HEAD ExpectedError = "Provided JWT token has expired at timestamp " ++ integer_to_list(Exp) ++ " (validated at " ++ integer_to_list(Exp) ++ ")", assert_resource_access_errors(ExpectedError, User, VHost, <<"foo">>, configure), ?assertMatch({refused, _, _}, rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}])). +======= + ExpectedError = "Provided JWT token has expired at timestamp " ++ + integer_to_list(Exp) ++ " (validated at " ++ integer_to_list(Exp) ++ ")", + assert_resource_access_errors(ExpectedError, User, VHost, <<"foo">>, configure), + + ?assertMatch({refused, _, _}, + user_login_authentication(Username, [{password, Token}])). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) test_incorrect_kid(_) -> AltKid = <<"other-token-key">>, Username = <<"username">>, Jwk = ?UTIL_MOD:fixture_jwk(), +<<<<<<< HEAD application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>), Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk, AltKid, true), ?assertMatch({refused, "Authentication using an OAuth 2/JWT token failed: ~tp", [{error,{missing_oauth_provider_attributes, [issuer]}}]}, @@ -1138,6 +1922,23 @@ login_and_check_vhost_access(Username, Token, Vhost) -> rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token}), ?assertEqual(true, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"vhost">>, Vhost)). +======= + unset_env(key_config), + set_env(resource_server_id, <<"rabbitmq">>), + Token = ?UTIL_MOD:sign_token_hs( + ?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk, + AltKid, true), + ?assertMatch( + {refused, "Authentication using an OAuth 2/JWT token failed: ~tp", + [{error,{missing_oauth_provider_attributes, [issuer]}}]}, + user_login_authentication(Username, #{password => Token})). + +login_and_check_vhost_access(Username, Token, Vhost) -> + {ok, #auth_user{username = Username} = User} = + user_login_authentication(Username, #{password => Token}), + + ?assertEqual(true, check_vhost_access(User, <<"vhost">>, Vhost)). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) test_command_json(Config) -> Username = <<"username">>, @@ -1146,9 +1947,18 @@ test_command_json(Config) -> 'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run( [<<"token-key">>], +<<<<<<< HEAD #{node => rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), json => Json}), Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk), rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, login_and_check_vhost_access, [Username, Token, none]). +======= + #{node => rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), + json => Json}), + Token = ?UTIL_MOD:sign_token_hs( + ?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk), + rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, + login_and_check_vhost_access, [Username, Token, none]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) test_username_from(_) -> Pairs = [ @@ -1185,7 +1995,12 @@ test_username_from(_) -> lists:foreach( fun( {Comment, PreferredUsernameClaims, Token, ExpectedUsername}) -> +<<<<<<< HEAD ActualUsername = rabbit_auth_backend_oauth2:username_from(PreferredUsernameClaims, Token), +======= + ActualUsername = rabbit_auth_backend_oauth2:username_from( + PreferredUsernameClaims, Token), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?assertEqual(ExpectedUsername, ActualUsername, Comment) end, Pairs). @@ -1202,10 +2017,20 @@ test_command_pem_file(Config) -> 'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run( [<<"token-key">>], +<<<<<<< HEAD #{node => rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), pem_file => PublicKeyFile}), Token = ?UTIL_MOD:sign_token_rsa(?UTIL_MOD:fixture_token(), Jwk, <<"token-key">>), rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, login_and_check_vhost_access, [Username, Token, none]). +======= + #{node => rabbit_ct_broker_helpers:get_node_config( + Config, 0, nodename), pem_file => PublicKeyFile}), + + Token = ?UTIL_MOD:sign_token_rsa(?UTIL_MOD:fixture_token(), + Jwk, <<"token-key">>), + rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, + login_and_check_vhost_access, [Username, Token, none]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) test_command_pem(Config) -> @@ -1218,10 +2043,20 @@ test_command_pem(Config) -> 'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run( [<<"token-key">>], +<<<<<<< HEAD #{node => rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), pem => Pem}), Token = ?UTIL_MOD:sign_token_rsa(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk, <<"token-key">>), rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, login_and_check_vhost_access, [Username, Token, none]). +======= + #{node => rabbit_ct_broker_helpers:get_node_config( + Config, 0, nodename), pem => Pem}), + + Token = ?UTIL_MOD:sign_token_rsa(?UTIL_MOD:token_with_sub( + ?UTIL_MOD:fixture_token(), Username), Jwk, <<"token-key">>), + rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, + login_and_check_vhost_access, [Username, Token, none]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) test_command_pem_no_kid(Config) -> Username = <<"username">>, @@ -1233,6 +2068,7 @@ test_command_pem_no_kid(Config) -> 'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run( [<<"token-key">>], +<<<<<<< HEAD #{node => rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), pem => Pem}), Token = ?UTIL_MOD:sign_token_no_kid(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk), @@ -1240,6 +2076,18 @@ test_command_pem_no_kid(Config) -> test_own_scope(_) -> +======= + #{node => rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), + pem => Pem}), + + Token = ?UTIL_MOD:sign_token_no_kid(?UTIL_MOD:token_with_sub( + ?UTIL_MOD:fixture_token(), Username), Jwk), + rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, + login_and_check_vhost_access, [Username, Token, none]). + + +filter_matching_scope_prefix_and_drop_it(_) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Examples = [ {<<"foo.">>, [<<"foo">>, <<"foo.bar">>, <<"bar.foo">>, <<"one.two">>, <<"foobar">>, <<"foo.other.third">>], @@ -1250,6 +2098,7 @@ test_own_scope(_) -> ], lists:map( fun({ScopePrefix, Src, Dest}) -> +<<<<<<< HEAD Dest = rabbit_auth_backend_oauth2:filter_scopes(Src, ScopePrefix) end, Examples). @@ -1321,11 +2170,67 @@ test_validate_payload_when_verify_aud_false(_) -> ?assertEqual({ok, #{<<"aud">> => [<<"unknown">>], <<"scope">> => [<<"bar">>, <<"other.third">>]}}, rabbit_auth_backend_oauth2:validate_payload(?RESOURCE_SERVER_ID, WithAudWithUnknownResourceId, ?DEFAULT_SCOPE_PREFIX)). +======= + Dest = rabbit_oauth2_scope:filter_matching_scope_prefix_and_drop_it( + Src, ScopePrefix) + end, + Examples). + +normalize_token_scopes_with_scope_prefix(_) -> + Scenarios = [ + { + <<"">>, + #{ + ?SCOPE_JWT_FIELD => [<<"foo">>, <<"foo.bar">>, <<"foo.other.third">> ] + }, + [<<"foo">>, <<"foo.bar">>, <<"foo.other.third">> ] + }, + { + <<"some-prefix::">>, + #{ + ?SCOPE_JWT_FIELD => [ + <<"some-prefix::foo">>, <<"foo.bar">>, + <<"some-prefix::other.third">> ] + }, + [<<"foo">>, <<"other.third">>] + } + ], + + lists:map(fun({ ScopePrefix, Token0, ExpectedScopes}) -> + ResourceServer0 = new_resource_server(?RESOURCE_SERVER_ID), + ResourceServer = ResourceServer0#resource_server { + scope_prefix = ScopePrefix + }, + Token = normalize_token_scope(ResourceServer, Token0), + ?assertEqual(ExpectedScopes, uaa_jwt:get_scope(Token)) + end, Scenarios). + +normalize_token_scope_from_space_separated_list_in_scope_claim(_) -> + ResourceServer = new_resource_server(?RESOURCE_SERVER_ID), + Token0 = #{ + ?SCOPE_JWT_FIELD => <<"foo rabbitmq.bar bar.foo one.two foobar rabbitmq.other.third">> + }, + Token = normalize_token_scope(ResourceServer, Token0), + ?assertEqual([<<"bar">>, <<"other.third">>], uaa_jwt:get_scope(Token)). + +normalize_token_scope_without_scope_claim(_) -> + ResourceServer = new_resource_server(?RESOURCE_SERVER_ID), + Token0 = #{ }, + ?assertEqual([], uaa_jwt:get_scope(normalize_token_scope(ResourceServer, Token0))). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% %% Helpers %% +<<<<<<< HEAD +======= +set_env(Par, Var) -> + application:set_env(rabbitmq_auth_backend_oauth2, Par, Var). +unset_env(Par) -> + application:unset_env(rabbitmq_auth_backend_oauth2, Par). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) assert_vhost_access_granted(AuthUser, VHost) -> assert_vhost_access_response(true, AuthUser, VHost). @@ -1334,6 +2239,7 @@ assert_vhost_access_denied(AuthUser, VHost) -> assert_vhost_access_response(ExpectedResult, AuthUser, VHost) -> ?assertEqual(ExpectedResult, +<<<<<<< HEAD rabbit_auth_backend_oauth2:check_vhost_access(AuthUser, VHost, none)). assert_resource_access_granted(AuthUser, VHost, ResourceName, PermissionKind) -> @@ -1346,12 +2252,32 @@ assert_resource_access_errors(ExpectedError, AuthUser, VHost, ResourceName, Perm assert_resource_access_response({error, ExpectedError}, AuthUser, VHost, ResourceName, PermissionKind). assert_resource_access_response(ExpectedResult, AuthUser, VHost, ResourceName, PermissionKind) -> +======= + check_vhost_access(AuthUser, VHost, none)). + +assert_resource_access_granted(AuthUser, VHost, ResourceName, PermissionKind) -> + assert_resource_access_response(true, AuthUser, VHost, ResourceName, + PermissionKind). + +assert_resource_access_denied(AuthUser, VHost, ResourceName, PermissionKind) -> + assert_resource_access_response(false, AuthUser, VHost, ResourceName, + PermissionKind). + +assert_resource_access_errors(ExpectedError, AuthUser, VHost, ResourceName, + PermissionKind) -> + assert_resource_access_response({error, ExpectedError}, AuthUser, VHost, + ResourceName, PermissionKind). + +assert_resource_access_response(ExpectedResult, AuthUser, VHost, ResourceName, + PermissionKind) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?assertEqual(ExpectedResult, rabbit_auth_backend_oauth2:check_resource_access( AuthUser, rabbit_misc:r(VHost, queue, ResourceName), PermissionKind, #{})). +<<<<<<< HEAD assert_resource_access_granted(AuthUser, VHost, ResourceKind, ResourceName, PermissionKind) -> assert_resource_access_response(true, AuthUser, VHost, ResourceKind, ResourceName, PermissionKind). @@ -1362,12 +2288,32 @@ assert_resource_access_errors(ExpectedError, AuthUser, VHost, ResourceKind, Reso assert_resource_access_response({error, ExpectedError}, AuthUser, VHost, ResourceKind, ResourceName, PermissionKind). assert_resource_access_response(ExpectedResult, AuthUser, VHost, ResourceKind, ResourceName, PermissionKind) -> +======= +assert_resource_access_granted(AuthUser, VHost, ResourceKind, ResourceName, + PermissionKind) -> + assert_resource_access_response(true, AuthUser, VHost, ResourceKind, + ResourceName, PermissionKind). + +assert_resource_access_denied(AuthUser, VHost, ResourceKind, ResourceName, + PermissionKind) -> + assert_resource_access_response(false, AuthUser, VHost, ResourceKind, + ResourceName, PermissionKind). + +assert_resource_access_errors(ExpectedError, AuthUser, VHost, ResourceKind, + ResourceName, PermissionKind) -> + assert_resource_access_response({error, ExpectedError}, AuthUser, VHost, + ResourceKind, ResourceName, PermissionKind). + +assert_resource_access_response(ExpectedResult, AuthUser, VHost, ResourceKind, + ResourceName, PermissionKind) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?assertEqual(ExpectedResult, rabbit_auth_backend_oauth2:check_resource_access( AuthUser, rabbit_misc:r(VHost, ResourceKind, ResourceName), PermissionKind, #{})). +<<<<<<< HEAD assert_topic_access_granted(AuthUser, VHost, ResourceName, PermissionKind, AuthContext) -> assert_topic_access_response(true, AuthUser, VHost, ResourceName, PermissionKind, AuthContext). @@ -1376,6 +2322,22 @@ assert_topic_access_refused(AuthUser, VHost, ResourceName, PermissionKind, AuthC assert_topic_access_response(ExpectedResult, AuthUser, VHost, ResourceName, PermissionKind, AuthContext) -> ?assertEqual(ExpectedResult, rabbit_auth_backend_oauth2:check_topic_access( +======= +assert_topic_access_granted(AuthUser, VHost, ResourceName, PermissionKind, + AuthContext) -> + assert_topic_access_response(true, AuthUser, VHost, ResourceName, + PermissionKind, AuthContext). + +assert_topic_access_refused(AuthUser, VHost, ResourceName, PermissionKind, + AuthContext) -> + assert_topic_access_response(false, AuthUser, VHost, ResourceName, + PermissionKind, AuthContext). + +assert_topic_access_response(ExpectedResult, AuthUser, VHost, ResourceName, + PermissionKind, AuthContext) -> + ?assertEqual(ExpectedResult, + rabbit_auth_backend_oauth2:check_topic_access( +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) AuthUser, #resource{virtual_host = VHost, kind = topic, diff --git a/deps/rabbitmq_cli/Makefile b/deps/rabbitmq_cli/Makefile index d32cc0b8c948..f2eb814c1172 100644 --- a/deps/rabbitmq_cli/Makefile +++ b/deps/rabbitmq_cli/Makefile @@ -117,7 +117,12 @@ rel:: $(ESCRIPTS) tests:: $(ESCRIPTS) $(verbose) $(MAKE) -C ../../ install-cli $(verbose) $(MAKE) -C ../../ start-background-broker \ +<<<<<<< HEAD PLUGINS="rabbit rabbitmq_federation rabbitmq_stomp rabbitmq_stream_management amqp_client" +======= + PLUGINS="rabbit rabbitmq_federation rabbitmq_stomp rabbitmq_stream_management amqp_client" \ + $(if $(filter khepri,$(RABBITMQ_METADATA_STORE)),,RABBITMQ_FEATURE_FLAGS="-khepri_db") +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) $(gen_verbose) $(MIX_TEST) \ $(if $(RABBITMQ_METADATA_STORE),--exclude $(filter-out $(RABBITMQ_METADATA_STORE),khepri mnesia),) \ $(TEST_FILE); \ diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_connections_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_connections_command.ex index c5a362e8859c..96166fc970ba 100644 --- a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_connections_command.ex +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_connections_command.ex @@ -17,7 +17,11 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListConnectionsCommand do @info_keys ~w(pid name port host peer_port peer_host ssl ssl_protocol ssl_key_exchange ssl_cipher ssl_hash peer_cert_subject peer_cert_issuer peer_cert_validity state +<<<<<<< HEAD channels protocol auth_mechanism user vhost timeout frame_max +======= + channels protocol auth_mechanism user vhost container_id timeout frame_max +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) channel_max client_properties recv_oct recv_cnt send_oct send_cnt send_pend connected_at)a @@ -79,7 +83,11 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListConnectionsCommand do def help_section(), do: :observability_and_health_checks +<<<<<<< HEAD def description(), do: "Lists AMQP 0.9.1 connections for the node" +======= + def description(), do: "Lists AMQP connections for the node" +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) def banner(_, _), do: "Listing connections ..." end diff --git a/deps/rabbitmq_cli/mix.exs b/deps/rabbitmq_cli/mix.exs index e810ce44bb3b..a1765430f847 100644 --- a/deps/rabbitmq_cli/mix.exs +++ b/deps/rabbitmq_cli/mix.exs @@ -11,7 +11,11 @@ defmodule RabbitMQCtl.MixfileBase do [ app: :rabbitmqctl, version: "4.0.0-dev", +<<<<<<< HEAD elixir: ">= 1.13.4 and < 1.18.0", +======= + elixir: ">= 1.13.4 and < 1.19.0", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) build_embedded: Mix.env() == :prod, start_permanent: Mix.env() == :prod, escript: [ diff --git a/deps/rabbitmq_event_exchange/BUILD.bazel b/deps/rabbitmq_event_exchange/BUILD.bazel index 6d0f269239ca..db9894c9ba51 100644 --- a/deps/rabbitmq_event_exchange/BUILD.bazel +++ b/deps/rabbitmq_event_exchange/BUILD.bazel @@ -42,6 +42,10 @@ rabbitmq_app( license_files = [":license_files"], priv = [":priv"], deps = [ +<<<<<<< HEAD +======= + "//deps/amqp10_common:erlang_app", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "//deps/rabbit:erlang_app", "//deps/rabbit_common:erlang_app", ], diff --git a/deps/rabbitmq_event_exchange/Makefile b/deps/rabbitmq_event_exchange/Makefile index f1f5ff81d952..dc78dddb9c60 100644 --- a/deps/rabbitmq_event_exchange/Makefile +++ b/deps/rabbitmq_event_exchange/Makefile @@ -1,12 +1,25 @@ PROJECT = rabbitmq_event_exchange PROJECT_DESCRIPTION = Event Exchange Type +<<<<<<< HEAD +======= +define PROJECT_ENV + [ + {protocol, amqp_0_9_1} + ] +endef + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) define PROJECT_APP_EXTRA_KEYS {broker_version_requirements, []} endef DEPS = rabbit_common rabbit +<<<<<<< HEAD TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers amqp_client +======= +TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers amqp_client rabbitmq_amqp_client +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) DEP_EARLY_PLUGINS = rabbit_common/mk/rabbitmq-early-plugin.mk DEP_PLUGINS = rabbit_common/mk/rabbitmq-plugin.mk diff --git a/deps/rabbitmq_event_exchange/README.md b/deps/rabbitmq_event_exchange/README.md index 1380a4d30f72..428fb4b0161e 100644 --- a/deps/rabbitmq_event_exchange/README.md +++ b/deps/rabbitmq_event_exchange/README.md @@ -1,5 +1,6 @@ # RabbitMQ Event Exchange +<<<<<<< HEAD ## Overview This plugin exposes the internal RabbitMQ event mechanism as messages that clients @@ -152,3 +153,10 @@ TL;DR: Released under the Mozilla Public License 2.0, the same as RabbitMQ. +======= +See the [website](https://www.rabbitmq.com/docs/event-exchange) for documentation. + +## License + +Released under the Mozilla Public License 2.0, the same as RabbitMQ. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbitmq_event_exchange/app.bzl b/deps/rabbitmq_event_exchange/app.bzl index 3ce9ec463521..1d5030fccd0a 100644 --- a/deps/rabbitmq_event_exchange/app.bzl +++ b/deps/rabbitmq_event_exchange/app.bzl @@ -17,6 +17,10 @@ def all_beam_files(name = "all_beam_files"): dest = "ebin", erlc_opts = "//:erlc_opts", deps = [ +<<<<<<< HEAD +======= + "//deps/amqp10_common:erlang_app", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "//deps/rabbit:erlang_app", "//deps/rabbit_common:erlang_app", ], @@ -40,6 +44,10 @@ def all_test_beam_files(name = "all_test_beam_files"): dest = "test", erlc_opts = "//:test_erlc_opts", deps = [ +<<<<<<< HEAD +======= + "//deps/amqp10_common:erlang_app", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "//deps/rabbit:erlang_app", "//deps/rabbit_common:erlang_app", ], diff --git a/deps/rabbitmq_event_exchange/priv/schema/rabbitmq_event_exchange.schema b/deps/rabbitmq_event_exchange/priv/schema/rabbitmq_event_exchange.schema index c8b2efe5acdd..8be7a6e556de 100644 --- a/deps/rabbitmq_event_exchange/priv/schema/rabbitmq_event_exchange.schema +++ b/deps/rabbitmq_event_exchange/priv/schema/rabbitmq_event_exchange.schema @@ -5,3 +5,10 @@ fun(Conf) -> list_to_binary(cuttlefish:conf_get("event_exchange.vhost", Conf)) end}. +<<<<<<< HEAD +======= + +{mapping, "event_exchange.protocol", "rabbitmq_event_exchange.protocol", [ + {datatype, {enum, [amqp_0_9_1, amqp_1_0]}} +]}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbitmq_event_exchange/src/rabbit_exchange_type_event.erl b/deps/rabbitmq_event_exchange/src/rabbit_exchange_type_event.erl index 70251406b20c..2839ee08f432 100644 --- a/deps/rabbitmq_event_exchange/src/rabbit_exchange_type_event.erl +++ b/deps/rabbitmq_event_exchange/src/rabbit_exchange_type_event.erl @@ -11,6 +11,11 @@ -include_lib("rabbit_common/include/rabbit.hrl"). -include_lib("rabbit_common/include/rabbit_framing.hrl"). +<<<<<<< HEAD +======= +-include_lib("amqp10_common/include/amqp10_framing.hrl"). +-include_lib("rabbit/include/mc.hrl"). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -include("rabbit_event_exchange.hrl"). -export([register/0, unregister/0]). @@ -20,8 +25,16 @@ -export([fmt_proplist/1]). %% testing +<<<<<<< HEAD -record(state, {vhost, has_any_bindings +======= +-define(APP_NAME, rabbitmq_event_exchange). + +-record(state, {protocol :: amqp_0_9_1 | amqp_1_0, + vhost :: rabbit_types:vhost(), + has_any_bindings :: boolean() +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }). -rabbit_boot_step({rabbit_event_exchange, @@ -65,6 +78,7 @@ exchange(VHost) -> %%---------------------------------------------------------------------------- init([]) -> +<<<<<<< HEAD VHost = get_vhost(), X = rabbit_misc:r(VHost, exchange, ?EXCH_NAME), HasBindings = case rabbit_binding:list_for_source(X) of @@ -72,10 +86,22 @@ init([]) -> _ -> true end, {ok, #state{vhost = VHost, +======= + {ok, Protocol} = application:get_env(?APP_NAME, protocol), + VHost = get_vhost(), + X = rabbit_misc:r(VHost, exchange, ?EXCH_NAME), + HasBindings = case rabbit_binding:list_for_source(X) of + [] -> false; + _ -> true + end, + {ok, #state{protocol = Protocol, + vhost = VHost, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) has_any_bindings = HasBindings}}. handle_call(_Request, State) -> {ok, not_understood, State}. +<<<<<<< HEAD handle_event(_, #state{has_any_bindings = false} = State) -> {ok, State}; handle_event(#event{type = Type, @@ -100,6 +126,24 @@ handle_event(#event{type = Type, rabbit_queue_type:publish_at_most_once(XName, Msg) end, {ok, State}; +======= +handle_event(#event{type = Type, + props = Props, + reference = none, + timestamp = Timestamp}, + #state{protocol = Protocol, + vhost = VHost, + has_any_bindings = true} = State) -> + case key(Type) of + ignore -> + {ok, State}; + Key -> + XName = exchange(VHost), + Mc = mc_init(Protocol, XName, Key, Props, Timestamp), + _ = rabbit_queue_type:publish_at_most_once(XName, Mc), + {ok, State} + end; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) handle_event(_Event, State) -> {ok, State}. @@ -207,9 +251,115 @@ key(S) -> Tokens -> list_to_binary(string:join(Tokens, ".")) end. +<<<<<<< HEAD fmt_proplist(Props) -> lists:foldl(fun({K, V}, Acc) -> case fmt(a2b(K), V) of +======= +get_vhost() -> + case application:get_env(?APP_NAME, vhost) of + undefined -> + {ok, V} = application:get_env(rabbit, default_vhost), + V; + {ok, V} -> + V + end. + +mc_init(amqp_1_0, #resource{name = XNameBin}, Key, Props, Timestamp) -> + Sections = [#'v1_0.message_annotations'{content = props_to_message_annotations(Props)}, + #'v1_0.properties'{creation_time = {timestamp, Timestamp}}, + #'v1_0.data'{content = <<>>}], + Payload = iolist_to_binary([amqp10_framing:encode_bin(S) || S <- Sections]), + Anns = #{?ANN_EXCHANGE => XNameBin, + ?ANN_ROUTING_KEYS => [Key]}, + mc:init(mc_amqp, Payload, Anns); +mc_init(amqp_0_9_1, XName, Key, Props0, TimestampMillis) -> + Props = [{<<"timestamp_in_ms">>, TimestampMillis} | Props0], + Headers = fmt_proplist(Props), + TimestampSecs = erlang:convert_time_unit(TimestampMillis, millisecond, second), + PBasic = #'P_basic'{delivery_mode = 2, + headers = Headers, + timestamp = TimestampSecs}, + Content = rabbit_basic:build_content(PBasic, <<>>), + {ok, Mc} = mc_amqpl:message(XName, Key, Content), + Mc. + +props_to_message_annotations(Props) -> + KVList = lists:foldl( + fun({K, #resource{virtual_host = Vhost, name = Name}}, Acc) -> + Ann0 = {to_message_annotation_key(K), {utf8, Name}}, + Ann1 = {{symbol, <<"x-opt-vhost">>}, {utf8, Vhost}}, + [Ann0, Ann1 | Acc]; + ({K, V}, Acc) -> + Ann = {to_message_annotation_key(K), + to_message_annotation_val(V)}, + [Ann | Acc] + end, [], Props), + lists:reverse(KVList). + +to_message_annotation_key(Key) -> + Key1 = to_binary(Key), + Pattern = try persistent_term:get(cp_underscore) + catch error:badarg -> + Cp = binary:compile_pattern(<<"_">>), + ok = persistent_term:put(cp_underscore, Cp), + Cp + end, + Key2 = binary:replace(Key1, Pattern, <<"-">>, [global]), + Key3 = case Key2 of + <<"x-", _/binary>> -> + Key2; + _ -> + <<"x-opt-", Key2/binary>> + end, + {symbol, Key3}. + +to_message_annotation_val(V) + when is_boolean(V) -> + {boolean, V}; +to_message_annotation_val(V) + when is_atom(V) -> + {utf8, atom_to_binary(V, utf8)}; +to_message_annotation_val(V) + when is_binary(V) -> + case mc_util:is_utf8_no_null_limited(V) of + true -> + {utf8, V}; + false -> + {binary, V} + end; +to_message_annotation_val(V) + when is_integer(V) -> + {long, V}; +to_message_annotation_val(V) + when is_number(V) -> + %% AMQP double and Erlang float are both 64-bit. + {double, V}; +to_message_annotation_val(V) + when is_pid(V) -> + {utf8, to_pid(V)}; +to_message_annotation_val([{Key, _} | _] = Proplist) + when is_atom(Key) orelse + is_binary(Key) -> + {map, lists:map(fun({K, V}) -> + {{utf8, to_binary(K)}, + to_message_annotation_val(V)} + end, Proplist)}; +to_message_annotation_val([{Key, Type, _Value} | _] = Table) + when is_binary(Key) andalso + is_atom(Type) -> + %% Looks like an AMQP 0.9.1 table + mc_amqpl:from_091(table, Table); +to_message_annotation_val(V) + when is_list(V) -> + {list, [to_message_annotation_val(Val) || Val <- V]}; +to_message_annotation_val(V) -> + {utf8, fmt_other(V)}. + +fmt_proplist(Props) -> + lists:foldl(fun({K, V}, Acc) -> + case fmt(to_binary(K), V) of +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) L when is_list(L) -> lists:append(L, Acc); T -> [T | Acc] end @@ -226,11 +376,16 @@ fmt(K, V) when is_number(V) -> {K, float, V}; fmt(K, V) when is_binary(V) -> {K, longstr, V}; fmt(K, [{_, _}|_] = Vs) -> {K, table, fmt_proplist(Vs)}; fmt(K, Vs) when is_list(Vs) -> {K, array, [fmt(V) || V <- Vs]}; +<<<<<<< HEAD fmt(K, V) when is_pid(V) -> {K, longstr, list_to_binary(rabbit_misc:pid_to_string(V))}; fmt(K, V) -> {K, longstr, list_to_binary( rabbit_misc:format("~1000000000p", [V]))}. +======= +fmt(K, V) when is_pid(V) -> {K, longstr, to_pid(V)}; +fmt(K, V) -> {K, longstr, fmt_other(V)}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% Exactly the same as fmt/2, duplicated only for performance issues fmt(true) -> {bool, true}; @@ -241,6 +396,7 @@ fmt(V) when is_number(V) -> {float, V}; fmt(V) when is_binary(V) -> {longstr, V}; fmt([{_, _}|_] = Vs) -> {table, fmt_proplist(Vs)}; fmt(Vs) when is_list(Vs) -> {array, [fmt(V) || V <- Vs]}; +<<<<<<< HEAD fmt(V) when is_pid(V) -> {longstr, list_to_binary(rabbit_misc:pid_to_string(V))}; fmt(V) -> {longstr, @@ -258,3 +414,18 @@ get_vhost() -> {ok, V} -> V end. +======= +fmt(V) when is_pid(V) -> {longstr, to_pid(V)}; +fmt(V) -> {longstr, fmt_other(V)}. + +fmt_other(V) -> + list_to_binary(rabbit_misc:format("~1000000000p", [V])). + +to_binary(Val) when is_atom(Val) -> + atom_to_binary(Val); +to_binary(Val) when is_binary(Val) -> + Val. + +to_pid(Val) -> + list_to_binary(rabbit_misc:pid_to_string(Val)). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbitmq_event_exchange/test/config_schema_SUITE_data/rabbitmq_event_exchange.snippets b/deps/rabbitmq_event_exchange/test/config_schema_SUITE_data/rabbitmq_event_exchange.snippets index 2fceed017a96..0e59ab732cb7 100644 --- a/deps/rabbitmq_event_exchange/test/config_schema_SUITE_data/rabbitmq_event_exchange.snippets +++ b/deps/rabbitmq_event_exchange/test/config_schema_SUITE_data/rabbitmq_event_exchange.snippets @@ -1,4 +1,5 @@ [ +<<<<<<< HEAD {virtual_host1, "event_exchange.vhost = /", [ @@ -16,4 +17,38 @@ ]} ], [rabbitmq_event_exchange] } +======= +{virtual_host1, + "event_exchange.vhost = /", + [{rabbitmq_event_exchange, [ + {vhost, <<"/">>} + ]}], + [rabbitmq_event_exchange] +}, + +{virtual_host2, + "event_exchange.vhost = dev", + [{rabbitmq_event_exchange, [ + {vhost, <<"dev">>} + ]} + ], + [rabbitmq_event_exchange] +}, + +{protocol_amqp, + "event_exchange.protocol = amqp_1_0", + [{rabbitmq_event_exchange, [ + {protocol, amqp_1_0} + ]}], + [rabbitmq_event_exchange] +}, + +{protocol_amqpl, + "event_exchange.protocol = amqp_0_9_1", + [{rabbitmq_event_exchange, [ + {protocol, amqp_0_9_1} + ]}], + [rabbitmq_event_exchange] +} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]. diff --git a/deps/rabbitmq_event_exchange/test/system_SUITE.erl b/deps/rabbitmq_event_exchange/test/system_SUITE.erl index 76d9199a586c..c0218dfc4cfb 100644 --- a/deps/rabbitmq_event_exchange/test/system_SUITE.erl +++ b/deps/rabbitmq_event_exchange/test/system_SUITE.erl @@ -13,12 +13,43 @@ -compile(export_all). +<<<<<<< HEAD -define(TAG, <<"user_who_performed_action">>). all() -> [ queue_created, authentication, +======= +all() -> + [ + {group, amqp_1_0}, + {group, amqp_0_9_1} + ]. + +groups() -> + [ + {amqp_1_0, [shuffle], + shared_tests() ++ + [ + amqp_1_0_amqp_connection, + amqp_1_0_queue_created, + headers_exchange + ]}, + {amqp_0_9_1, [], + shared_tests() ++ + [ + amqp_0_9_1_amqp_connection, + amqp_0_9_1_queue_created, + unregister + ]} + ]. + +shared_tests() -> + [ + authentication_success, + authentication_failure, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) audit_queue, audit_exchange, audit_exchange_internal_parameter, @@ -37,8 +68,12 @@ all() -> audit_user_tags, audit_permission, audit_topic_permission, +<<<<<<< HEAD resource_alarm, unregister +======= + resource_alarm +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]. %% ------------------------------------------------------------------- @@ -46,6 +81,7 @@ all() -> %% ------------------------------------------------------------------- init_per_suite(Config) -> +<<<<<<< HEAD rabbit_ct_helpers:log_environment(), Config1 = rabbit_ct_helpers:set_config(Config, [ {rmq_nodename_suffix, ?MODULE} @@ -66,17 +102,47 @@ init_per_group(_, Config) -> end_per_group(_, Config) -> Config. +======= + {ok, _} = application:ensure_all_started(rabbitmq_amqp_client), + rabbit_ct_helpers:log_environment(), + Config. + +end_per_suite(Config) -> + Config. + +init_per_group(Group, Config) -> + Config1 = rabbit_ct_helpers:merge_app_env( + Config, + {rabbitmq_event_exchange, [{protocol, Group}]}), + Config2 = rabbit_ct_helpers:set_config( + Config1, [{rmq_nodename_suffix, ?MODULE}]), + rabbit_ct_helpers:run_setup_steps( + Config2, + rabbit_ct_broker_helpers:setup_steps() ++ + rabbit_ct_client_helpers:setup_steps()). + +end_per_group(_Group, Config) -> + rabbit_ct_helpers:run_teardown_steps( + Config, + rabbit_ct_client_helpers:teardown_steps() ++ + rabbit_ct_broker_helpers:teardown_steps()). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) init_per_testcase(Testcase, Config) -> rabbit_ct_helpers:testcase_started(Config, Testcase). end_per_testcase(Testcase, Config) -> rabbit_ct_helpers:testcase_finished(Config, Testcase). +<<<<<<< HEAD +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% ------------------------------------------------------------------- %% Testsuite cases %% ------------------------------------------------------------------- +<<<<<<< HEAD %% Only really tests that we're not completely broken. queue_created(Config) -> Now = os:system_time(seconds), @@ -100,6 +166,50 @@ queue_created(Config) -> authentication(Config) -> +======= +amqp_1_0_queue_created(Config) -> + QName = atom_to_binary(?FUNCTION_NAME), + Headers = queue_created(QName, Config), + ?assertEqual({longstr, QName}, + rabbit_misc:table_lookup(Headers, <<"x-opt-name">>)), + ?assertEqual({table, [{<<"x-queue-type">>, longstr, <<"classic">>}]}, + rabbit_misc:table_lookup(Headers, <<"x-opt-arguments">>)). + +amqp_0_9_1_queue_created(Config) -> + QName = atom_to_binary(?FUNCTION_NAME), + Headers = queue_created(QName,Config), + ?assertEqual({longstr, QName}, + rabbit_misc:table_lookup(Headers, <<"name">>)), + {array, QArgs} = rabbit_misc:table_lookup(Headers, <<"arguments">>), + %% Ideally, instead of a longstr containing the formatted Erlang term, + %% we should expect a table. + ?assertEqual(<<"{<<\"x-queue-type\">>,longstr,<<\"classic\">>}">>, + proplists:get_value(longstr, QArgs)). + +queue_created(QName, Config) -> + Ch = declare_event_queue(Config, <<"queue.created">>), + + Now = os:system_time(second), + #'queue.declare_ok'{} = amqp_channel:call( + Ch, #'queue.declare'{ + queue = QName, + exclusive = true, + arguments = [{<<"x-queue-type">>, longstr, <<"classic">>}] + }), + + receive + {#'basic.deliver'{routing_key = Key}, + #amqp_msg{props = #'P_basic'{headers = Headers, + timestamp = TS}}} -> + %% timestamp is within the last 5 seconds + ?assert(((TS - Now) =< 5)), + ?assertEqual(<<"queue.created">>, Key), + rabbit_ct_client_helpers:close_channel(Ch), + Headers + end. + +authentication_success(Config) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Ch = declare_event_queue(Config, <<"user.#">>), Conn2 = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 0), @@ -107,6 +217,7 @@ authentication(Config) -> {#'basic.deliver'{routing_key = Key}, #amqp_msg{props = #'P_basic'{headers = Headers}}} -> <<"user.authentication.success">> = Key, +<<<<<<< HEAD undefined = rabbit_misc:table_lookup(Headers, <<"vhost">>), {longstr, _PeerHost} = rabbit_misc:table_lookup(Headers, <<"peer_host">>), {bool, false} = rabbit_misc:table_lookup(Headers, <<"ssl">>) @@ -115,6 +226,43 @@ authentication(Config) -> amqp_connection:close(Conn2), rabbit_ct_client_helpers:close_channel(Ch), ok. +======= + {Vhost, PeerHost, Ssl} = + case group_name(Config) of + amqp_0_9_1 -> + {<<"vhost">>, <<"peer_host">>, <<"ssl">>}; + amqp_1_0 -> + {<<"x-opt-vhost">>, <<"x-opt-peer-host">>, <<"x-opt-ssl">>} + end, + undefined = rabbit_misc:table_lookup(Headers, Vhost), + {longstr, _PeerHost} = rabbit_misc:table_lookup(Headers, PeerHost), + {bool, false} = rabbit_misc:table_lookup(Headers, Ssl) + after 5000 -> missing_deliver + end, + + ok = amqp_connection:close(Conn2), + ok = rabbit_ct_client_helpers:close_channel(Ch). + +authentication_failure(Config) -> + Ch = declare_event_queue(Config, <<"user.authentication.*">>), + {error, _} = rabbit_ct_client_helpers:open_unmanaged_connection( + Config, 0, <<"fake user">>, <<"fake password">>), + + receive + {#'basic.deliver'{routing_key = Key}, + #amqp_msg{props = #'P_basic'{headers = Headers}}} -> + ?assertEqual(<<"user.authentication.failure">>, Key), + User = case group_name(Config) of + amqp_0_9_1 -> <<"name">>; + amqp_1_0 -> <<"x-opt-name">> + end, + ?assertEqual({longstr, <<"fake user">>}, + rabbit_misc:table_lookup(Headers, User)) + after 5000 -> missing_deliver + end, + + ok = rabbit_ct_client_helpers:close_channel(Ch). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) audit_queue(Config) -> Ch = declare_event_queue(Config, <<"queue.*">>), @@ -122,13 +270,21 @@ audit_queue(Config) -> #'queue.declare_ok'{queue = Q} = amqp_channel:call(Ch, #'queue.declare'{exclusive = true}), +<<<<<<< HEAD User = proplists:get_value(rmq_username, Config), receive_user_in_event(<<"queue.created">>, User), +======= + receive_user_in_event(<<"queue.created">>, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) #'queue.delete_ok'{} = amqp_channel:call(Ch, #'queue.delete'{queue = Q}), +<<<<<<< HEAD receive_user_in_event(<<"queue.deleted">>, User), +======= + receive_user_in_event(<<"queue.deleted">>, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_client_helpers:close_channel(Ch), ok. @@ -141,13 +297,21 @@ audit_exchange(Config) -> amqp_channel:call(Ch, #'exchange.declare'{exchange = X, type = <<"topic">>}), +<<<<<<< HEAD User = proplists:get_value(rmq_username, Config), receive_user_in_event(<<"exchange.created">>, User), +======= + receive_user_in_event(<<"exchange.created">>, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) #'exchange.delete_ok'{} = amqp_channel:call(Ch, #'exchange.delete'{exchange = X}), +<<<<<<< HEAD receive_user_in_event(<<"exchange.deleted">>, User), +======= + receive_user_in_event(<<"exchange.deleted">>, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_client_helpers:close_channel(Ch), ok. @@ -155,8 +319,12 @@ audit_exchange(Config) -> audit_binding(Config) -> Ch = declare_event_queue(Config, <<"binding.*">>), %% The binding to the event exchange itself is the first queued event +<<<<<<< HEAD User = proplists:get_value(rmq_username, Config), receive_user_in_event(<<"binding.created">>, User), +======= + receive_user_in_event(<<"binding.created">>, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) #'queue.declare_ok'{queue = Q} = amqp_channel:call(Ch, #'queue.declare'{exclusive = true}), @@ -165,26 +333,52 @@ audit_binding(Config) -> amqp_channel:call(Ch, #'queue.bind'{queue = Q, exchange = <<"amq.direct">>, routing_key = <<"test">>}), +<<<<<<< HEAD receive_user_in_event(<<"binding.created">>, User), +======= + receive_user_in_event(<<"binding.created">>, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) #'queue.unbind_ok'{} = amqp_channel:call(Ch, #'queue.unbind'{queue = Q, exchange = <<"amq.direct">>, routing_key = <<"test">>}), +<<<<<<< HEAD receive_user_in_event(<<"binding.deleted">>, User), +======= + receive_user_in_event(<<"binding.deleted">>, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_client_helpers:close_channel(Ch), ok. audit_vhost(Config) -> +<<<<<<< HEAD +======= + Node = atom_to_binary(rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Ch = declare_event_queue(Config, <<"vhost.*">>), User = <<"Bugs Bunny">>, rabbit_ct_broker_helpers:add_vhost(Config, 0, <<"test-vhost">>, User), +<<<<<<< HEAD receive_user_in_event(<<"vhost.created">>, User), rabbit_ct_broker_helpers:delete_vhost(Config, 0, <<"test-vhost">>, User), receive_user_in_event(<<"vhost.deleted">>, User), +======= + Headers = receive_user_in_event(<<"vhost.created">>, User, Config), + + Key = case group_name(Config) of + amqp_0_9_1 -> <<"cluster_state">>; + amqp_1_0 -> <<"x-opt-cluster-state">> + end, + ?assertEqual({table, [{Node, longstr, <<"running">>}]}, + rabbit_misc:table_lookup(Headers, Key)), + + rabbit_ct_broker_helpers:delete_vhost(Config, 0, <<"test-vhost">>, User), + receive_user_in_event(<<"vhost.deleted">>, User, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_client_helpers:close_channel(Ch), ok. @@ -202,19 +396,28 @@ audit_vhost_deletion(Config) -> %% The user that creates the queue is the connection one, not the vhost creator #'queue.declare_ok'{queue = _Q} = amqp_channel:call(Ch2, #'queue.declare'{}), +<<<<<<< HEAD receive_user_in_event(<<"queue.created">>, ConnUser), +======= + receive_user_in_event(<<"queue.created">>, ConnUser, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ok = rabbit_ct_client_helpers:close_connection_and_channel(Conn, Ch2), %% Validate that the user deleting the queue is the one used to delete the vhost, %% not the original user that created the queue (the connection one) rabbit_ct_broker_helpers:delete_vhost(Config, 0, Vhost, User), +<<<<<<< HEAD receive_user_in_event(<<"queue.deleted">>, User), +======= + receive_user_in_event(<<"queue.deleted">>, User, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_client_helpers:close_channel(Ch), ok. audit_channel(Config) -> Ch = declare_event_queue(Config, <<"channel.*">>), +<<<<<<< HEAD User = proplists:get_value(rmq_username, Config), Conn = rabbit_ct_client_helpers:open_unmanaged_connection(Config), @@ -223,12 +426,22 @@ audit_channel(Config) -> rabbit_ct_client_helpers:close_channel(Ch2), receive_user_in_event(<<"channel.closed">>, User), +======= + + Conn = rabbit_ct_client_helpers:open_unmanaged_connection(Config), + {ok, Ch2} = amqp_connection:open_channel(Conn), + receive_user_in_event(<<"channel.created">>, Config), + + rabbit_ct_client_helpers:close_channel(Ch2), + receive_user_in_event(<<"channel.closed">>, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_client_helpers:close_channel(Ch), ok. audit_connection(Config) -> Ch = declare_event_queue(Config, <<"connection.*">>), +<<<<<<< HEAD User = proplists:get_value(rmq_username, Config), Conn = rabbit_ct_client_helpers:open_unmanaged_connection(Config), @@ -237,12 +450,35 @@ audit_connection(Config) -> %% Username is not available in connection_close rabbit_ct_client_helpers:close_connection(Conn), receive_event(<<"connection.closed">>, ?TAG, undefined), +======= + + Conn = rabbit_ct_client_helpers:open_unmanaged_connection(Config), + receive_user_in_event(<<"connection.created">>, Config), + + %% Username is not available in connection_close + rabbit_ct_client_helpers:close_connection(Conn), + Headers = receive_event(<<"connection.closed">>, user_key(Config), undefined), + case group_name(Config) of + amqp_0_9_1 -> + ?assert(lists:keymember(<<"client_properties">>, 1, Headers)); + amqp_1_0 -> + {table, ClientProps} = rabbit_misc:table_lookup(Headers, <<"x-opt-client-properties">>), + ?assertEqual({longstr, <<"Erlang">>}, + rabbit_misc:table_lookup(ClientProps, <<"platform">>)), + {table, Caps} = rabbit_misc:table_lookup(ClientProps, <<"capabilities">>), + ?assertEqual({bool, true}, + rabbit_misc:table_lookup(Caps, <<"basic.nack">>)), + ?assertEqual({bool, true}, + rabbit_misc:table_lookup(Caps, <<"connection.blocked">>)) + end, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_client_helpers:close_channel(Ch), ok. audit_direct_connection(Config) -> Ch = declare_event_queue(Config, <<"connection.*">>), +<<<<<<< HEAD User = proplists:get_value(rmq_username, Config), Conn = rabbit_ct_client_helpers:open_unmanaged_connection_direct(Config), @@ -250,24 +486,43 @@ audit_direct_connection(Config) -> rabbit_ct_client_helpers:close_connection(Conn), receive_event(<<"connection.closed">>, ?TAG, undefined), +======= + + Conn = rabbit_ct_client_helpers:open_unmanaged_connection_direct(Config), + receive_user_in_event(<<"connection.created">>, Config), + + rabbit_ct_client_helpers:close_connection(Conn), + receive_event(<<"connection.closed">>, user_key(Config), undefined), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_client_helpers:close_channel(Ch), ok. audit_consumer(Config) -> Ch = declare_event_queue(Config, <<"consumer.*">>), +<<<<<<< HEAD User = proplists:get_value(rmq_username, Config), receive_user_in_event(<<"consumer.created">>, User), +======= + receive_user_in_event(<<"consumer.created">>, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) #'queue.declare_ok'{queue = Q} = amqp_channel:call(Ch, #'queue.declare'{exclusive = true}), amqp_channel:subscribe(Ch, #'basic.consume'{queue = Q, no_ack = true}, self()), CTag = receive #'basic.consume_ok'{consumer_tag = C} -> C end, +<<<<<<< HEAD receive_user_in_event(<<"consumer.created">>, User), amqp_channel:call(Ch, #'basic.cancel'{consumer_tag = CTag}), receive_user_in_event(<<"consumer.deleted">>, User), +======= + receive_user_in_event(<<"consumer.created">>, Config), + + amqp_channel:call(Ch, #'basic.cancel'{consumer_tag = CTag}), + receive_user_in_event(<<"consumer.deleted">>, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_client_helpers:close_channel(Ch), ok. @@ -282,11 +537,18 @@ audit_exchange_internal_parameter(Config) -> #'exchange.delete_ok'{} = amqp_channel:call(Ch, #'exchange.delete'{exchange = X}), +<<<<<<< HEAD User = proplists:get_value(rmq_username, Config), %% Exchange deletion sets and clears a runtime parameter which acts as a %% kind of lock: receive_user_in_event(<<"parameter.set">>, User), receive_user_in_event(<<"parameter.cleared">>, User), +======= + %% Exchange deletion sets and clears a runtime parameter which acts as a + %% kind of lock: + receive_user_in_event(<<"parameter.set">>, Config), + receive_user_in_event(<<"parameter.cleared">>, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_client_helpers:close_channel(Ch), ok. @@ -299,11 +561,19 @@ audit_parameter(Config) -> ok = rabbit_ct_broker_helpers:set_parameter( Config, 0, VHost, <<"vhost-limits">>, <<"limits">>, [{<<"max-connections">>, 200}], User), +<<<<<<< HEAD receive_user_in_event(<<"parameter.set">>, User), ok = rabbit_ct_broker_helpers:clear_parameter( Config, 0, VHost, <<"vhost-limits">>, <<"limits">>, User), receive_user_in_event(<<"parameter.cleared">>, User), +======= + receive_user_in_event(<<"parameter.set">>, User, Config), + + ok = rabbit_ct_broker_helpers:clear_parameter( + Config, 0, VHost, <<"vhost-limits">>, <<"limits">>, User), + receive_user_in_event(<<"parameter.cleared">>, User, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_client_helpers:close_channel(Ch), ok. @@ -314,10 +584,17 @@ audit_policy(Config) -> rabbit_ct_broker_helpers:set_policy(Config, 0, <<".*">>, <<"all">>, <<"queues">>, [{<<"max-length-bytes">>, 10000}], User), +<<<<<<< HEAD receive_user_in_event(<<"policy.set">>, User), ok = rabbit_ct_broker_helpers:clear_policy(Config, 0, <<".*">>, User), receive_user_in_event(<<"policy.cleared">>, User), +======= + receive_user_in_event(<<"policy.set">>, User, Config), + + ok = rabbit_ct_broker_helpers:clear_policy(Config, 0, <<".*">>, User), + receive_user_in_event(<<"policy.cleared">>, User, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_client_helpers:close_channel(Ch), ok. @@ -330,11 +607,19 @@ audit_vhost_limit(Config) -> ok = rabbit_ct_broker_helpers:set_parameter( Config, 0, VHost, <<"vhost-limits">>, <<"limits">>, [{<<"max-connections">>, 200}], User), +<<<<<<< HEAD receive_user_in_event(<<"vhost.limits.set">>, User), ok = rabbit_ct_broker_helpers:clear_parameter( Config, 0, VHost, <<"vhost-limits">>, <<"limits">>, User), receive_user_in_event(<<"vhost.limits.cleared">>, User), +======= + receive_user_in_event(<<"vhost.limits.set">>, User, Config), + + ok = rabbit_ct_broker_helpers:clear_parameter( + Config, 0, VHost, <<"vhost-limits">>, <<"limits">>, User), + receive_user_in_event(<<"vhost.limits.cleared">>, User, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_client_helpers:close_channel(Ch), ok. @@ -345,10 +630,17 @@ audit_user(Config) -> User = <<"Wabbit">>, rabbit_ct_broker_helpers:add_user(Config, 0, User, User, ActingUser), +<<<<<<< HEAD receive_user_in_event(<<"user.created">>, ActingUser), rabbit_ct_broker_helpers:delete_user(Config, 0, User, ActingUser), receive_user_in_event(<<"user.deleted">>, ActingUser), +======= + receive_user_in_event(<<"user.created">>, ActingUser, Config), + + rabbit_ct_broker_helpers:delete_user(Config, 0, User, ActingUser), + receive_user_in_event(<<"user.deleted">>, ActingUser, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_client_helpers:close_channel(Ch), ok. @@ -360,10 +652,17 @@ audit_user_password(Config) -> rabbit_ct_broker_helpers:add_user(Config, 0, User, User, ActingUser), rabbit_ct_broker_helpers:change_password(Config, 0, User, <<"pass">>, ActingUser), +<<<<<<< HEAD receive_user_in_event(<<"user.password.changed">>, ActingUser), rabbit_ct_broker_helpers:clear_password(Config, 0, User, ActingUser), receive_user_in_event(<<"user.password.cleared">>, ActingUser), +======= + receive_user_in_event(<<"user.password.changed">>, ActingUser, Config), + + rabbit_ct_broker_helpers:clear_password(Config, 0, User, ActingUser), + receive_user_in_event(<<"user.password.cleared">>, ActingUser, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_broker_helpers:delete_user(Config, 0, User, ActingUser), rabbit_ct_client_helpers:close_channel(Ch), @@ -376,7 +675,11 @@ audit_user_tags(Config) -> rabbit_ct_broker_helpers:add_user(Config, 0, User, User, ActingUser), rabbit_ct_broker_helpers:set_user_tags(Config, 0, User, [management], ActingUser), +<<<<<<< HEAD receive_user_in_event(<<"user.tags.set">>, ActingUser), +======= + receive_user_in_event(<<"user.tags.set">>, ActingUser, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_broker_helpers:delete_user(Config, 0, User, ActingUser), @@ -392,10 +695,17 @@ audit_permission(Config) -> rabbit_ct_broker_helpers:add_user(Config, 0, User, User, ActingUser), rabbit_ct_broker_helpers:set_permissions(Config, 0, User, VHost, <<".*">>, <<".*">>, <<".*">>, ActingUser), +<<<<<<< HEAD receive_user_in_event(<<"permission.created">>, ActingUser), rabbit_ct_broker_helpers:clear_permissions(Config, 0, User, VHost, ActingUser), receive_user_in_event(<<"permission.deleted">>, ActingUser), +======= + receive_user_in_event(<<"permission.created">>, ActingUser, Config), + + rabbit_ct_broker_helpers:clear_permissions(Config, 0, User, VHost, ActingUser), + receive_user_in_event(<<"permission.deleted">>, ActingUser, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_broker_helpers:delete_user(Config, 0, User, ActingUser), rabbit_ct_client_helpers:close_channel(Ch), @@ -411,12 +721,20 @@ audit_topic_permission(Config) -> rabbit_ct_broker_helpers:rpc( Config, 0, rabbit_auth_backend_internal, set_topic_permissions, [User, VHost, <<"amq.topic">>, "^a", "^a", ActingUser]), +<<<<<<< HEAD receive_user_in_event(<<"topic.permission.created">>, ActingUser), +======= + receive_user_in_event(<<"topic.permission.created">>, ActingUser, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_broker_helpers:rpc( Config, 0, rabbit_auth_backend_internal, clear_topic_permissions, [User, VHost, ActingUser]), +<<<<<<< HEAD receive_user_in_event(<<"topic.permission.deleted">>, ActingUser), +======= + receive_user_in_event(<<"topic.permission.deleted">>, ActingUser, Config), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_broker_helpers:delete_user(Config, 0, User, ActingUser), rabbit_ct_client_helpers:close_channel(Ch), @@ -453,6 +771,143 @@ unregister(Config) -> lookup, [X])), ok. +<<<<<<< HEAD +======= +%% Test the plugin publishing internally with AMQP 0.9.1 while the client uses AMQP 1.0. +amqp_0_9_1_amqp_connection(Config) -> + QName = atom_to_binary(?FUNCTION_NAME), + Address = rabbitmq_amqp_address:queue(QName), + {Connection1, Session, LinkPair} = amqp_init(Config), + {ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName,#{}), + ok = rabbitmq_amqp_client:bind_queue( + LinkPair, QName, <<"amq.rabbitmq.event">>, <<"connection.*">>, #{}), + {ok, Receiver} = amqp10_client:attach_receiver_link( + Session, <<"receiver">>, Address, settled), + + OpnConf0 = amqp_connection_config(Config), + OpnConf = maps:update(container_id, <<"2nd container">>, OpnConf0), + {ok, Connection2} = amqp10_client:open_connection(OpnConf), + receive {amqp10_event, {connection, Connection2, opened}} -> ok + after 5000 -> ct:fail({missing_event, ?LINE}) + end, + {ok, Msg} = amqp10_client:get_msg(Receiver), + ?assertMatch(#{<<"x-routing-key">> := <<"connection.created">>}, + amqp10_msg:message_annotations(Msg)), + ?assertMatch(#{<<"container_id">> := <<"2nd container">>}, + amqp10_msg:application_properties(Msg)), + ok = amqp10_client:close_connection(Connection2), + + {ok, _} = rabbitmq_amqp_client:delete_queue(LinkPair, QName), + ok = rabbitmq_amqp_client:detach_management_link_pair_sync(LinkPair), + ok = amqp10_client:end_session(Session), + ok = amqp10_client:close_connection(Connection1). + +%% Test the plugin publishing internally with AMQP 1.0 and the client using AMQP 1.0. +amqp_1_0_amqp_connection(Config) -> + QName = atom_to_binary(?FUNCTION_NAME), + Address = rabbitmq_amqp_address:queue(QName), + {Connection1, Session, LinkPair} = amqp_init(Config), + {ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName,#{}), + ok = rabbitmq_amqp_client:bind_queue( + LinkPair, QName, <<"amq.rabbitmq.event">>, <<"connection.*">>, #{}), + {ok, Receiver} = amqp10_client:attach_receiver_link( + Session, <<"receiver">>, Address, settled), + + Now = os:system_time(millisecond), + OpnConf0 = amqp_connection_config(Config), + OpnConf = maps:update(container_id, <<"2nd container">>, OpnConf0), + {ok, Connection2} = amqp10_client:open_connection(OpnConf), + receive {amqp10_event, {connection, Connection2, opened}} -> ok + after 5000 -> ct:fail({missing_event, ?LINE}) + end, + {ok, Msg} = amqp10_client:get_msg(Receiver), + ?assertEqual(<<>>, iolist_to_binary(amqp10_msg:body(Msg))), + MsgAnns = amqp10_msg:message_annotations(Msg), + ?assertMatch(#{<<"x-routing-key">> := <<"connection.created">>, + <<"x-opt-container-id">> := <<"2nd container">>, + <<"x-opt-channel-max">> := ChannelMax} + when is_integer(ChannelMax), + MsgAnns), + %% We expect to receive event properties that have complex types. + ClientProps = maps:get(<<"x-opt-client-properties">>, MsgAnns), + OtpRelease = integer_to_binary(?OTP_RELEASE), + ?assertMatch(#{ + {symbol, <<"version">>} := {utf8, _Version}, + {symbol, <<"product">>} := {utf8, <<"AMQP 1.0 client">>}, + {symbol, <<"platform">>} := {utf8, <<"Erlang/OTP ", OtpRelease/binary>>} + }, + maps:from_list(ClientProps)), + FormattedPid = maps:get(<<"x-opt-pid">>, MsgAnns), + + %% The formatted Pid should include the RabbitMQ node name: + ?assertMatch({match, _}, + re:run(FormattedPid, <<"rmq-ct-system_SUITE">>)), + + #{creation_time := CreationTime} = amqp10_msg:properties(Msg), + ?assert(is_integer(CreationTime)), + ?assert(CreationTime > Now - 5000), + ?assert(CreationTime < Now + 5000), + + ok = amqp10_client:close_connection(Connection2), + {ok, _} = rabbitmq_amqp_client:delete_queue(LinkPair, QName), + ok = rabbitmq_amqp_client:detach_management_link_pair_sync(LinkPair), + ok = amqp10_client:end_session(Session), + ok = amqp10_client:close_connection(Connection1). + +%% Test that routing on specific event properties works. +headers_exchange(Config) -> + XName = <<"my headers exchange">>, + QName = atom_to_binary(?FUNCTION_NAME), + Address = rabbitmq_amqp_address:queue(QName), + OpnConf = amqp_connection_config(Config), + {Connection, Session, LinkPair} = amqp_init(Config), + + ok = rabbitmq_amqp_client:declare_exchange(LinkPair, XName, #{type => <<"headers">>}), + {ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName, #{}), + ok = rabbitmq_amqp_client:bind_queue( + LinkPair, QName, XName, <<>>, + #{<<"x-opt-container-id">> => {utf8, <<"client-2">>}, + <<"x-match">> => {utf8, <<"any-with-x">>}}), + ok = rabbitmq_amqp_client:bind_exchange( + LinkPair, XName, <<"amq.rabbitmq.event">>, <<"connection.created">>, #{}), + {ok, Receiver} = amqp10_client:attach_receiver_link( + Session, <<"receiver">>, Address, settled), + + %% Open two connections. + OpnConf1 = maps:update(container_id, <<"client-1">>, OpnConf), + {ok, Connection1} = amqp10_client:open_connection(OpnConf1), + receive {amqp10_event, {connection, Connection1, opened}} -> ok + after 5000 -> ct:fail({missing_event, ?LINE}) + end, + OpnConf2 = maps:update(container_id, <<"client-2">>, OpnConf), + {ok, Connection2} = amqp10_client:open_connection(OpnConf2), + receive {amqp10_event, {connection, Connection2, opened}} -> ok + after 5000 -> ct:fail({missing_event, ?LINE}) + end, + + %% Thanks to routing via headers exchange on event property + %% x-opt-container-id = client-2 + %% we should only receive the second connection.created event. + ok = amqp10_client:flow_link_credit(Receiver, 2, never, true), + receive {amqp10_msg, Receiver, Msg} -> + ?assertMatch(#{<<"x-routing-key">> := <<"connection.created">>, + <<"x-opt-container-id">> := <<"client-2">>}, + amqp10_msg:message_annotations(Msg)) + after 5000 -> ct:fail({missing_msg, ?LINE}) + end, + receive {amqp10_event, {link, Receiver, credit_exhausted}} -> ok + after 5000 -> ct:fail({missing_event, ?LINE}) + end, + + ok = amqp10_client:close_connection(Connection1), + ok = amqp10_client:close_connection(Connection2), + {ok, _} = rabbitmq_amqp_client:delete_queue(LinkPair, QName), + ok = rabbitmq_amqp_client:delete_exchange(LinkPair, XName), + ok = rabbitmq_amqp_client:detach_management_link_pair_sync(LinkPair), + ok = amqp10_client:end_session(Session), + ok = amqp10_client:close_connection(Connection). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% ------------------------------------------------------------------- %% Helpers %% ------------------------------------------------------------------- @@ -471,17 +926,48 @@ declare_event_queue(Config, RoutingKey) -> end, Ch. +<<<<<<< HEAD receive_user_in_event(Event, User) -> receive_event(Event, ?TAG, {longstr, User}). +======= +user_key(Config) -> + case group_name(Config) of + amqp_0_9_1 -> + <<"user_who_performed_action">>; + amqp_1_0 -> + <<"x-opt-user-who-performed-action">> + end. + +group_name(Config) -> + GroupProps = proplists:get_value(tc_group_properties, Config), + proplists:get_value(name, GroupProps). + +receive_user_in_event(Event, Config) -> + User = proplists:get_value(rmq_username, Config), + receive_user_in_event(Event, User, Config). + +receive_user_in_event(Event, User, Config) -> + Key = user_key(Config), + Value = {longstr, User}, + receive_event(Event, Key, Value). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) receive_event(Event, Key, Value) -> receive {#'basic.deliver'{routing_key = RoutingKey}, #amqp_msg{props = #'P_basic'{headers = Headers}}} -> +<<<<<<< HEAD Event = RoutingKey, Value = rabbit_misc:table_lookup(Headers, Key) after 60000 -> +======= + ?assertEqual(Event, RoutingKey), + ?assertEqual(Value, rabbit_misc:table_lookup(Headers, Key)), + Headers + after + 10_000 -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) throw({receive_event_timeout, Event, Key, Value}) end. @@ -489,8 +975,31 @@ receive_event(Event) -> receive {#'basic.deliver'{routing_key = RoutingKey}, #amqp_msg{props = #'P_basic'{}}} -> +<<<<<<< HEAD Event = RoutingKey after 60000 -> throw({receive_event_timeout, Event}) end. +======= + ?assertEqual(Event, RoutingKey) + after + 10_000 -> + throw({receive_event_timeout, Event}) + end. + +amqp_init(Config) -> + OpnConf = amqp_connection_config(Config), + {ok, Connection} = amqp10_client:open_connection(OpnConf), + {ok, Session} = amqp10_client:begin_session_sync(Connection), + {ok, LinkPair} = rabbitmq_amqp_client:attach_management_link_pair_sync(Session, <<"my link pair">>), + {Connection, Session, LinkPair}. + +amqp_connection_config(Config) -> + Host = proplists:get_value(rmq_hostname, Config), + Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp), + #{address => Host, + port => Port, + container_id => <<"my container">>, + sasl => {plain, <<"guest">>, <<"guest">>}}. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbitmq_management/.gitignore b/deps/rabbitmq_management/.gitignore index 96463fa9b670..7b2ecf172df2 100644 --- a/deps/rabbitmq_management/.gitignore +++ b/deps/rabbitmq_management/.gitignore @@ -2,6 +2,7 @@ test/config_schema_SUITE_data/schema/ +<<<<<<< HEAD selenium/node_modules selenium/package-lock.json selenium/screens/*/* @@ -11,3 +12,7 @@ selenium/suites/screens/* selenium/test/oauth/*/h2/*.trace.db selenium/test/oauth/*/h2/*.lock.db selenium/*/target/* +======= +test/js/node_modules +test/js/package-lock.json +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbitmq_management/BUILD.bazel b/deps/rabbitmq_management/BUILD.bazel index 2f053bb609a7..e48354ee3c47 100644 --- a/deps/rabbitmq_management/BUILD.bazel +++ b/deps/rabbitmq_management/BUILD.bazel @@ -89,6 +89,10 @@ rabbitmq_app( "//deps/rabbitmq_web_dispatch:erlang_app", "@cowboy//:erlang_app", "@cowlib//:erlang_app", +<<<<<<< HEAD +======= + "@cuttlefish//:erlang_app", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "@ranch//:erlang_app", ], ) @@ -130,6 +134,14 @@ rabbitmq_suite( ], ) +<<<<<<< HEAD +======= +rabbitmq_suite( + name = "rabbit_mgmt_schema_SUITE", + size = "small", +) + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbitmq_integration_suite( name = "clustering_prop_SUITE", size = "large", @@ -167,7 +179,11 @@ rabbitmq_integration_suite( additional_beam = [ "test/rabbit_mgmt_runtime_parameters_util.beam", ], +<<<<<<< HEAD shard_count = 7, +======= + shard_count = 6, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) runtime_deps = [ "//deps/amqp10_client:erlang_app", ], diff --git a/deps/rabbitmq_management/Makefile b/deps/rabbitmq_management/Makefile index 98998bfcdb48..d80f3360a9a0 100644 --- a/deps/rabbitmq_management/Makefile +++ b/deps/rabbitmq_management/Makefile @@ -22,7 +22,11 @@ define PROJECT_APP_EXTRA_KEYS endef DEPS = rabbit_common rabbit amqp_client cowboy cowlib rabbitmq_web_dispatch rabbitmq_management_agent oauth2_client +<<<<<<< HEAD TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers proper amqp10_client +======= +TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers proper rabbitmq_amqp_client +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) LOCAL_DEPS += ranch ssl crypto public_key # FIXME: Add Ranch as a BUILD_DEPS to be sure the correct version is picked. diff --git a/deps/rabbitmq_management/app.bzl b/deps/rabbitmq_management/app.bzl index 5d6adba15b2c..b400196a3da8 100644 --- a/deps/rabbitmq_management/app.bzl +++ b/deps/rabbitmq_management/app.bzl @@ -32,6 +32,10 @@ def all_beam_files(name = "all_beam_files"): "src/rabbit_mgmt_nodes.erl", "src/rabbit_mgmt_oauth_bootstrap.erl", "src/rabbit_mgmt_reset_handler.erl", +<<<<<<< HEAD +======= + "src/rabbit_mgmt_schema.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_mgmt_stats.erl", "src/rabbit_mgmt_sup.erl", "src/rabbit_mgmt_sup_sup.erl", @@ -47,6 +51,10 @@ def all_beam_files(name = "all_beam_files"): "src/rabbit_mgmt_wm_cluster_name.erl", "src/rabbit_mgmt_wm_connection.erl", "src/rabbit_mgmt_wm_connection_channels.erl", +<<<<<<< HEAD +======= + "src/rabbit_mgmt_wm_connection_sessions.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_mgmt_wm_connection_user_name.erl", "src/rabbit_mgmt_wm_connections.erl", "src/rabbit_mgmt_wm_connections_vhost.erl", @@ -166,6 +174,10 @@ def all_test_beam_files(name = "all_test_beam_files"): "src/rabbit_mgmt_nodes.erl", "src/rabbit_mgmt_oauth_bootstrap.erl", "src/rabbit_mgmt_reset_handler.erl", +<<<<<<< HEAD +======= + "src/rabbit_mgmt_schema.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_mgmt_stats.erl", "src/rabbit_mgmt_sup.erl", "src/rabbit_mgmt_sup_sup.erl", @@ -181,6 +193,10 @@ def all_test_beam_files(name = "all_test_beam_files"): "src/rabbit_mgmt_wm_cluster_name.erl", "src/rabbit_mgmt_wm_connection.erl", "src/rabbit_mgmt_wm_connection_channels.erl", +<<<<<<< HEAD +======= + "src/rabbit_mgmt_wm_connection_sessions.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_mgmt_wm_connection_user_name.erl", "src/rabbit_mgmt_wm_connections.erl", "src/rabbit_mgmt_wm_connections_vhost.erl", @@ -361,6 +377,10 @@ def all_srcs(name = "all_srcs"): "priv/www/js/tmpl/queues.ejs", "priv/www/js/tmpl/rate-options.ejs", "priv/www/js/tmpl/registry.ejs", +<<<<<<< HEAD +======= + "priv/www/js/tmpl/sessions-list.ejs", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "priv/www/js/tmpl/status.ejs", "priv/www/js/tmpl/topic-permissions.ejs", "priv/www/js/tmpl/user.ejs", @@ -391,6 +411,10 @@ def all_srcs(name = "all_srcs"): "src/rabbit_mgmt_nodes.erl", "src/rabbit_mgmt_oauth_bootstrap.erl", "src/rabbit_mgmt_reset_handler.erl", +<<<<<<< HEAD +======= + "src/rabbit_mgmt_schema.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_mgmt_stats.erl", "src/rabbit_mgmt_sup.erl", "src/rabbit_mgmt_sup_sup.erl", @@ -406,6 +430,10 @@ def all_srcs(name = "all_srcs"): "src/rabbit_mgmt_wm_cluster_name.erl", "src/rabbit_mgmt_wm_connection.erl", "src/rabbit_mgmt_wm_connection_channels.erl", +<<<<<<< HEAD +======= + "src/rabbit_mgmt_wm_connection_sessions.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_mgmt_wm_connection_user_name.erl", "src/rabbit_mgmt_wm_connections.erl", "src/rabbit_mgmt_wm_connections_vhost.erl", @@ -499,6 +527,17 @@ def all_srcs(name = "all_srcs"): def test_suite_beam_files(name = "test_suite_beam_files"): erlang_bytecode( +<<<<<<< HEAD +======= + name = "rabbit_mgmt_schema_SUITE_beam_files", + testonly = True, + srcs = ["test/rabbit_mgmt_schema_SUITE.erl"], + outs = ["test/rabbit_mgmt_schema_SUITE.beam"], + app_name = "rabbitmq_management", + erlc_opts = "//:test_erlc_opts", + ) + erlang_bytecode( +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) name = "cache_SUITE_beam_files", testonly = True, srcs = ["test/cache_SUITE.erl"], diff --git a/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema b/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema index 83c32b3022ac..8fe6ee8a9640 100644 --- a/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema +++ b/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema @@ -472,23 +472,58 @@ end}. {mapping, "management.oauth_response_type", "rabbitmq_management.oauth_response_type", [{datatype, string}]}. +<<<<<<< HEAD %% The scopes RabbitMq should claim during the authorization flow. Defaults to "openid profile" {mapping, "management.oauth_scopes", "rabbitmq_management.oauth_scopes", [{datatype, string}]}. +======= +%% THIS VARIABLE IS DEPRECATED. CHECKOUT auth_oauth2.discovery_endpoint_path VARIABLE. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% The URL of the OIDC discovery url where the provider is listening on %% by default it is /.well-known/openid-configuration which is the %% default OIDC discovery endpoint {mapping, "management.oauth_metadata_url", "rabbitmq_management.oauth_metadata_url", [{datatype, string}]}. +<<<<<<< HEAD +======= +%% Configure OAuth2 authorization_endpoint additional request parameters +{mapping, "management.oauth_authorization_endpoint_params.$name", + "rabbitmq_management.oauth_authorization_endpoint_params", + [{datatype, string}]}. + +{translation, "rabbitmq_management.oauth_authorization_endpoint_params", + fun(Conf) -> + rabbit_mgmt_schema:translate_endpoint_params("oauth_authorization_endpoint_params", Conf) + end}. + +%% Configure OAuth2 token_endpoint additional request parameters +{mapping, "management.oauth_token_endpoint_params.$name", + "rabbitmq_management.oauth_token_endpoint_params", + [{datatype, string}]}. + +{translation, "rabbitmq_management.oauth_token_endpoint_params", + fun(Conf) -> + rabbit_mgmt_schema:translate_endpoint_params("oauth_token_endpoint_params", Conf) + end}. + +%% The scopes RabbitMq should claim during the authorization flow. Defaults to "openid profile" +{mapping, "management.oauth_scopes", "rabbitmq_management.oauth_scopes", + [{datatype, string}]}. + + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% Configure the OAuth 2 type allowed for the end users to logon to the management UI %% Default type is sp_initiated meaning the standard OAuth 2.0 mode where users come without any token %% Other type is idp_initiated meaning users must come with a token {mapping, "management.oauth_initiated_logon_type", "rabbitmq_management.oauth_initiated_logon_type", [{datatype, {enum, [sp_initiated, idp_initiated]}}]}. +<<<<<<< HEAD +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {mapping, "management.oauth_resource_servers.$name.id", "rabbitmq_management.oauth_resource_servers", @@ -514,8 +549,11 @@ end}. [{datatype, string}] }. +<<<<<<< HEAD +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {mapping, "management.oauth_resource_servers.$name.oauth_client_id", "rabbitmq_management.oauth_resource_servers", @@ -534,7 +572,10 @@ end}. [{datatype, string}] }. +<<<<<<< HEAD +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {mapping, "management.oauth_resource_servers.$name.oauth_scopes", "rabbitmq_management.oauth_resource_servers", @@ -552,6 +593,7 @@ end}. "rabbitmq_management.oauth_resource_servers", [{datatype, {enum, [sp_initiated, idp_initiated]}}]}. +<<<<<<< HEAD {translation, "rabbitmq_management.oauth_resource_servers", fun(Conf) -> Settings = cuttlefish_variable:filter_by_prefix("management.oauth_resource_servers", Conf), @@ -582,6 +624,19 @@ end}. end end, maps:fold(IndexByIdOrElseNameFun,#{}, NewGroupTwo) +======= +{mapping, "management.oauth_resource_servers.$name.oauth_authorization_endpoint_params.$name", + "rabbitmq_management.oauth_resource_servers", + [{datatype, string}]}. + +{mapping, "management.oauth_resource_servers.$name.oauth_token_endpoint_params.$name", + "rabbitmq_management.oauth_resource_servers", + [{datatype, string}]}. + +{translation, "rabbitmq_management.oauth_resource_servers", + fun(Conf) -> + rabbit_mgmt_schema:translate_oauth_resource_servers(Conf) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end}. %% =========================================================================== diff --git a/deps/rabbitmq_management/priv/www/css/main.css b/deps/rabbitmq_management/priv/www/css/main.css index a3bcaae5d5f5..c75a27abb206 100644 --- a/deps/rabbitmq_management/priv/www/css/main.css +++ b/deps/rabbitmq_management/priv/www/css/main.css @@ -232,7 +232,11 @@ div.form-popup-help { width: 500px; z-index: 2; } +<<<<<<< HEAD p.warning, div.form-popup-warn { background: #FF9; } +======= +div.warning, p.warning, div.form-popup-warn { background: #FF9; } +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) div.form-popup-options { z-index: 3; overflow:auto; max-height:95%; } @@ -255,7 +259,18 @@ div.form-popup-options span:hover { cursor: pointer; } +<<<<<<< HEAD p.warning { padding: 15px; border-radius: 5px; -moz-border-radius: 5px; text-align: center; } +======= +div.warning, p.warning { padding: 15px; border-radius: 5px; -moz-border-radius: 5px; text-align: center; } +div.warning { + margin: 15px 0; +} + +div.warning button { + margin: auto; +} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) .highlight { min-width: 120px; font-size: 120%; text-align:center; padding:10px; background-color: #ddd; margin: 0 20px 0 0; color: #888; border-radius: 5px; -moz-border-radius: 5px; } .highlight strong { font-size: 2em; display: block; color: #444; font-weight: normal; } @@ -367,3 +382,52 @@ div.bindings-wrapper p.arrow { font-size: 200%; } } table.dynamic-shovels td label {width: 200px; margin-right:10px;padding: 4px 0px 5px 0px} +<<<<<<< HEAD +======= + +input[type=checkbox].toggle { + display: none; +} + +label.toggle { + cursor: pointer; + text-indent: -9999px; + width: 32px; + height: 16px; + background: #ff5630; + display: block; + border-radius: 16px; + position: relative; + margin: auto; +} + +label.toggle:after { + content: ''; + position: absolute; + top: 2px; + left: 2px; + width: 12px; + height: 12px; + background: #fff; + border-radius: 12px; + transition: 0.3s; +} + +input.toggle:indeterminate + label.toggle { + background: #ffab00; +} + +input.toggle:checked + label.toggle { + background: #36b37e; +} + +input.toggle:indeterminate + label.toggle:after { + left: calc(50%); + transform: translateX(-50%); +} + +input.toggle:checked + label.toggle:after { + left: calc(100% - 2px); + transform: translateX(-100%); +} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbitmq_management/priv/www/js/dispatcher.js b/deps/rabbitmq_management/priv/www/js/dispatcher.js index e0e520715fe4..c9ffd67cdb5b 100644 --- a/deps/rabbitmq_management/priv/www/js/dispatcher.js +++ b/deps/rabbitmq_management/priv/www/js/dispatcher.js @@ -46,10 +46,28 @@ dispatcher_add(function(sammy) { }); sammy.get('#/connections/:name', function() { var name = esc(this.params['name']); +<<<<<<< HEAD render({'connection': {path: '/connections/' + name, options: {ranges: ['data-rates-conn']}}, 'channels': '/connections/' + name + '/channels'}, 'connection', '#/connections'); +======= + var connectionPath = '/connections/' + name; + var reqs = { + 'connection': { + path: connectionPath, + options: { ranges: ['data-rates-conn'] } + } + }; + // First, get the connection details to check the protocol + var connectionDetails = JSON.parse(sync_get(connectionPath)); + if (connectionDetails.protocol === 'AMQP 1-0') { + reqs['sessions'] = connectionPath + '/sessions'; + } else { + reqs['channels'] = connectionPath + '/channels'; + } + render(reqs, 'connection', '#/connections'); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }); sammy.del('#/connections', function() { var options = {headers: { diff --git a/deps/rabbitmq_management/priv/www/js/global.js b/deps/rabbitmq_management/priv/www/js/global.js index a3ad397bc061..ff63ad7f776c 100644 --- a/deps/rabbitmq_management/priv/www/js/global.js +++ b/deps/rabbitmq_management/priv/www/js/global.js @@ -108,7 +108,12 @@ var ALL_COLUMNS = ['rate-redeliver', 'redelivered', false], ['rate-ack', 'ack', true]]}, 'connections': +<<<<<<< HEAD {'Overview': [['user', 'User name', true], +======= + {'Overview': [['container_id', 'Container ID', true], + ['user', 'User name', true], +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ['state', 'State', true]], 'Details': [['ssl', 'TLS', true], ['ssl_info', 'TLS details', false], @@ -582,8 +587,42 @@ var HELP = {
Rate at which queues are created. Declaring a queue that already exists counts for a "Declared" event, but not for a "Created" event.
\
Deleted
\
Rate at which queues are deleted.
\ +<<<<<<< HEAD ' +======= + ', + + 'container-id': + 'Name of the client application as sent from client to RabbitMQ in the "container-id" field of the AMQP 1.0 open frame.', + + 'incoming-links': + 'Links where the client is the sender/publisher and RabbitMQ is the receiver of messages.', + + 'outgoing-links': + 'Links where the client is the receiver/consumer and RabbitMQ is the sender of messages.', + + 'target-address': + 'The "address" field of the link target.', + + 'source-address': + 'The "address" field of the link source.', + + 'amqp-source-queue': + 'The client receives messages from this queue.', + + 'amqp-unconfirmed-messages': + 'Number of messages that have been sent to queues but have not been confirmed by all queues.', + + 'snd-settle-mode': + 'Sender Settle Mode', + + 'sender-settles': + '"true" if the sender sends all deliveries settled to the receiver. "false" if the sender sends all deliveries initially unsettled to the receiver.', + + 'outgoing-unsettled-deliveries': + 'Number of messages that have been sent to consumers but have not yet been settled/acknowledged.' +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }; /////////////////////////////////////////////////////////////////////////// diff --git a/deps/rabbitmq_management/priv/www/js/main.js b/deps/rabbitmq_management/priv/www/js/main.js index 22c9f47bc145..36ed4980b3ac 100644 --- a/deps/rabbitmq_management/priv/www/js/main.js +++ b/deps/rabbitmq_management/priv/www/js/main.js @@ -301,6 +301,26 @@ function reset_timer() { } } +<<<<<<< HEAD +======= +function pause_auto_refresh() { + if (typeof globalThis.rmq_webui_auto_refresh_paused == 'undefined') + globalThis.rmq_webui_auto_refresh_paused = 0; + + globalThis.rmq_webui_auto_refresh_paused++; + if (timer != null) { + clearInterval(timer); + } +} + +function resume_auto_refresh() { + globalThis.rmq_webui_auto_refresh_paused--; + if (globalThis.rmq_webui_auto_refresh_paused == 0) { + reset_timer(); + } +} + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) function update_manual(div, query) { var path; var template; diff --git a/deps/rabbitmq_management/priv/www/js/oidc-oauth/helper.js b/deps/rabbitmq_management/priv/www/js/oidc-oauth/helper.js index ef5e20f44812..b7709d22a6ee 100644 --- a/deps/rabbitmq_management/priv/www/js/oidc-oauth/helper.js +++ b/deps/rabbitmq_management/priv/www/js/oidc-oauth/helper.js @@ -46,6 +46,7 @@ function auth_settings_apply_defaults(authSettings) { } if (!resource_server.oauth_response_type) { resource_server.oauth_response_type = authSettings.oauth_response_type +<<<<<<< HEAD if (!resource_server.oauth_response_type) { resource_server.oauth_response_type = "code" } @@ -55,6 +56,11 @@ function auth_settings_apply_defaults(authSettings) { if (!resource_server.oauth_scopes) { resource_server.oauth_scopes = "openid profile" } +======= + } + if (!resource_server.oauth_scopes) { + resource_server.oauth_scopes = authSettings.oauth_scopes +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) } if (!resource_server.oauth_client_id) { resource_server.oauth_client_id = authSettings.oauth_client_id @@ -78,6 +84,17 @@ function auth_settings_apply_defaults(authSettings) { if (!resource_server.oauth_metadata_url) { resource_server.oauth_metadata_url = authSettings.metadata_url } +<<<<<<< HEAD +======= + if (!resource_server.oauth_authorization_endpoint_params) { + resource_server.oauth_authorization_endpoint_params = + authSettings.oauth_authorization_endpoint_params + } + if (!resource_server.oauth_token_endpoint_params) { + resource_server.oauth_token_endpoint_params = + authSettings.oauth_token_endpoint_params + } +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) resource_server.id = resource_server_id authSettings.resource_servers.push(resource_server) } @@ -98,6 +115,7 @@ function get_oauth_settings() { export function oauth_initialize_if_required(state = "index") { let oauth = oauth_initialize(get_oauth_settings()) if (!oauth.enabled) return oauth; +<<<<<<< HEAD switch (state) { case 'login-callback': oauth_completeLogin(); break; @@ -107,12 +125,27 @@ export function oauth_initialize_if_required(state = "index") { oauth = oauth_initiate(oauth); } return oauth; +======= + switch (state) { + case 'login-callback': + oauth_completeLogin(); break; + case 'logout-callback': + oauth_completeLogout(); break; + default: + oauth = oauth_initiate(oauth); + } + return oauth; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) } export function oauth_initiate(oauth) { if (oauth.enabled) { if (!oauth.sp_initiated) { +<<<<<<< HEAD oauth.logged_in = has_auth_credentials(); +======= + oauth.logged_in = has_auth_credentials(); +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) } else { oauth_is_logged_in().then( status => { if (status.loggedIn && !has_auth_credentials()) { @@ -122,7 +155,11 @@ export function oauth_initiate(oauth) { if (!status.loggedIn) { clear_auth(); } else { +<<<<<<< HEAD oauth.logged_in = true; +======= + oauth.logged_in = true; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) oauth.expiryDate = new Date(status.user.expires_at * 1000); // it is epoch in seconds let current = new Date(); _management_logger.debug('token expires in ', (oauth.expiryDate-current)/1000, @@ -139,6 +176,7 @@ export function oauth_initiate(oauth) { } return oauth; } +<<<<<<< HEAD function oauth_initialize_user_manager(resource_server) { let oidcSettings = { userStore: new oidc.WebStorageStateStore({ store: window.localStorage }), @@ -173,6 +211,43 @@ function oauth_initialize_user_manager(resource_server) { mgr = new oidc.UserManager(oidcSettings); // oauth.readiness_url = mgr.settings.metadataUrl; +======= +export function oidc_settings_from(resource_server) { + let oidcSettings = { + userStore: new oidc.WebStorageStateStore({ store: window.localStorage }), + authority: resource_server.oauth_provider_url, + metadataUrl: resource_server.oauth_metadata_url, + client_id: resource_server.oauth_client_id, + response_type: resource_server.oauth_response_type, + scope: resource_server.oauth_scopes, + redirect_uri: rabbit_base_uri() + "/js/oidc-oauth/login-callback.html", + post_logout_redirect_uri: rabbit_base_uri() + "/", + automaticSilentRenew: true, + revokeAccessTokenOnSignout: true + } + if (resource_server.end_session_endpoint != "") { + oidcSettings.metadataSeed = { + end_session_endpoint: resource_server.end_session_endpoint + } + } + if (resource_server.oauth_client_secret != "") { + oidcSettings.client_secret = resource_server.oauth_client_secret + } + if (resource_server.oauth_authorization_endpoint_params) { + oidcSettings.extraQueryParams = resource_server.oauth_authorization_endpoint_params + } + if (resource_server.oauth_token_endpoint_params) { + oidcSettings.extraTokenParams = resource_server.oauth_token_endpoint_params + } + return oidcSettings +} + +function oauth_initialize_user_manager(resource_server) { + oidc.Log.setLevel(oidc.Log.DEBUG); + oidc.Log.setLogger(console); + + mgr = new oidc.UserManager(oidc_settings_from(resource_server)) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) _management_logger = new oidc.Logger("Management"); @@ -218,6 +293,7 @@ export function oauth_initialize(authSettings) { return oauth; } +<<<<<<< HEAD function log() { message = "" Array.prototype.forEach.call(arguments, function(msg) { @@ -232,6 +308,8 @@ function log() { _management_logger.info(message) } +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) function oauth_is_logged_in() { return mgr.getUser().then(user => { if (!user) { diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/connection.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/connection.ejs index f834b02fb5e0..0645054891ea 100644 --- a/deps/rabbitmq_management/priv/www/js/tmpl/connection.ejs +++ b/deps/rabbitmq_management/priv/www/js/tmpl/connection.ejs @@ -1,7 +1,11 @@

Connection <%= fmt_string(connection.name) %> <%= fmt_maybe_vhost(connection.vhost) %>

<% if (!disable_stats) { %> +<<<<<<< HEAD
+======= +
+>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x)

Overview

<%= data_rates('data-rates-conn', connection, 'Data rates') %> @@ -17,11 +21,27 @@ <% if (connection.client_properties.connection_name) { %> +<<<<<<< HEAD Client-provided name +======= + Client-provided connection name +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) <%= fmt_string(connection.client_properties.connection_name) %> <% } %> +<<<<<<< HEAD +======= +<% if (connection.container_id) { %> + + Container ID + + + <%= fmt_string(connection.container_id) %> + +<% } %> + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Username <%= fmt_string(connection.user) %> @@ -75,13 +95,33 @@
+<<<<<<< HEAD
+======= +<% if (connection.protocol === 'AMQP 1-0') { %> + +
+

Sessions (<%=(sessions.length)%>)

+
+ <%= format('sessions-list', {'sessions': sessions}) %> +
+
+ +<% } else { %> + +
+>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x)

Channels (<%=(channels.length)%>)

<%= format('channels-list', {'channels': channels, 'mode': 'connection'}) %>
+<<<<<<< HEAD +======= +<% } %> + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) <% if (connection.ssl) { %>

SSL

@@ -127,7 +167,11 @@ <% } %> <% if (properties_size(connection.client_properties) > 0) { %> +<<<<<<< HEAD
+======= +
+>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x)

Client properties

<%= fmt_table_long(connection.client_properties) %> @@ -136,7 +180,11 @@ <% } %> <% if(connection.reductions || connection.garbage_collection) { %> +<<<<<<< HEAD
+======= +
+>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x)

Runtime Metrics (Advanced)

<%= data_reductions('reductions-rates-conn', connection) %> @@ -175,7 +223,11 @@ <% } %> <% } %> +<<<<<<< HEAD
+======= +
+>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x)

Close this connection

diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/connections.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/connections.ejs index 464894d20876..6fdd7b12f5eb 100644 --- a/deps/rabbitmq_management/priv/www/js/tmpl/connections.ejs +++ b/deps/rabbitmq_management/priv/www/js/tmpl/connections.ejs @@ -1,8 +1,15 @@

Connections

+<<<<<<< HEAD
<%= paginate_ui(connections, 'connections') %>
+======= +
+ <%= paginate_ui(connections, 'connections') %> +
+
+>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) <% if (connections.items.length > 0) { %> @@ -26,6 +33,12 @@ <% if (nodes_interesting) { %> <% } %> +<<<<<<< HEAD +======= +<% if (show_column('connections', 'container_id')) { %> + +<% } %> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) <% if (show_column('connections', 'user')) { %> <% } %> @@ -84,7 +97,13 @@ <% if(connection.client_properties) { %> <% } else { %> @@ -92,6 +111,16 @@ <% if (nodes_interesting) { %> <% } %> +<<<<<<< HEAD +======= +<% if (show_column('connections', 'container_id')) { %> + +<% } %> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) <% if (show_column('connections', 'user')) { %> <% } %> diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/deprecated-features.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/deprecated-features.ejs index 384ffefb3c9c..c2b452bb73e9 100644 --- a/deps/rabbitmq_management/priv/www/js/tmpl/deprecated-features.ejs +++ b/deps/rabbitmq_management/priv/www/js/tmpl/deprecated-features.ejs @@ -56,6 +56,12 @@ <% } else { %>

... no deprecated features ...

<% } %> +<<<<<<< HEAD +======= +

+ See the Deprecated features documentation for more information. +

+>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/feature-flags.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/feature-flags.ejs index a2ed48ad4573..c80872b5fd8d 100644 --- a/deps/rabbitmq_management/priv/www/js/tmpl/feature-flags.ejs +++ b/deps/rabbitmq_management/priv/www/js/tmpl/feature-flags.ejs @@ -1,3 +1,4 @@ +<<<<<<< HEAD

Feature Flags

<% var needs_enabling = false; @@ -18,16 +19,290 @@ <%= filter_ui(feature_flags) %>
<% if (feature_flags.length > 0) { %> +======= + +

Feature Flags

+ <% + var nonreq_feature_flags = []; + for (var i = 0; i < feature_flags.length; i++) { + if (feature_flags[i].stability == 'required') + continue; + nonreq_feature_flags.push(feature_flags[i]); + } + %> + +
+

Feature Flags

+
+<%= filter_ui(nonreq_feature_flags) %> +
+<% if (nonreq_feature_flags.length > 0) { %> + + + + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x)
<%= fmt_sort('Node', 'node') %>Container ID <%= fmt_sort('User name', 'user') %> <%= link_conn(connection.name) %> +<<<<<<< HEAD <%= fmt_string(short_conn(connection.client_properties.connection_name)) %> +======= + <% if (connection.client_properties.connection_name) { %> + <%= fmt_string(short_conn(connection.client_properties.connection_name)) %> + <% } %> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) <%= link_conn(connection.name) %><%= fmt_node(connection.node) %> + <% if (connection.container_id) { %> + <%= fmt_string(connection.container_id) %> + <% } %> + <%= fmt_string(connection.user) %>
+<<<<<<< HEAD +======= + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) <% +<<<<<<< HEAD for (var i = 0; i < feature_flags.length; i++) { var feature_flag = feature_flags[i]; if (feature_flag.stability == "experimental") { @@ -135,6 +410,43 @@ These flags can be enabled in production deployments after an appropriate amount <%= fmt_string(feature_flag.state) %> <% } %> +======= + for (var i = 0; i < nonreq_feature_flags.length; i++) { + var feature_flag = nonreq_feature_flags[i]; + %> + > + + + +<<<<<<< HEAD +======= + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) <% } %> diff --git a/deps/rabbitmq_stream_management/test/http_SUITE_data/pom.xml b/deps/rabbitmq_stream_management/test/http_SUITE_data/pom.xml index c8103f607e72..09cb7ded5641 100644 --- a/deps/rabbitmq_stream_management/test/http_SUITE_data/pom.xml +++ b/deps/rabbitmq_stream_management/test/http_SUITE_data/pom.xml @@ -27,11 +27,19 @@ [0.12.0-SNAPSHOT,) +<<<<<<< HEAD 5.10.3 3.26.3 1.2.13 3.12.1 3.3.0 +======= + 5.11.3 + 3.26.3 + 1.2.13 + 3.12.1 + 3.5.2 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) 2.43.0 1.18.1 4.12.0 diff --git a/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_handler.erl b/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_handler.erl index 67e99400b500..95a2f7b4ac31 100644 --- a/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_handler.erl +++ b/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_handler.erl @@ -42,7 +42,11 @@ stats_timer :: option(rabbit_event:state()), keepalive = rabbit_mqtt_keepalive:init() :: rabbit_mqtt_keepalive:state(), conn_name :: option(binary()) +<<<<<<< HEAD }). +======= + }). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -type state() :: #state{}. @@ -79,6 +83,7 @@ init(Req, Opts) -> false -> no_supported_sub_protocol(Protocol, Req); true -> +<<<<<<< HEAD WsOpts0 = proplists:get_value(ws_opts, Opts, #{}), WsOpts = maps:merge(#{compress => true}, WsOpts0), @@ -86,6 +91,14 @@ init(Req, Opts) -> cowboy_req:set_resp_header(<<"sec-websocket-protocol">>, <<"mqtt">>, Req), #state{socket = maps:get(proxy_header, Req, undefined)}, WsOpts} +======= + Req1 = cowboy_req:set_resp_header(<<"sec-websocket-protocol">>, <<"mqtt">>, Req), + State = #state{socket = maps:get(proxy_header, Req, undefined), + stats_timer = rabbit_event:init_stats_timer()}, + WsOpts0 = proplists:get_value(ws_opts, Opts, #{}), + WsOpts = maps:merge(#{compress => true}, WsOpts0), + {?MODULE, Req1, State, WsOpts} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end end. @@ -112,8 +125,12 @@ websocket_init(State0 = #state{socket = Sock}) -> ConnName = rabbit_data_coercion:to_binary(ConnStr), ?LOG_INFO("Accepting Web MQTT connection ~s", [ConnName]), _ = rabbit_alarm:register(self(), {?MODULE, conserve_resources, []}), +<<<<<<< HEAD State1 = State0#state{conn_name = ConnName}, State = rabbit_event:init_stats_timer(State1, #state.stats_timer), +======= + State = State0#state{conn_name = ConnName}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) process_flag(trap_exit, true), {[], State, hibernate}; {error, Reason} -> diff --git a/moduleindex.yaml b/moduleindex.yaml index c0809c1f9156..02d11190a52c 100755 --- a/moduleindex.yaml +++ b/moduleindex.yaml @@ -543,6 +543,10 @@ rabbit: - rabbit_access_control - rabbit_alarm - rabbit_amqp1_0 +<<<<<<< HEAD +======= +- rabbit_amqp_filtex +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - rabbit_amqp_management - rabbit_amqp_reader - rabbit_amqp_session @@ -670,6 +674,10 @@ rabbit: - rabbit_metrics - rabbit_mirror_queue_misc - rabbit_mnesia +<<<<<<< HEAD +======= +- rabbit_msg_size_metrics +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - rabbit_msg_store - rabbit_msg_store_gc - rabbit_networking @@ -835,7 +843,14 @@ rabbitmq_auth_backend_oauth2: - Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand - rabbit_auth_backend_oauth2 - rabbit_auth_backend_oauth2_app +<<<<<<< HEAD - rabbit_oauth2_config +======= +- rabbit_oauth2_keycloak +- rabbit_oauth2_provider +- rabbit_oauth2_rar +- rabbit_oauth2_resource_server +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - rabbit_oauth2_schema - rabbit_oauth2_scope - uaa_jwks @@ -928,6 +943,10 @@ rabbitmq_management: - rabbit_mgmt_nodes - rabbit_mgmt_oauth_bootstrap - rabbit_mgmt_reset_handler +<<<<<<< HEAD +======= +- rabbit_mgmt_schema +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - rabbit_mgmt_stats - rabbit_mgmt_sup - rabbit_mgmt_sup_sup @@ -943,6 +962,10 @@ rabbitmq_management: - rabbit_mgmt_wm_cluster_name - rabbit_mgmt_wm_connection - rabbit_mgmt_wm_connection_channels +<<<<<<< HEAD +======= +- rabbit_mgmt_wm_connection_sessions +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - rabbit_mgmt_wm_connection_user_name - rabbit_mgmt_wm_connections - rabbit_mgmt_wm_connections_vhost @@ -1103,6 +1126,10 @@ rabbitmq_prometheus: - prometheus_rabbitmq_core_metrics_collector - prometheus_rabbitmq_dynamic_collector - prometheus_rabbitmq_global_metrics_collector +<<<<<<< HEAD +======= +- prometheus_rabbitmq_message_size_metrics_collector +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) - rabbit_prometheus_app - rabbit_prometheus_dispatcher - rabbit_prometheus_handler diff --git a/rabbitmq-components.mk b/rabbitmq-components.mk index 8dfdaed0664a..4277fe0449ba 100644 --- a/rabbitmq-components.mk +++ b/rabbitmq-components.mk @@ -53,7 +53,11 @@ dep_prometheus = hex 4.11.0 dep_ra = hex 2.14.0 dep_ranch = hex 2.1.0 dep_recon = hex 2.5.6 +<<<<<<< HEAD dep_redbug = hex 2.1.0 +======= +dep_redbug = hex 2.0.7 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) dep_systemd = hex 0.6.1 dep_thoas = hex 1.2.1 dep_observer_cli = hex 1.7.5 diff --git a/rabbitmq.bzl b/rabbitmq.bzl index d0a5b52405fc..5732b4009a5b 100644 --- a/rabbitmq.bzl +++ b/rabbitmq.bzl @@ -290,12 +290,20 @@ def rabbitmq_integration_suite( "RABBITMQCTL": "$TEST_SRCDIR/$TEST_WORKSPACE/{}/broker-for-tests-home/sbin/rabbitmqctl".format(package), "RABBITMQ_PLUGINS": "$TEST_SRCDIR/$TEST_WORKSPACE/{}/broker-for-tests-home/sbin/rabbitmq-plugins".format(package), "RABBITMQ_QUEUES": "$TEST_SRCDIR/$TEST_WORKSPACE/{}/broker-for-tests-home/sbin/rabbitmq-queues".format(package), +<<<<<<< HEAD "RABBITMQ_RUN_SECONDARY": "$(location @rabbitmq-server-generic-unix-3.13//:rabbitmq-run)", +======= + "RABBITMQ_RUN_SECONDARY": "$(location @rabbitmq-server-generic-unix-4.0//:rabbitmq-run)", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "LANG": "C.UTF-8", }.items() + test_env.items()), tools = [ ":rabbitmq-for-tests-run", +<<<<<<< HEAD "@rabbitmq-server-generic-unix-3.13//:rabbitmq-run", +======= + "@rabbitmq-server-generic-unix-4.0//:rabbitmq-run", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ] + tools, deps = assumed_deps + deps + runtime_deps, ct_run_extra_args = ["-kernel net_ticktime 5"], diff --git a/release-notes/4.0.1.md b/release-notes/4.0.1.md index 50a0d3c84cce..db72588bf5e7 100644 --- a/release-notes/4.0.1.md +++ b/release-notes/4.0.1.md @@ -356,6 +356,15 @@ This section is incomplete and will be expanded as 4.0 approaches its release ca GitHub issue: [#11743](https://github.com/rabbitmq/rabbitmq-server/issues/11743) +<<<<<<< HEAD +======= + * Several new metrics for streams, for example, `stream_consumer_max_offset_lag`. + + Contributed by @markus812498, @gomoripeti. + + GitHub issue: [#10275](https://github.com/rabbitmq/rabbitmq-server/pull/10275) + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) * New per-exchange and per-queue metrics. Contributed by @LoisSotoLopez. diff --git a/release-notes/4.1.0.md b/release-notes/4.1.0.md new file mode 100644 index 000000000000..75405f9d560b --- /dev/null +++ b/release-notes/4.1.0.md @@ -0,0 +1,371 @@ +## RabbitMQ 4.1.0-beta.4 + +RabbitMQ 4.1.0-beta.4 is a preview release (in development) of a new feature release. + +See Compatibility Notes below to learn about **breaking or potentially breaking changes** in this release. + + +## Highlights + +Some key improvements in this release are listed below. + +### Initial Support for AMQP 1.0 Filter Expressions + +Support for the `properties` and `appliation-properties` filters of [AMQP Filter Expressions Version 1.0 Working Draft 09](https://groups.oasis-open.org/higherlogic/ws/public/document?document_id=66227). + + +### Feature Flags Quality of Life Improvements + +Graduated (mandatory) [feature flags](https://www.rabbitmq.com/docs/feature-flags) several minors ago has proven that they could use some user experience improvements. +For example, certain required feature flags will now be enabled on node boot when all nodes in the cluster support them. + +See core server changes below as well as the [GitHub project dedicated to feature flags improvements](https://github.com/orgs/rabbitmq/projects/4/views/1) +for the complete list of related changes. + + +## Breaking Changes and Compatibility Notes + +### MQTT + + * The default MQTT [Maximum Packet Size](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901086) changed from 256 MiB to 16 MiB. + + This default can be overridden by [configuring](https://www.rabbitmq.com/docs/configure#config-file) `mqtt.max_packet_size_authenticated`. + Note that this value must not be greater than `max_message_size` (which also defaults to 16 MiB). + + +## Erlang/OTP Compatibility Notes + +This release [requires Erlang 26.2](https://www.rabbitmq.com/docs/which-erlang) and supports Erlang 27.x. + +[Provisioning Latest Erlang Releases](https://www.rabbitmq.com/docs/which-erlang#erlang-repositories) explains +what package repositories and tools can be used to provision latest patch versions of Erlang 26.x and 27.x. + + +## Release Artifacts + +Artifacts for preview releases are distributed via GitHub releases: + + * In main repository, [`rabbitmq/rabbitmq-server`](https://github.com/rabbitmq/rabbitmq-server/releases) + * In the development builds repository, [`rabbitmq/server-packages`](https://github.com/rabbitmq/server-packages/releases) + +There is a `4.1.0` preview version of the [community RabbitMQ image](https://github.com/docker-library/rabbitmq). + + +## Upgrading to 4.1.0 + +### Documentation guides on upgrades + +See the [Upgrading guide](https://www.rabbitmq.com/docs/upgrade) for documentation on upgrades and [GitHub releases](https://github.com/rabbitmq/rabbitmq-server/releases) +for release notes of individual releases. + +This release series only supports upgrades from `4.0.x`. + +[Blue/Green Deployment](https://www.rabbitmq.com/docs/blue-green-upgrade)-style upgrades are avaialble for migrations from 3.12.x and 3.13.x series +to `4.1.x`. + +### Required Feature Flags + +None/TBD. + +### Mixed version cluster compatibility + +RabbitMQ 4.1.0 nodes can run alongside `4.0.x` nodes. `4.1.x`-specific features can only be made available when all nodes in the cluster +upgrade to 4.0.0 or a later patch release in the new series. + +While operating in mixed version mode, some aspects of the system may not behave as expected. The list of known behavior changes will be covered in future updates. +Once all nodes are upgraded to 4.1.0, these irregularities will go away. + +Mixed version clusters are a mechanism that allows rolling upgrade and are not meant to be run for extended +periods of time (no more than a few hours). + +### Recommended Post-upgrade Procedures + +This version does not require any additional post-upgrade procedures +compared to other versions. + + + +## Changes Worth Mentioning + +This section can be incomplete and will be expanded as 4.1 approaches its release candidate stage. + +### Core Server + +#### Enhancements + + * Feature flag quality of live improvements. + + Certain required feature flags will now be automatically required on node boot + and do not have to be explicitly enabled before an upgrade. + This does not apply to all feature flags, however. + + GitHub project: [#4](https://github.com/orgs/rabbitmq/projects/4/views/1). + + GitHub issues: [#12466](https://github.com/rabbitmq/rabbitmq-server/pull/12466), [#12444](https://github.com/rabbitmq/rabbitmq-server/pull/12444), + [#12447](https://github.com/rabbitmq/rabbitmq-server/pull/12447) + + * `properties` and `appliation-properties` filters of [AMQP Filter Expressions Version 1.0 Working Draft 09](https://groups.oasis-open.org/higherlogic/ws/public/document?document_id=66227) + when consuming from a stream via AMQP 1.0. String prefix and suffix matching is also supported. + + This feature adds the ability to RabbitMQ to have multiple concurrent clients each consuming only a subset of messages while maintaining message order. + It also reduces network traffic between RabbitMQ and clients by only dispatching those messages that the clients are actually interested in. + + GitHub issue: [#12415](https://github.com/rabbitmq/rabbitmq-server/pull/12415) + + * AMQP 1.0 connections that use OAuth 2.0 now can renew their JWT tokens + This allows clients to set a new token proactively before the current one [expires](/docs/oauth2#token-expiration), ensuring uninterrupted connectivity. + If a client does not set a new token before the existing one expires, RabbitMQ will automatically close the AMQP 1.0 connection. + + GitHub issue: [#12599](https://github.com/rabbitmq/rabbitmq-server/pull/12599) + + * Nodes will now fall back to system CA certificate list (if available) when no CA certificate + is explicitly configured. + + Contributed by @LoisSotoLopez. + + GitHub issue: [#10519](https://github.com/rabbitmq/rabbitmq-server/issues/10519), [#12564](https://github.com/rabbitmq/rabbitmq-server/pull/12564) + + * Support for Multiple Routing Keys in AMQP 1.0 via `x-cc` Message Annotation. + + AMQP 1.0 publishers now can set multiple routing keys by using the `x-cc` message annotation. + This annotation allows publishers to specify a [list](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-types-v1.0-os.html#type-list) + of routing keys ([strings](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-types-v1.0-os.html#type-string)) for more flexible message distribution, + similar to the [CC](https://www.rabbitmq.com/docs/sender-selected) header in AMQP 0.9.1. + + GitHub issue: [#12559](https://github.com/rabbitmq/rabbitmq-server/pull/12559) + + * Peer discovery resilience improvements. + + GitHub issues: [#12801](https://github.com/rabbitmq/rabbitmq-server/pull/12801), [#12809](https://github.com/rabbitmq/rabbitmq-server/pull/12809) + +#### Bug Fixes + + * AMQP 0-9-1 channel exception generator could not handle entity names (say, queue or stream names) + that contained non-ASCII characters. + + This affected applications that use passive queue declarations, such as the Shovel plugin. + + Contributed by @bpint. + + GitHub issue: [#12888](https://github.com/rabbitmq/rabbitmq-server/pull/12888) + + * Reintroduced transient flow control between classic queue replicas and AMQP 0-9-1 channels, + MQTT connections. + + Flow control between these specific parts of the core were unintentionally + removed in `4.0.0` together with classic queue mirroring. + + Contributed by @gomoripeti. + + GitHub issue: [#12907](https://github.com/rabbitmq/rabbitmq-server/pull/12907) + + * AMQP 1.0 connections with a higher consumption rate could set the incoming window field + on the flow frame to a negative value, which resulted in an exception that affected the consumer. + + GitHub issues: [#12816](https://github.com/rabbitmq/rabbitmq-server/issues/12816) + + * In rare cases quorum queue could end up without an elected leader because + chosen candidate replica was not verified for aliveness. + + Contributed by @Ayanda-D. + + GitHub issues: [#12727](https://github.com/rabbitmq/rabbitmq-server/pull/12727), [#10423](https://github.com/rabbitmq/rabbitmq-server/discussions/10423), [#12701](https://github.com/rabbitmq/rabbitmq-server/discussions/12701) + + * When a new replica is added to a quorum queue, the node that handles this request will now wait + the operation to complete. Previously an early return could result in confusing `cluster_change_not_permitted` + errors for subsequent operations, for example, an addition of another replica. + + GitHub issue: [#12837](https://github.com/rabbitmq/rabbitmq-server/pull/12837) + + * In very rare cases, RabbitMQ could fail to notify stream consumers connected to follower replicas + about newly committed offsets as quickly as it usually happens for consumers connected to the stream leader. + + GitHub issue: [#12785](https://github.com/rabbitmq/rabbitmq-server/pull/12785) + + +### MQTT Plugin + +#### Enhancements + + * The default MQTT [Maximum Packet Size](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901086) changed from 256 MiB to 16 MiB. + + This default can be overridden by [configuring](https://www.rabbitmq.com/docs/configure#config-file) `mqtt.max_packet_size_authenticated`. + Note that this value must not be greater than `max_message_size` (which also defaults to 16 MiB). + + +### CLI Tools + +#### Enhancements + + * New major version of `rabbitmqadmin`, a CLI tool that targets RabbitMQ's HTTP API, is maturing. + Unlike its predecessor, the tool is distirbuted via GitHub as as a standalone native binary. + + There are minor command line interface changes and a slightly different configuration file + format ([TOML](https://toml.io/en/) instead of `ini`) + + GitHub repository: [`rabbitmq/rabbitmqadmin-ng`](https://github.com/rabbitmq/rabbitmqadmin-ng) + + * `rabbitmq-diagnostics check_if_any_deprecated_features_are_used` implementation is now more complete + (checks for a more deprecated features). + + GitHub issue: [#12675](https://github.com/rabbitmq/rabbitmq-server/pull/12675) + +#### Bug Fixes + + * Fixes a false positive that incorrectly reported deprecated feature use, specifically + the use of non-exclusive transient classic queues. + + GitHub issue: [#12840](https://github.com/rabbitmq/rabbitmq-server/pull/12840) + + +### Prometheus Plugin + +#### Enhancements + + * RabbitMQ nodes now provide a Prometheus histogram for message sizes published by applications. + + This feature allows operators to gain insights into the message sizes being published to RabbitMQ, + such as average message size, number of messages per pre-defined bucket (which can both be computed accurately), and percentiles (which will be approximated). + Each metric is labelled by protocol (AMQP 1.0, AMQP 0.9.1, MQTT 5.0, MQTT 3.1.1, and MQTT 3.1). + + GitHub issue: [#12342](https://github.com/rabbitmq/rabbitmq-server/pull/12342) + + * Two new stream metrics for streams. + + Contributed by @gomoripeti and @markus812498. + + GitHub issue: [#12765](https://github.com/rabbitmq/rabbitmq-server/pull/12765) + + +### Grafana Dashboards + +#### Bug Fixes + + * Grafana 11.3.x compatibility. + + Contributed by @anhanhnguyen. + + GitHub issue: [#12720](https://github.com/rabbitmq/rabbitmq-server/pull/12720) + + +### Management UI + +#### Enhancements + + * Static assets served by the management UI now have a `control-cache` header set + to make sure that browsers reload them between upgrades. + + UI code now tries to track version changes to perform a forced reload of the entire page. + + GitHub issue: [#12749](https://github.com/rabbitmq/rabbitmq-server/pull/12749) + + * Connection pages now display detailed AMQP 1.0 session and link information: + + 1. Link names + 2. Link target and source addresses + 3. Link flow control state + 4. Session flow control state + 5. Number of unconfirmed and unacknowledged messages + + GitHub issue: [#12670](https://github.com/rabbitmq/rabbitmq-server/pull/12670) + + * The management UI now shows if a feature flag has a migration function (in other words, it may take time to be enabled), + if it is experimental and whether it is supported or not. To enable an experimental feature flag, + a user must to tick checkboxes to confirm they know what they are doing. + + GitHub issue: [#12643](https://github.com/rabbitmq/rabbitmq-server/pull/12643) + + * Feature flags are now enabled using asynchronous requests in the management UI. + This means that feature flags that perform data migrations (which can take some time) + won't block the browser tab. + + GitHub issue: [#12643](https://github.com/rabbitmq/rabbitmq-server/pull/12643) + +#### Bug Fixes + + * Fixes a false positive that incorrectly reported deprecated feature use, specifically + the use of non-exclusive transient classic queues. + + GitHub issue: [#12840](https://github.com/rabbitmq/rabbitmq-server/pull/12840) + + * When a logged in user's JWT token was refreshed, the user identity displayed in the UI was changed. + + GitHub issue: [#12818](https://github.com/rabbitmq/rabbitmq-server/pull/12818) + + +### Shovel Plugin + +#### Bug Fixes + + * AMQP 0-9-1 channel exception generator could not handle entity names (say, queue or stream names) + that contained non-ASCII characters. + + This affected applications that use passive queue declarations, such as the Shovel plugin. + + Contributed by @bpint. + + GitHub issue: [#12888](https://github.com/rabbitmq/rabbitmq-server/pull/12888) + + +### Event Exchange Plugin + +#### Enhancements + + * The `rabbitmq_event_exchange` plugin now can be configured to internally publish AMQP 1.0 instead of AMQP 0.9.1 messages to the `amq.rabbitmq.event` topic exchange. + + This allows AMQP 1.0 consumers to receive event properties containing complex types such as [lists](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-types-v1.0-os.html#type-list) + or [maps](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-types-v1.0-os.html#type-map), for example queue arguments for the `queue.created` + event or client provided properties for the `connection.created` event. + + GitHub issue: [#12714](https://github.com/rabbitmq/rabbitmq-server/pull/12714) + + +### AWS Peer Discovery Plugin + +#### Bug Fixes + + * Avoids an exception during automatic removal of cluster members that are + no longer returned by peer discovery (an [opt-in feature](https://www.rabbitmq.com/docs/cluster-formation#node-health-checks-and-cleanup)). + + GitHub issue: [#12809](https://github.com/rabbitmq/rabbitmq-server/pull/12809) + + +### Kubernetes Peer Discovery Plugin + +#### Bug Fixes + + * Avoids an exception during automatic removal of cluster members that are + no longer returned by peer discovery (an [opt-in feature](https://www.rabbitmq.com/docs/cluster-formation#node-health-checks-and-cleanup)). + + GitHub issue: [#12809](https://github.com/rabbitmq/rabbitmq-server/pull/12809) + + +### Consul Peer Discovery Plugin + +#### Bug Fixes + + * Avoids an exception during automatic removal of cluster members that are + no longer returned by peer discovery (an [opt-in feature](https://www.rabbitmq.com/docs/cluster-formation#node-health-checks-and-cleanup)). + + GitHub issue: [#12809](https://github.com/rabbitmq/rabbitmq-server/pull/12809) + + +### etcd Peer Discovery Plugin + +#### Bug Fixes + + * Avoids an exception during automatic removal of cluster members that are + no longer returned by peer discovery (an [opt-in feature](https://www.rabbitmq.com/docs/cluster-formation#node-health-checks-and-cleanup)). + + GitHub issue: [#12809](https://github.com/rabbitmq/rabbitmq-server/pull/12809) + + +### Dependency Changes + + * `osiris` was upgraded to [`1.8.5`](https://github.com/rabbitmq/osiris/releases) + + +## Source Code Archives + +To obtain source code of the entire distribution, please download the archive named `rabbitmq-server-4.1.0.tar.xz` +instead of the source tarball produced by GitHub. diff --git a/selenium/.gitignore b/selenium/.gitignore index ee78120fba98..fa97e18b0f68 100644 --- a/selenium/.gitignore +++ b/selenium/.gitignore @@ -13,4 +13,8 @@ test/*/certs/*.p12 test/*/certs/*.jks test/*/*/*.pem test/*/*/*.p12 -test/*/*/*.jks \ No newline at end of file +<<<<<<< HEAD +test/*/*/*.jks +======= +test/*/*/*.jks +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/bin/components/devkeycloak b/selenium/bin/components/devkeycloak index 87062b67465b..35814ec1a62b 100644 --- a/selenium/bin/components/devkeycloak +++ b/selenium/bin/components/devkeycloak @@ -48,7 +48,11 @@ start_devkeycloak() { --https-certificate-key-file=/opt/keycloak/data/import/server_devkeycloak_key.pem \ --hostname=devkeycloak --hostname-admin=devkeycloak --https-port=8442 +<<<<<<< HEAD wait_for_oidc_endpoint devkeycloak $DEVKEYCLOAK_URL $MOUNT_DEVKEYCLOAK_CONF_DIR/ca_devkeycloak_certificate.pem +======= + wait_for_oidc_endpoint devkeycloak $DEVKEYCLOAK_URL $MOUNT_DEVKEYCLOAK_CONF_DIR/ca_certificate.pem +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end "devkeycloak is ready" print " Note: If you modify devkeycloak configuration, make sure to run the following command to export the configuration." print " docker exec -it devkeycloak /opt/keycloak/bin/kc.sh export --users realm_file --realm test --dir /opt/keycloak/data/import/" diff --git a/selenium/bin/components/fakeportal b/selenium/bin/components/fakeportal index aadbda50327b..98f562820310 100644 --- a/selenium/bin/components/fakeportal +++ b/selenium/bin/components/fakeportal @@ -1,3 +1,13 @@ +<<<<<<< HEAD +======= +#!/usr/bin/env bash + +SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +if [[ ! -z "${DEBUG}" ]]; then + set -x +fi +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ensure_fakeportal() { if docker ps | grep fakeportal &> /dev/null; then @@ -9,7 +19,11 @@ ensure_fakeportal() { init_fakeportal() { FAKEPORTAL_URL=${FAKEPORTAL_URL:-http://fakeportal:3000} +<<<<<<< HEAD FAKEPORTAL_DIR=${SCRIPT}/../fakeportal +======= + FAKEPORTAL_DIR=${SCRIPT}/../../fakeportal +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) CLIENT_ID="${CLIENT_ID:-rabbit_idp_user}" CLIENT_SECRET="${CLIENT_SECRET:-rabbit_idp_user}" RABBITMQ_HOST=${RABBITMQ_HOST:-proxy:9090} @@ -44,6 +58,11 @@ start_fakeportal() { --env UAA_URL="${UAA_URL_FOR_FAKEPORTAL}" \ --env CLIENT_ID="${CLIENT_ID}" \ --env CLIENT_SECRET="${CLIENT_SECRET}" \ +<<<<<<< HEAD +======= + --env NODE_EXTRA_CA_CERTS=/etc/uaa/ca_uaa_certificate.pem \ + -v ${TEST_CONFIG_PATH}/uaa:/etc/uaa \ +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -v ${FAKEPORTAL_DIR}:/code/fakeportal \ mocha-test:${mocha_test_tag} run fakeportal diff --git a/selenium/bin/components/fakeproxy b/selenium/bin/components/fakeproxy index 2705ee80427e..95fbf08a6b99 100644 --- a/selenium/bin/components/fakeproxy +++ b/selenium/bin/components/fakeproxy @@ -1,4 +1,14 @@ +<<<<<<< HEAD +======= +#!/usr/bin/env bash + +SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +if [[ ! -z "${DEBUG}" ]]; then + set -x +fi +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ensure_fakeproxy() { if docker ps | grep fakeproxy &> /dev/null; then @@ -10,7 +20,11 @@ ensure_fakeproxy() { init_fakeproxy() { FAKEPROXY_URL=${FAKEPROXY_URL:-http://fakeproxy:9090} +<<<<<<< HEAD FAKEPROXY_DIR=${SCRIPT}/../fakeportal +======= + FAKEPROXY_DIR=${SCRIPT}/../../fakeportal +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) CLIENT_ID="${CLIENT_ID:-rabbit_idp_user}" CLIENT_SECRET="${CLIENT_SECRET:-rabbit_idp_user}" RABBITMQ_HOST_FOR_FAKEPROXY=${RABBITMQ_HOST_FOR_FAKEPROXY:-rabbitmq:15672} @@ -43,6 +57,11 @@ start_fakeproxy() { --env UAA_URL="${UAA_URL_FOR_FAKEPROXY}" \ --env CLIENT_ID="${CLIENT_ID}" \ --env CLIENT_SECRET="${CLIENT_SECRET}" \ +<<<<<<< HEAD +======= + --env NODE_EXTRA_CA_CERTS=/etc/uaa/ca_uaa_certificate.pem \ + -v ${TEST_CONFIG_PATH}/uaa:/etc/uaa \ +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -v ${FAKEPROXY_DIR}:/code/fakeportal \ mocha-test:${mocha_test_tag} run fakeproxy diff --git a/selenium/bin/components/prodkeycloak b/selenium/bin/components/prodkeycloak index 9753d66ebf4b..a18c8f5d980f 100644 --- a/selenium/bin/components/prodkeycloak +++ b/selenium/bin/components/prodkeycloak @@ -17,7 +17,12 @@ init_prodkeycloak() { print "> PRODKEYCLOAK_URL: ${PRODKEYCLOAK_URL}" print "> KEYCLOAK_DOCKER_IMAGE: ${KEYCLOAK_DOCKER_IMAGE}" +<<<<<<< HEAD generate-ca-server-client-kpi prodkeycloak $PRODKEYCLOAK_CONFIG_DIR +======= + generate-ca-server-client-kpi prodkeycloak $PRODKEYCLOAK_CONFIG_DIR + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) } start_prodkeycloak() { begin "Starting prodkeycloak ..." @@ -46,7 +51,11 @@ start_prodkeycloak() { --https-certificate-key-file=/opt/keycloak/data/import/server_prodkeycloak_key.pem \ --hostname=prodkeycloak --hostname-admin=prodkeycloak --https-port=8443 +<<<<<<< HEAD wait_for_oidc_endpoint prodkeycloak $PRODKEYCLOAK_URL $MOUNT_PRODKEYCLOAK_CONF_DIR/ca_prodkeycloak_certificate.pem +======= + wait_for_oidc_endpoint prodkeycloak $PRODKEYCLOAK_URL $MOUNT_PRODKEYCLOAK_CONF_DIR/ca_certificate.pem +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end "prodkeycloak is ready" print " Note: If you modify prodkeycloak configuration, make sure to run the following command to export the configuration." print " docker exec -it prodkeycloak /opt/keycloak/bin/kc.sh export --users realm_file --realm test --dir /opt/keycloak/data/import/" diff --git a/selenium/bin/components/rabbitmq b/selenium/bin/components/rabbitmq index 46cbb1ee2738..69df0e776b17 100644 --- a/selenium/bin/components/rabbitmq +++ b/selenium/bin/components/rabbitmq @@ -1,6 +1,14 @@ +<<<<<<< HEAD #!/usr/bin/env bash +======= +#!/usr/bin/env bash + +SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) init_rabbitmq() { RABBITMQ_CONFIG_DIR=${TEST_CONFIG_DIR} RABBITMQ_DOCKER_IMAGE=${RABBITMQ_DOCKER_IMAGE:-rabbitmq} @@ -60,6 +68,10 @@ start_local_rabbitmq() { init_rabbitmq RABBITMQ_SERVER_ROOT=$(realpath ../) +<<<<<<< HEAD +======= + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) MOUNT_RABBITMQ_CONF="/etc/rabbitmq/rabbitmq.conf" MOUNT_ADVANCED_CONFIG="/etc/rabbitmq/advanced.config" @@ -164,7 +176,11 @@ start_docker_rabbitmq() { if [ -f ${RABBITMQ_CONFIG_DIR}/enabled_plugins ]; then cp ${RABBITMQ_CONFIG_DIR}/enabled_plugins $CONF_DIR/rabbitmq fi +<<<<<<< HEAD if [ -d ${RABBITMQ_CONFIG_DIR}/certs ]; then +======= + if [ -d "${RABBITMQ_CONFIG_DIR}/certs" ]; then +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) cp -r ${RABBITMQ_CONFIG_DIR}/certs $CONF_DIR/rabbitmq fi if [ -d ${RABBITMQ_CONFIG_DIR}/imports ]; then @@ -182,10 +198,17 @@ start_docker_rabbitmq() { -p 15672:15672 \ -p 15671:15671 \ -v $CONF_DIR/rabbitmq/:/etc/rabbitmq \ +<<<<<<< HEAD -v $CONF_DIR/rabbitmq/:/var/rabbitmq \ -v ${TEST_DIR}:/config \ ${RABBITMQ_DOCKER_IMAGE} +======= + -v $CONF_DIR/rabbitmq/imports:/var/rabbitmq/imports \ + -v ${TEST_DIR}:/config \ + ${RABBITMQ_DOCKER_IMAGE} + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) wait_for_message rabbitmq "Server startup complete" end "RabbitMQ ready" } diff --git a/selenium/bin/components/uaa b/selenium/bin/components/uaa index b344ee0211bd..18ef0a6b8d70 100644 --- a/selenium/bin/components/uaa +++ b/selenium/bin/components/uaa @@ -16,7 +16,11 @@ init_uaa() { print "> UAA_CONFIG_DIR: ${UAA_CONFIG_DIR}" print "> UAA_URL: ${UAA_URL}" print "> UAA_DOCKER_IMAGE: ${UAA_DOCKER_IMAGE}" +<<<<<<< HEAD +======= + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) generate-ca-server-client-kpi uaa $UAA_CONFIG_DIR generate-server-keystore-if-required uaa $UAA_CONFIG_DIR } @@ -37,12 +41,22 @@ start_uaa() { --detach \ --name uaa \ --net ${DOCKER_NETWORK} \ +<<<<<<< HEAD --publish 8080:8080 \ --mount "type=bind,source=$MOUNT_UAA_CONF_DIR,target=/uaa" \ --env UAA_CONFIG_PATH="/uaa" \ --env JAVA_OPTS="-Djava.security.egd=file:/dev/./urandom" \ ${UAA_DOCKER_IMAGE} +======= + --publish 8443:8443 \ + -v ${MOUNT_UAA_CONF_DIR}:/uaa \ + -v ${UAA_CONFIG_DIR}/server.xml:/layers/paketo-buildpacks_apache-tomcat/catalina-base/conf/server.xml \ + --env UAA_CONFIG_PATH="/uaa" \ + --env JAVA_OPTS="-Djava.security.policy=unlimited -Djava.security.egd=file:/dev/./urandom" \ + ${UAA_DOCKER_IMAGE} + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) wait_for_oidc_endpoint uaa $UAA_URL end "UAA is ready" } diff --git a/selenium/bin/gen-env-file b/selenium/bin/gen-env-file index 60c4b4bfc50d..8b9ce5c6dfe9 100755 --- a/selenium/bin/gen-env-file +++ b/selenium/bin/gen-env-file @@ -1,7 +1,14 @@ #!/usr/bin/env bash SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +<<<<<<< HEAD #set -x +======= +if [[ ! -z "${DEBUG}" ]]; then + set -x +fi + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ENV_FILE="/tmp/rabbitmq/.env" FIND_PATH=$1 @@ -13,6 +20,11 @@ generate_env_file() { mkdir -p $parentdir echo "#!/usr/bin/env bash" > $ENV_FILE echo "set -u" >> $ENV_FILE +<<<<<<< HEAD +======= + echo "export SELENIUM=${SCRIPT}/.." >> $ENV_FILE + echo "export TEST_CONFIG_PATH=${FIND_PATH}" >> $ENV_FILE +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) declare -a FILE_ARRAY for f in $($SCRIPT/find-template-files $FIND_PATH "env") diff --git a/selenium/bin/suite_template b/selenium/bin/suite_template index 4b8427994dfe..37798cb16863 100644 --- a/selenium/bin/suite_template +++ b/selenium/bin/suite_template @@ -30,9 +30,15 @@ find_selenium_dir() { SELENIUM_ROOT_FOLDER=$(find_selenium_dir $SCRIPT) TEST_DIR=$SELENIUM_ROOT_FOLDER/test BIN_DIR=$SELENIUM_ROOT_FOLDER/bin +<<<<<<< HEAD LOGS=${SELENIUM_ROOT_FOLDER}/logs/${SUITE} SCREENS=${SELENIUM_ROOT_FOLDER}/screens/${SUITE} CONF_DIR=/tmp/selenium/${SUITE} +======= +SCREENS=${SELENIUM_ROOT_FOLDER}/screens/${SUITE} +CONF_DIR=/tmp/selenium/${SUITE} +LOGS=${CONF_DIR}/logs +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ENV_FILE=$CONF_DIR/.env rm -rf $CONF_DIR @@ -132,7 +138,11 @@ build_mocha_image() { tag=($(md5sum $SELENIUM_ROOT_FOLDER/package.json)) print "> tag : $tag" if [[ $(docker images -q mocha-test:$tag 2> /dev/null) == "" ]]; then +<<<<<<< HEAD docker build -t mocha-test:$tag --target test $SCRIPT/.. +======= + docker build -t mocha-test:$tag --target test $SELENIUM_ROOT_FOLDER +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) print "> Built docker image mocha-test:$tag" fi end "mocha-test image exists" @@ -170,13 +180,21 @@ wait_for_oidc_endpoint() { wait_for_oidc_endpoint_local() { NAME=$1 BASE_URL=$2 +<<<<<<< HEAD CURL_ARGS="-L --fail " +======= + CURL_ARGS="-k --tlsv1.2 -L --fail " +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) DELAY_BETWEEN_ATTEMPTS=5 if [[ $# -eq 3 ]]; then CURL_ARGS="$CURL_ARGS --cacert $3" DELAY_BETWEEN_ATTEMPTS=10 fi +<<<<<<< HEAD max_retry=10 +======= + max_retry=15 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) counter=0 print "Waiting for OIDC discovery endpoint $NAME ... (BASE_URL: $BASE_URL)" until (curl $CURL_ARGS ${BASE_URL}/.well-known/openid-configuration >/dev/null 2>&1) @@ -191,7 +209,11 @@ wait_for_oidc_endpoint_local() { wait_for_oidc_endpoint_docker() { NAME=$1 BASE_URL=$2 +<<<<<<< HEAD CURL_ARGS="-L --fail " +======= + CURL_ARGS="-k --tlsv1.2 -L --fail " +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) DOCKER_ARGS="--rm --net ${DOCKER_NETWORK} " DELAY_BETWEEN_ATTEMPTS=5 if [[ $# -gt 2 ]]; then @@ -199,7 +221,11 @@ wait_for_oidc_endpoint_docker() { CURL_ARGS="$CURL_ARGS --cacert /tmp/ca_certificate.pem" DELAY_BETWEEN_ATTEMPTS=10 fi +<<<<<<< HEAD max_retry=10 +======= + max_retry=15 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) counter=0 print "Waiting for OIDC discovery endpoint $NAME ... (BASE_URL: $BASE_URL)" until (docker run $DOCKER_ARGS curlimages/curl:7.85.0 $CURL_ARGS ${BASE_URL}/.well-known/openid-configuration >/dev/null 2>&1) @@ -333,9 +359,17 @@ _test() { --env SELENIUM_POLLING=${SELENIUM_POLLING} \ --env PROFILES="${PROFILES}" \ --env ENV_FILE="/code/.env" \ +<<<<<<< HEAD + --env NODE_EXTRA_CA_CERTS=/nodejs/ca.pem \ + -v ${MOUNT_NODE_EXTRA_CA_CERTS}:/nodejs/ca.pem \ + -v ${TEST_DIR}:/code/test \ +======= + --env RABBITMQ_CERTS=/etc/rabbitmq/certs \ --env NODE_EXTRA_CA_CERTS=/nodejs/ca.pem \ -v ${MOUNT_NODE_EXTRA_CA_CERTS}:/nodejs/ca.pem \ -v ${TEST_DIR}:/code/test \ + -v ${TEST_CONFIG_DIR}/certs:/etc/rabbitmq/certs \ +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -v ${SCREENS}:/screens \ -v ${ENV_FILE}:/code/.env \ mocha-test:${mocha_test_tag} test /code/test${TEST_CASES_PATH} @@ -371,8 +405,13 @@ profiles_with_local_or_docker() { generate_env_file() { begin "Generating env file ..." mkdir -p $CONF_DIR +<<<<<<< HEAD ${BIN_DIR}/gen-env-file $TEST_CONFIG_DIR $ENV_FILE source $ENV_FILE +======= + ${BIN_DIR}/gen-env-file $TEST_CONFIG_DIR $ENV_FILE + source $ENV_FILE +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end "Finished generating env file." } generate-ca-server-client-kpi() { @@ -468,6 +507,10 @@ generate-client-keystore-if-required() { -noprompt fi } +<<<<<<< HEAD +======= + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) run() { runWith rabbitmq } @@ -582,7 +625,11 @@ test_local() { export RABBITMQ_AMQP_PASSWORD=${RABBITMQ_AMQP_PASSWORD} export SELENIUM_TIMEOUT=${SELENIUM_TIMEOUT:-20000} export SELENIUM_POLLING=${SELENIUM_POLLING:-500} +<<<<<<< HEAD +======= + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) print "> SELENIUM_TIMEOUT: ${SELENIUM_TIMEOUT}" print "> SELENIUM_POLLING: ${SELENIUM_POLLING}" print "> RABBITMQ_HOST: ${RABBITMQ_HOST}" diff --git a/selenium/fakeportal/app.js b/selenium/fakeportal/app.js index ea0ff1a37021..b9e37c75334f 100644 --- a/selenium/fakeportal/app.js +++ b/selenium/fakeportal/app.js @@ -56,8 +56,15 @@ function access_token(id, secret) { if (req.status == 200) { const token = JSON.parse(req.responseText).access_token; console.log("Token => " + token) +<<<<<<< HEAD return token; } else { throw new Error(req.status + " : " + req.responseText); +======= + return token + } else { + throw new Error(req.status + " : " + " : " + + req.response + " : " + req.responseText) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) } } diff --git a/selenium/full-suite-authnz-messaging b/selenium/full-suite-authnz-messaging index 5eec8081fa62..d45962156358 100644 --- a/selenium/full-suite-authnz-messaging +++ b/selenium/full-suite-authnz-messaging @@ -4,6 +4,10 @@ authnz-messaging/auth-http-backend.sh authnz-messaging/auth-http-internal-backends-with-internal.sh authnz-messaging/auth-http-internal-backends.sh authnz-messaging/auth-internal-backend.sh +<<<<<<< HEAD +======= +authnz-messaging/auth-internal-mtls-backend.sh +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) authnz-messaging/auth-internal-http-backends.sh authnz-messaging/auth-ldap-backend.sh authnz-messaging/auth-http-backend.sh diff --git a/selenium/full-suite-management-ui b/selenium/full-suite-management-ui index dc58a67e0edf..adadb91d938f 100644 --- a/selenium/full-suite-management-ui +++ b/selenium/full-suite-management-ui @@ -1,10 +1,17 @@ authnz-mgt/basic-auth-behind-proxy.sh authnz-mgt/basic-auth.sh authnz-mgt/basic-auth-with-mgt-prefix.sh +<<<<<<< HEAD authnz-mgt/multi-oauth-with-basic-auth.sh authnz-mgt/multi-oauth-without-basic-auth-and-resource-label-and-scopes.sh authnz-mgt/multi-oauth-without-basic-auth.sh authnz-mgt/multi-oauth-with-basic-auth-when-idps-down.sh +======= +authnz-mgt/multi-oauth-with-basic-auth-when-idps-down.sh +authnz-mgt/multi-oauth-with-basic-auth.sh +authnz-mgt/multi-oauth-without-basic-auth-and-resource-label-and-scopes.sh +authnz-mgt/multi-oauth-without-basic-auth.sh +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) authnz-mgt/oauth-and-basic-auth.sh authnz-mgt/oauth-idp-initiated-with-uaa-and-prefix-via-proxy.sh authnz-mgt/oauth-idp-initiated-with-uaa-and-prefix.sh diff --git a/selenium/package.json b/selenium/package.json index 7bab8c6add7d..64b8749a4c31 100644 --- a/selenium/package.json +++ b/selenium/package.json @@ -12,7 +12,11 @@ "author": "", "license": "ISC", "dependencies": { +<<<<<<< HEAD "chromedriver": "^128.0.0", +======= + "chromedriver": "^130.0.4", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "ejs": "^3.1.8", "express": "^4.18.2", "geckodriver": "^3.0.2", @@ -21,7 +25,11 @@ "path": "^0.12.7", "proxy": "^1.0.2", "rhea": "^3.0.3", +<<<<<<< HEAD "selenium-webdriver": "^4.19.0", +======= + "selenium-webdriver": "^4.26.0", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "xmlhttprequest": "^1.8.0" }, "devDependencies": { diff --git a/selenium/run-suites.sh b/selenium/run-suites.sh index 70f7e5685a45..d8824e339df6 100755 --- a/selenium/run-suites.sh +++ b/selenium/run-suites.sh @@ -30,7 +30,11 @@ do fi echo -e "=== $TEST_STATUS $SUITE ===========================================" echo " " +<<<<<<< HEAD done <<< "$(cat $SCRIPT/$SUITE_FILE)" +======= +done <<< "$(cat $SCRIPT/$SUITE_FILE | sort)" +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) echo -e "=== Summary (${TOTAL_SUITES}/${GREEN}${#SUCCESSFUL_SUITES[@]}/${RED}${#FAILED_SUITES[@]}${NC}) ============================================" if [ ${#SUCCESSFUL_SUITES[@]} -gt 0 ]; then echo -e " > ${GREEN}Successful suites ${NC}"; fi diff --git a/selenium/short-suite-management-ui b/selenium/short-suite-management-ui index dd0c79f0f889..9b924c990ef5 100644 --- a/selenium/short-suite-management-ui +++ b/selenium/short-suite-management-ui @@ -1,5 +1,13 @@ authnz-mgt/basic-auth.sh authnz-mgt/oauth-with-keycloak.sh +<<<<<<< HEAD mgt/vhosts.sh mgt/exchanges.sh mgt/limits.sh +======= +authnz-mgt/oauth-with-uaa.sh +mgt/vhosts.sh +mgt/exchanges.sh +mgt/limits.sh +mgt/amqp10-connections.sh +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/suites/authnz-messaging/auth-internal-backend.sh b/selenium/suites/authnz-messaging/auth-internal-backend.sh index a3f49c7ecf96..15ee2bb5cf94 100755 --- a/selenium/suites/authnz-messaging/auth-internal-backend.sh +++ b/selenium/suites/authnz-messaging/auth-internal-backend.sh @@ -3,7 +3,11 @@ SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" TEST_CASES_PATH=/authnz-msg-protocols +<<<<<<< HEAD PROFILES="internal-user auth_backends-internal " +======= +PROFILES="internal-user auth_backends-internal" +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) source $SCRIPT/../../bin/suite_template run diff --git a/selenium/suites/authnz-messaging/auth-internal-mtls-backend.sh b/selenium/suites/authnz-messaging/auth-internal-mtls-backend.sh new file mode 100755 index 000000000000..df92f9d9cd43 --- /dev/null +++ b/selenium/suites/authnz-messaging/auth-internal-mtls-backend.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +TEST_CASES_PATH=/authnz-msg-protocols +PROFILES="internal-user auth_backends-internal tls auth-mtls" + +source $SCRIPT/../../bin/suite_template +run diff --git a/selenium/suites/authnz-mgt/oauth-with-uaa.sh b/selenium/suites/authnz-mgt/oauth-with-uaa.sh index 2e382ab2c5f2..f542ff30f541 100755 --- a/selenium/suites/authnz-mgt/oauth-with-uaa.sh +++ b/selenium/suites/authnz-mgt/oauth-with-uaa.sh @@ -4,7 +4,11 @@ SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" TEST_CASES_PATH=/oauth/with-sp-initiated TEST_CONFIG_PATH=/oauth +<<<<<<< HEAD PROFILES="uaa uaa-oauth-provider uaa-mgt-oauth-provider" +======= +PROFILES="uaa uaa-oauth-provider uaa-mgt-oauth-provider tls" +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) source $SCRIPT/../../bin/suite_template $@ runWith uaa diff --git a/selenium/suites/mgt/amqp10-connections.sh b/selenium/suites/mgt/amqp10-connections.sh new file mode 100755 index 000000000000..91be8686f385 --- /dev/null +++ b/selenium/suites/mgt/amqp10-connections.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +TEST_CASES_PATH=/connections/amqp10 +TEST_CONFIG_PATH=/basic-auth + +source $SCRIPT/../../bin/suite_template $@ +run diff --git a/selenium/test/amqp.js b/selenium/test/amqp.js index fe21cfdc8f87..2691c4352a8f 100644 --- a/selenium/test/amqp.js +++ b/selenium/test/amqp.js @@ -1,6 +1,10 @@ var container = require('rhea') // https://github.com/amqp/rhea var fs = require('fs'); var path = require('path'); +<<<<<<< HEAD +======= +var connectionOptions = getConnectionOptions() +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) function getAmqpConnectionOptions() { return { @@ -19,6 +23,7 @@ function getAmqpsConnectionOptions() { options['enable_sasl_external'] = true } options['transport'] = 'tls' +<<<<<<< HEAD let certsLocation = getEnv("RABBITMQ_CERTS"); options['key'] = fs.readFileSync(path.resolve(certsLocation,'client_rabbitmq_key.pem')) options['cert'] = fs.readFileSync(path.resolve(certsLocation,'client_rabbitmq_certificate.pem')) @@ -29,6 +34,20 @@ function getConnectionOptions() { case 'amqp': return getAmqpConnectionOptions() case 'amqps': +======= + let certsLocation = process.env.RABBITMQ_CERTS + options['key'] = fs.readFileSync(path.resolve(certsLocation,'client_rabbitmq_key.pem')) + options['cert'] = fs.readFileSync(path.resolve(certsLocation,'client_rabbitmq_certificate.pem')) + options['ca'] = fs.readFileSync(path.resolve(certsLocation,'ca_rabbitmq_certificate.pem')) + return options +} +function getConnectionOptions() { + let scheme = process.env.RABBITMQ_AMQP_SCHEME || 'amqp' + switch(scheme){ + case "amqp": + return getAmqpConnectionOptions() + case "amqps": +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) return getAmqpsConnectionOptions() } } @@ -40,7 +59,11 @@ module.exports = { resolve() }) }) +<<<<<<< HEAD let connection = container.connect(getConnectionOptions()) +======= + let connection = container.connect(connectionOptions) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) let receiver = connection.open_receiver({ source: 'my-queue', target: 'receiver-target', diff --git a/selenium/test/authnz-msg-protocols/amqp10.js b/selenium/test/authnz-msg-protocols/amqp10.js index a7cda69e0851..b46c31097bd8 100644 --- a/selenium/test/authnz-msg-protocols/amqp10.js +++ b/selenium/test/authnz-msg-protocols/amqp10.js @@ -10,7 +10,13 @@ var untilConnectionEstablished = new Promise((resolve, reject) => { }) }) +<<<<<<< HEAD +======= +onAmqp('message', function (context) { + receivedAmqpMessageCount++ +}) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) onceAmqp('sendable', function (context) { context.sender.send({body:'first message'}) }) @@ -50,6 +56,7 @@ describe('Having AMQP 1.0 protocol enabled and the following auth_backends: ' + }) it('can open an AMQP 1.0 connection', async function () { +<<<<<<< HEAD var untilFirstMessageReceived = new Promise((resolve, reject) => { onAmqp('message', function(context) { resolve() @@ -59,12 +66,22 @@ describe('Having AMQP 1.0 protocol enabled and the following auth_backends: ' + await untilConnectionEstablished await untilFirstMessageReceived var untilSecondMessageReceived = new Promise((resolve, reject) => { +======= + amqp = openAmqp() + await untilConnectionEstablished + var untilMessageReceived = new Promise((resolve, reject) => { +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) onAmqp('message', function(context) { resolve() }) }) amqp.sender.send({body:'second message'}) +<<<<<<< HEAD await untilSecondMessageReceived +======= + await untilMessageReceived + assert.equal(2, receivedAmqpMessageCount) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }) after(function () { @@ -76,7 +93,12 @@ describe('Having AMQP 1.0 protocol enabled and the following auth_backends: ' + closeAmqp(amqp.connection) } } catch (error) { +<<<<<<< HEAD console.error("Failed to close amqp10 connection due to " + error); } +======= + console.error("Failed to close amqp10 connection due to " + error); + } +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }) }) diff --git a/selenium/test/authnz-msg-protocols/env.auth-mtls b/selenium/test/authnz-msg-protocols/env.auth-mtls new file mode 100644 index 000000000000..d00282f8e180 --- /dev/null +++ b/selenium/test/authnz-msg-protocols/env.auth-mtls @@ -0,0 +1,2 @@ +export MQTT_USE_MTLS=true +export AMQP_USE_MTLS=true diff --git a/selenium/test/authnz-msg-protocols/env.auth-oauth-dev.docker b/selenium/test/authnz-msg-protocols/env.auth-oauth-dev.docker index cd05083899e2..119f2e84532d 100644 --- a/selenium/test/authnz-msg-protocols/env.auth-oauth-dev.docker +++ b/selenium/test/authnz-msg-protocols/env.auth-oauth-dev.docker @@ -1,2 +1,6 @@ export OAUTH_PROVIDER_URL=https://devkeycloak:8442/realms/dev +<<<<<<< HEAD export OAUTH_NODE_EXTRA_CA_CERTS=multi-oauth/devkeycloak/ca_devkeycloak_certificate.pem +======= +export OAUTH_NODE_EXTRA_CA_CERTS=multi-oauth/devkeycloak/ca_certificate.pem +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/authnz-msg-protocols/env.auth-oauth-dev.local b/selenium/test/authnz-msg-protocols/env.auth-oauth-dev.local index cd05083899e2..119f2e84532d 100644 --- a/selenium/test/authnz-msg-protocols/env.auth-oauth-dev.local +++ b/selenium/test/authnz-msg-protocols/env.auth-oauth-dev.local @@ -1,2 +1,6 @@ export OAUTH_PROVIDER_URL=https://devkeycloak:8442/realms/dev +<<<<<<< HEAD export OAUTH_NODE_EXTRA_CA_CERTS=multi-oauth/devkeycloak/ca_devkeycloak_certificate.pem +======= +export OAUTH_NODE_EXTRA_CA_CERTS=multi-oauth/devkeycloak/ca_certificate.pem +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/authnz-msg-protocols/env.auth-oauth-prod.docker b/selenium/test/authnz-msg-protocols/env.auth-oauth-prod.docker index 935fd0dea6f2..099d4b8b30cf 100644 --- a/selenium/test/authnz-msg-protocols/env.auth-oauth-prod.docker +++ b/selenium/test/authnz-msg-protocols/env.auth-oauth-prod.docker @@ -1,2 +1,6 @@ export OAUTH_PROVIDER_URL=https://prodkeycloak:8442/realms/prod +<<<<<<< HEAD export OAUTH_NODE_EXTRA_CA_CERTS=multi-oauth/prodkeycloak/ca_prodkeycloak_certificate.pem +======= +export OAUTH_NODE_EXTRA_CA_CERTS=multi-oauth/prodkeycloak/ca_certificate.pem +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/authnz-msg-protocols/env.auth-oauth-prod.local b/selenium/test/authnz-msg-protocols/env.auth-oauth-prod.local index 935fd0dea6f2..099d4b8b30cf 100644 --- a/selenium/test/authnz-msg-protocols/env.auth-oauth-prod.local +++ b/selenium/test/authnz-msg-protocols/env.auth-oauth-prod.local @@ -1,2 +1,6 @@ export OAUTH_PROVIDER_URL=https://prodkeycloak:8442/realms/prod +<<<<<<< HEAD export OAUTH_NODE_EXTRA_CA_CERTS=multi-oauth/prodkeycloak/ca_prodkeycloak_certificate.pem +======= +export OAUTH_NODE_EXTRA_CA_CERTS=multi-oauth/prodkeycloak/ca_certificate.pem +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/authnz-msg-protocols/env.docker.devkeycloak b/selenium/test/authnz-msg-protocols/env.docker.devkeycloak index 8f4ac30830db..a5080160816e 100644 --- a/selenium/test/authnz-msg-protocols/env.docker.devkeycloak +++ b/selenium/test/authnz-msg-protocols/env.docker.devkeycloak @@ -1,2 +1,6 @@ export DEVKEYCLOAK_URL=https://devkeycloak:8442/realms/dev +<<<<<<< HEAD export DEVKEYCLOAK_CA_CERT=/config/oauth/keycloak/ca_devkeycloak_certificate.pem +======= +export DEVKEYCLOAK_CA_CERT=/config/oauth/keycloak/ca_certificate.pem +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/authnz-msg-protocols/env.docker.prodkeycloak b/selenium/test/authnz-msg-protocols/env.docker.prodkeycloak index 82a1215b373c..f0487b0aba8c 100644 --- a/selenium/test/authnz-msg-protocols/env.docker.prodkeycloak +++ b/selenium/test/authnz-msg-protocols/env.docker.prodkeycloak @@ -1,2 +1,6 @@ export PRODKEYCLOAK_URL=https://prodkeycloak:8443/realms/prod +<<<<<<< HEAD export PRODKEYCLOAK_CA_CERT=/config/oauth/keycloak/ca_prodkeycloak_certificate.pem +======= +export PRODKEYCLOAK_CA_CERT=/config/oauth/keycloak/ca_certificate.pem +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/authnz-msg-protocols/env.local.devkeycloak b/selenium/test/authnz-msg-protocols/env.local.devkeycloak index 23978f5c87e5..ddc1596087f6 100644 --- a/selenium/test/authnz-msg-protocols/env.local.devkeycloak +++ b/selenium/test/authnz-msg-protocols/env.local.devkeycloak @@ -1,2 +1,6 @@ export DEVKEYCLOAK_URL=https://localhost:8442/realms/dev +<<<<<<< HEAD export DEVKEYCLOAK_CA_CERT=test/multi-oauth/devkeycloak/ca_devkeycloak_certificate.pem +======= +export DEVKEYCLOAK_CA_CERT=test/multi-oauth/devkeycloak/ca_certificate.pem +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/authnz-msg-protocols/env.local.prodkeycloak b/selenium/test/authnz-msg-protocols/env.local.prodkeycloak index 3b98ef1a4c3c..cfd191da4705 100644 --- a/selenium/test/authnz-msg-protocols/env.local.prodkeycloak +++ b/selenium/test/authnz-msg-protocols/env.local.prodkeycloak @@ -1,2 +1,6 @@ export PRODKEYCLOAK_URL=https://localhost:8443/realms/prod +<<<<<<< HEAD export PRODKEYCLOAK_CA_CERT=test/multi-oauth/prodkeycloak/ca_prodkeycloak_certificate.pem +======= +export PRODKEYCLOAK_CA_CERT=test/multi-oauth/prodkeycloak/ca_certificate.pem +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/authnz-msg-protocols/env.tls b/selenium/test/authnz-msg-protocols/env.tls new file mode 100644 index 000000000000..73854e5666ea --- /dev/null +++ b/selenium/test/authnz-msg-protocols/env.tls @@ -0,0 +1,2 @@ +export MQTT_PROTOCOL=mqtts +export RABBITMQ_MQTT_URL=mqtts://rabbitmq:8883 diff --git a/selenium/test/authnz-msg-protocols/mqtt.js b/selenium/test/authnz-msg-protocols/mqtt.js index 8a665c871834..8b67588377ec 100644 --- a/selenium/test/authnz-msg-protocols/mqtt.js +++ b/selenium/test/authnz-msg-protocols/mqtt.js @@ -1,3 +1,7 @@ +<<<<<<< HEAD +======= +const fs = require('fs') +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) const assert = require('assert') const { tokenFor, openIdConfiguration } = require('../utils') const { reset, expectUser, expectVhost, expectResource, allow, verifyAll } = require('../mock_http_backend') @@ -14,11 +18,22 @@ for (const element of profiles.split(" ")) { describe('Having MQTT protocol enbled and the following auth_backends: ' + backends, function () { let mqttOptions let expectations = [] +<<<<<<< HEAD let client_id = 'selenium-client' let rabbit = process.env.RABBITMQ_HOSTNAME || 'localhost' let username = process.env.RABBITMQ_AMQP_USERNAME let password = process.env.RABBITMQ_AMQP_PASSWORD +======= + let mqttProtocol = process.env.MQTT_PROTOCOL || 'mqtt' + let usemtls = process.env.MQTT_USE_MTLS || false + let rabbit = process.env.RABBITMQ_HOSTNAME || 'localhost' + let mqttUrl = process.env.RABBITMQ_MQTT_URL || "mqtt://" + rabbit + ":1883" + let username = process.env.RABBITMQ_AMQP_USERNAME + let password = process.env.RABBITMQ_AMQP_PASSWORD + let client_id = process.env.RABBITMQ_AMQP_USERNAME || 'selenium-client' + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) before(function () { if (backends.includes("http") && username.includes("http")) { reset() @@ -36,17 +51,39 @@ describe('Having MQTT protocol enbled and the following auth_backends: ' + backe mqttOptions = { clientId: client_id, protocolId: 'MQTT', +<<<<<<< HEAD protocolVersion: 4, keepalive: 10000, clean: false, reconnectPeriod: '1000', username: username, password: password, +======= + protocol: mqttProtocol, + protocolVersion: 4, + keepalive: 10000, + clean: false, + reconnectPeriod: '1000' + } + if (mqttProtocol == 'mqtts') { + mqttOptions["ca"] = [fs.readFileSync(process.env.RABBITMQ_CERTS + "/ca_rabbitmq_certificate.pem")] + } + if (usemtls) { + mqttOptions["cert"] = fs.readFileSync(process.env.RABBITMQ_CERTS + "/client_rabbitmq_certificate.pem") + mqttOptions["key"] = fs.readFileSync(process.env.RABBITMQ_CERTS + "/client_rabbitmq_key.pem") + } else { + mqttOptions["username"] = username + mqttOptions["password"] = password +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) } }) it('can open an MQTT connection', function () { +<<<<<<< HEAD var client = mqtt.connect("mqtt://" + rabbit + ":1883", mqttOptions) +======= + var client = mqtt.connect(mqttUrl, mqttOptions) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) client.on('error', function(err) { assert.fail("Mqtt connection failed due to " + err) client.end() diff --git a/selenium/test/authnz-msg-protocols/rabbitmq.auth-mtls.conf b/selenium/test/authnz-msg-protocols/rabbitmq.auth-mtls.conf new file mode 100644 index 000000000000..9f40857d94fb --- /dev/null +++ b/selenium/test/authnz-msg-protocols/rabbitmq.auth-mtls.conf @@ -0,0 +1,13 @@ + +auth_mechanisms.1 = EXTERNAL + +ssl_cert_login_from = subject_alternative_name +ssl_cert_login_san_type = dns +ssl_cert_login_san_index = 1 +ssl_options.verify = verify_peer +ssl_options.fail_if_no_peer_cert = true + +mqtt.ssl_cert_login = true +mqtt.ssl_cert_client_id_from = subject_alternative_name +mqtt.ssl_cert_login_san_type = dns +mqtt.ssl_cert_login_san_index = 1 diff --git a/selenium/test/authnz-msg-protocols/rabbitmq.tls.conf b/selenium/test/authnz-msg-protocols/rabbitmq.tls.conf new file mode 100644 index 000000000000..8478c874bf2f --- /dev/null +++ b/selenium/test/authnz-msg-protocols/rabbitmq.tls.conf @@ -0,0 +1,13 @@ + +listeners.ssl.1 = 5671 + +ssl_options.cacertfile = ${RABBITMQ_CERTS}/ca_rabbitmq_certificate.pem +ssl_options.certfile = ${RABBITMQ_CERTS}/server_rabbitmq_certificate.pem +ssl_options.keyfile = ${RABBITMQ_CERTS}/server_rabbitmq_key.pem + +management.ssl.port = 15671 +management.ssl.cacertfile = ${RABBITMQ_CERTS}/ca_rabbitmq_certificate.pem +management.ssl.certfile = ${RABBITMQ_CERTS}/server_rabbitmq_certificate.pem +management.ssl.keyfile = ${RABBITMQ_CERTS}/server_rabbitmq_key.pem + +mqtt.listeners.ssl.default = 8883 diff --git a/selenium/test/basic-auth/env.local b/selenium/test/basic-auth/env.local index 26cc7522d3b9..83ac24daea15 100644 --- a/selenium/test/basic-auth/env.local +++ b/selenium/test/basic-auth/env.local @@ -1 +1,5 @@ +<<<<<<< HEAD export IMPORT_DIR=deps/rabbitmq_management/selenium/test/basic-auth/imports +======= +export IMPORT_DIR=selenium/test/basic-auth/imports +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/basic-auth/imports/users.json b/selenium/test/basic-auth/imports/users.json index 83497198e066..830989525e29 100644 --- a/selenium/test/basic-auth/imports/users.json +++ b/selenium/test/basic-auth/imports/users.json @@ -70,6 +70,7 @@ "read": ".*" }, { +<<<<<<< HEAD "user": "guest", "vhost": "other", "configure": ".*", @@ -77,6 +78,8 @@ "read": ".*" }, { +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "user": "management", "vhost": "/", "configure": ".*", @@ -89,6 +92,23 @@ "configure": ".*", "write": ".*", "read": ".*" +<<<<<<< HEAD +======= + }, + { + "user": "rabbit_no_management", + "vhost": "other", + "configure": ".*", + "write": ".*", + "read": ".*" + }, + { + "user": "monitoring-only", + "vhost": "other", + "configure": ".*", + "write": ".*", + "read": ".*" +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) } ] diff --git a/selenium/test/basic-auth/rabbitmq.conf b/selenium/test/basic-auth/rabbitmq.conf index 7bacc14af27a..7c7b17ac4c20 100644 --- a/selenium/test/basic-auth/rabbitmq.conf +++ b/selenium/test/basic-auth/rabbitmq.conf @@ -1,6 +1,10 @@ auth_backends.1 = rabbit_auth_backend_internal management.login_session_timeout = 1 +<<<<<<< HEAD load_definitions = ${IMPORT_DIR}/users.json +======= +load_definitions = ${RABBITMQ_TEST_DIR}/imports/users.json +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) loopback_users = none diff --git a/selenium/test/connections/amqp10/sessions-for-monitoring-user.js b/selenium/test/connections/amqp10/sessions-for-monitoring-user.js index 0cd98f6ea08c..e640e8b7d66f 100644 --- a/selenium/test/connections/amqp10/sessions-for-monitoring-user.js +++ b/selenium/test/connections/amqp10/sessions-for-monitoring-user.js @@ -58,7 +58,10 @@ describe('Given an amqp10 connection opened, listed and clicked on it', function let sessions = await connectionPage.getSessions() assert.equal(1, sessions.sessions.length) let session = connectionPage.getSessionInfo(sessions.sessions, 0) +<<<<<<< HEAD //console.log("session: " + JSON.stringify(session)) +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) assert.equal(0, session.channelNumber) assert.equal(1, session.nextIncomingId) assert.equal(0, session.outgoingUnsettledDeliveries) @@ -70,20 +73,33 @@ describe('Given an amqp10 connection opened, listed and clicked on it', function assert.equal(1, sessions.outgoing_links.length) let incomingLink = connectionPage.getIncomingLinkInfo(sessions.incoming_links, 0) +<<<<<<< HEAD //console.log("incomingLink: " + JSON.stringify(incomingLink)) assert.equal(1, incomingLink.handle) assert.equal("sender-link", incomingLink.name) assert.equal("examples", incomingLink.targetAddress) +======= + assert.equal(1, incomingLink.handle) + assert.equal("sender-link", incomingLink.name) + assert.equal("my-queue", incomingLink.targetAddress) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) assert.equal("mixed", incomingLink.sndSettleMode) assert.equal("0", incomingLink.unconfirmedMessages) assert.equal(1, incomingLink.deliveryCount) let outgoingLink = connectionPage.getOutgoingLinkInfo(sessions.outgoing_links, 0) +<<<<<<< HEAD //console.log("outgoingLink: " + JSON.stringify(outgoingLink)) assert.equal(0, outgoingLink.handle) assert.equal("receiver-link", outgoingLink.name) assert.equal("examples", outgoingLink.sourceAddress) assert.equal("examples", outgoingLink.queueName) +======= + assert.equal(0, outgoingLink.handle) + assert.equal("receiver-link", outgoingLink.name) + assert.equal("my-queue", outgoingLink.sourceAddress) + assert.equal("my-queue", outgoingLink.queueName) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) assert.equal(false, outgoingLink.sendSettled) assert.equal("unlimited", outgoingLink.maxMessageSize) diff --git a/selenium/test/env.docker b/selenium/test/env.docker index 1d058b9f4e88..675a89c7c4b7 100644 --- a/selenium/test/env.docker +++ b/selenium/test/env.docker @@ -2,3 +2,7 @@ export RABBITMQ_SCHEME=http export RABBITMQ_HOSTNAME=rabbitmq export RABBITMQ_HOST=rabbitmq:15672 export IMPORT_DIR=/var/rabbitmq/imports +<<<<<<< HEAD +======= +export RABBITMQ_CERTS=/etc/rabbitmq/certs +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/env.local b/selenium/test/env.local index 8ec9aeac8fac..12af049e0cab 100644 --- a/selenium/test/env.local +++ b/selenium/test/env.local @@ -1,3 +1,8 @@ export RABBITMQ_SCHEME=http export RABBITMQ_HOSTNAME=localhost export RABBITMQ_HOST=localhost:15672 +<<<<<<< HEAD +======= +export RABBITMQ_CERTS=${TEST_CONFIG_PATH}/certs +export IMPORT_DIR=${TEST_CONFIG_PATH}/imports +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/env.tls.docker b/selenium/test/env.tls.docker index e598d14b7439..bafaac86c06b 100644 --- a/selenium/test/env.tls.docker +++ b/selenium/test/env.tls.docker @@ -1,3 +1,8 @@ export RABBITMQ_SCHEME=https export RABBITMQ_HOSTNAME=rabbitmq export RABBITMQ_HOST=rabbitmq:15671 +<<<<<<< HEAD +======= +export RABBITMQ_AMQP_SCHEME=amqps +export RABBITMQ_AMQP_PORT=5671 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/env.tls.local b/selenium/test/env.tls.local index e39b7b520c8a..81d17a0e33a8 100644 --- a/selenium/test/env.tls.local +++ b/selenium/test/env.tls.local @@ -1,3 +1,9 @@ export RABBITMQ_SCHEME=https export RABBITMQ_HOSTNAME=localhost export RABBITMQ_HOST=localhost:15671 +<<<<<<< HEAD +======= +export RABBITMQ_AMQP_SCHEME=amqps +export RABBITMQ_AMQP_PORT=5671 + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/exchanges/management.js b/selenium/test/exchanges/management.js index 3e772ad24d21..b037059253ff 100644 --- a/selenium/test/exchanges/management.js +++ b/selenium/test/exchanges/management.js @@ -36,9 +36,15 @@ describe('Exchange management', function () { }) it('list all default exchanges', async function () { +<<<<<<< HEAD actual_table = await exchanges.getExchangesTable(3) console.log("a :" + actual_table) expected_table = [ +======= + let actual_table = await exchanges.getExchangesTable(3) + + let expected_table = [ +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ["/", "(AMQP default)", "direct"], ["/", "amq.direct", "direct"], ["/", "amq.fanout", "fanout"], @@ -47,7 +53,11 @@ describe('Exchange management', function () { ["/", "amq.rabbitmq.event", "topic"], ["/", "amq.rabbitmq.trace", "topic"], ["/", "amq.topic", "topic"], +<<<<<<< HEAD +======= + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ["other", "(AMQP default)", "direct"], ["other", "amq.direct", "direct"], ["other", "amq.fanout", "fanout"], @@ -56,6 +66,10 @@ describe('Exchange management', function () { ["other", "amq.rabbitmq.trace", "topic"], ["other", "amq.topic", "topic"] ] +<<<<<<< HEAD +======= + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) console.log("e :" + actual_table) assert.deepEqual(actual_table, expected_table) }) diff --git a/selenium/test/multi-oauth/env.local b/selenium/test/multi-oauth/env.local index d61f528c4e4a..4db756bce16a 100644 --- a/selenium/test/multi-oauth/env.local +++ b/selenium/test/multi-oauth/env.local @@ -1 +1,5 @@ +<<<<<<< HEAD export OAUTH_SERVER_CONFIG_BASEDIR=deps/rabbitmq_management/selenium/test +======= +export OAUTH_SERVER_CONFIG_BASEDIR=${SELENIUM}/test +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/multi-oauth/env.local.devkeycloak b/selenium/test/multi-oauth/env.local.devkeycloak index 92044ba17881..a12c38b5710c 100644 --- a/selenium/test/multi-oauth/env.local.devkeycloak +++ b/selenium/test/multi-oauth/env.local.devkeycloak @@ -1,2 +1,6 @@ export DEVKEYCLOAK_URL=https://localhost:8442/realms/dev +<<<<<<< HEAD export DEVKEYCLOAK_CA_CERT=deps/rabbitmq_management/selenium/test/multi-oauth/devkeycloak/ca_prodkeycloak_certificate.pem +======= +export DEVKEYCLOAK_CA_CERT=${SELENIUM}/test/multi-oauth/devkeycloak/ca_devkeycloak_certificate.pem +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/multi-oauth/env.local.prodkeycloak b/selenium/test/multi-oauth/env.local.prodkeycloak index b8745aa8df98..e25487638645 100644 --- a/selenium/test/multi-oauth/env.local.prodkeycloak +++ b/selenium/test/multi-oauth/env.local.prodkeycloak @@ -1,2 +1,6 @@ export PRODKEYCLOAK_URL=https://localhost:8443/realms/prod +<<<<<<< HEAD export PRODKEYCLOAK_CA_CERT=deps/rabbitmq_management/selenium/test/multi-oauth/prodkeycloak/ca_prodkeycloak_certificate.pem +======= +export PRODKEYCLOAK_CA_CERT=${SELENIUM}/test/multi-oauth/prodkeycloak/ca_prodkeycloak_certificate.pem +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/multi-oauth/rabbitmq.tls.conf b/selenium/test/multi-oauth/rabbitmq.tls.conf index 7401d4079b1d..f61771559574 100644 --- a/selenium/test/multi-oauth/rabbitmq.tls.conf +++ b/selenium/test/multi-oauth/rabbitmq.tls.conf @@ -2,13 +2,25 @@ auth_backends.1 = rabbit_auth_backend_oauth2 listeners.ssl.1 = 5671 +<<<<<<< HEAD ssl_options.cacertfile = ${RABBITMQ_TEST_DIR}/certs/ca_rabbitmq_certificate.pem ssl_options.certfile = ${RABBITMQ_TEST_DIR}/certs/server_rabbitmq_certificate.pem ssl_options.keyfile = ${RABBITMQ_TEST_DIR}/certs/server_rabbitmq_key.pem +======= +ssl_options.cacertfile = ${RABBITMQ_CERTS}/ca_rabbitmq_certificate.pem +ssl_options.certfile = ${RABBITMQ_CERTS}/server_rabbitmq_certificate.pem +ssl_options.keyfile = ${RABBITMQ_CERTS}/server_rabbitmq_key.pem +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ssl_options.verify = verify_peer ssl_options.fail_if_no_peer_cert = true management.ssl.port = 15671 +<<<<<<< HEAD management.ssl.cacertfile = ${RABBITMQ_TEST_DIR}/certs/ca_rabbitmq_certificate.pem management.ssl.certfile = ${RABBITMQ_TEST_DIR}/certs/server_rabbitmq_certificate.pem management.ssl.keyfile = ${RABBITMQ_TEST_DIR}/certs/server_rabbitmq_key.pem +======= +management.ssl.cacertfile = ${RABBITMQ_CERTS}/ca_rabbitmq_certificate.pem +management.ssl.certfile = ${RABBITMQ_CERTS}/server_rabbitmq_certificate.pem +management.ssl.keyfile = ${RABBITMQ_CERTS}/server_rabbitmq_key.pem +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/oauth/env.docker.fakeportal b/selenium/test/oauth/env.docker.fakeportal index fc6d56f47b3a..9789e1700501 100644 --- a/selenium/test/oauth/env.docker.fakeportal +++ b/selenium/test/oauth/env.docker.fakeportal @@ -1,3 +1,7 @@ export FAKEPORTAL_URL=http://fakeportal:3000 export RABBITMQ_HOST_FOR_FAKEPORTAL=${RABBITMQ_HOST} +<<<<<<< HEAD export UAA_URL_FOR_FAKEPORTAL=http://uaa:8080 +======= +export UAA_URL_FOR_FAKEPORTAL=https://uaa:8443 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/oauth/env.docker.fakeproxy b/selenium/test/oauth/env.docker.fakeproxy index 37d1e5eccd9f..2d4541b98159 100644 --- a/selenium/test/oauth/env.docker.fakeproxy +++ b/selenium/test/oauth/env.docker.fakeproxy @@ -1,4 +1,8 @@ export FAKEPROXY_URL=http://fakeproxy:9090 +<<<<<<< HEAD export UAA_URL_FOR_FAKEPROXY=http://uaa:8080 +======= +export UAA_URL_FOR_FAKEPROXY=https://uaa:8443 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) export RABBITMQ_HOST_FOR_FAKEPROXY=${RABBITMQ_HOST} export PUBLIC_RABBITMQ_HOST=fakeproxy:9090 diff --git a/selenium/test/oauth/env.docker.uaa b/selenium/test/oauth/env.docker.uaa index afc439185290..151f35909c6b 100644 --- a/selenium/test/oauth/env.docker.uaa +++ b/selenium/test/oauth/env.docker.uaa @@ -1 +1,5 @@ +<<<<<<< HEAD export UAA_URL=http://uaa:8080 +======= +export UAA_URL=https://uaa:8443 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/oauth/env.local b/selenium/test/oauth/env.local index 80cfe7430e52..3984f3e0bbc3 100644 --- a/selenium/test/oauth/env.local +++ b/selenium/test/oauth/env.local @@ -1 +1,5 @@ +<<<<<<< HEAD export OAUTH_SERVER_CONFIG_BASEDIR=selenium/test +======= +export OAUTH_SERVER_CONFIG_BASEDIR=${SELENIUM}/test +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/oauth/env.local.fakeportal b/selenium/test/oauth/env.local.fakeportal index 520c2ce34c42..c91b678b31a2 100644 --- a/selenium/test/oauth/env.local.fakeportal +++ b/selenium/test/oauth/env.local.fakeportal @@ -1,3 +1,7 @@ export FAKEPORTAL_URL=http://localhost:3000 export RABBITMQ_HOST_FOR_FAKEPORTAL=localhost:15672 +<<<<<<< HEAD export UAA_URL_FOR_FAKEPORTAL=http://host.docker.internal:8080 +======= +export UAA_URL_FOR_FAKEPORTAL=https://uaa:8443 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/oauth/env.local.keycloak b/selenium/test/oauth/env.local.keycloak index e691790c25d7..364086c6023d 100644 --- a/selenium/test/oauth/env.local.keycloak +++ b/selenium/test/oauth/env.local.keycloak @@ -1,3 +1,7 @@ export KEYCLOAK_URL=https://localhost:8443/realms/test export OAUTH_PROVIDER_URL=https://localhost:8443/realms/test +<<<<<<< HEAD export OAUTH_PROVIDER_CA_CERT=deps/rabbitmq_management/selenium/test/oauth/keycloak/ca_keycloak_certificate.pem +======= +export OAUTH_PROVIDER_CA_CERT=selenium/test/oauth/keycloak/ca_keycloak_certificate.pem +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/oauth/env.local.uaa b/selenium/test/oauth/env.local.uaa index 40d8bf716099..ffa7f2e5ae54 100644 --- a/selenium/test/oauth/env.local.uaa +++ b/selenium/test/oauth/env.local.uaa @@ -1 +1,5 @@ +<<<<<<< HEAD export UAA_URL=http://localhost:8080 +======= +export UAA_URL=https://localhost:8443 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/oauth/keycloak/openssl.cnf.in b/selenium/test/oauth/keycloak/openssl.cnf.in new file mode 100644 index 000000000000..5ac3282046c5 --- /dev/null +++ b/selenium/test/oauth/keycloak/openssl.cnf.in @@ -0,0 +1,3 @@ +[ client_alt_names ] +email.1 = rabbit_client@localhost +URI.1 = rabbit_client_id_uri diff --git a/selenium/test/oauth/rabbitmq.conf b/selenium/test/oauth/rabbitmq.conf index 02b0227d4bf8..5bcdb52fba3b 100644 --- a/selenium/test/oauth/rabbitmq.conf +++ b/selenium/test/oauth/rabbitmq.conf @@ -10,6 +10,10 @@ auth_oauth2.resource_server_id = rabbitmq auth_oauth2.preferred_username_claims.1 = user_name auth_oauth2.preferred_username_claims.2 = preferred_username auth_oauth2.preferred_username_claims.3 = email +<<<<<<< HEAD +======= +auth_oauth2.preferred_username_claims.4 = sub +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) loopback_users = none diff --git a/selenium/test/oauth/rabbitmq.keycloak-mgt-oauth-provider.conf b/selenium/test/oauth/rabbitmq.keycloak-mgt-oauth-provider.conf index 9e6e55f94073..57400123e9bb 100644 --- a/selenium/test/oauth/rabbitmq.keycloak-mgt-oauth-provider.conf +++ b/selenium/test/oauth/rabbitmq.keycloak-mgt-oauth-provider.conf @@ -1,2 +1,6 @@ # uaa requires a secret in order to renew tokens management.oauth_provider_url = ${KEYCLOAK_URL} +<<<<<<< HEAD +======= +management.oauth_authorization_endpoint_params.resource = rabbitmq +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/oauth/rabbitmq.tls.conf b/selenium/test/oauth/rabbitmq.tls.conf index 7401d4079b1d..f61771559574 100644 --- a/selenium/test/oauth/rabbitmq.tls.conf +++ b/selenium/test/oauth/rabbitmq.tls.conf @@ -2,13 +2,25 @@ auth_backends.1 = rabbit_auth_backend_oauth2 listeners.ssl.1 = 5671 +<<<<<<< HEAD ssl_options.cacertfile = ${RABBITMQ_TEST_DIR}/certs/ca_rabbitmq_certificate.pem ssl_options.certfile = ${RABBITMQ_TEST_DIR}/certs/server_rabbitmq_certificate.pem ssl_options.keyfile = ${RABBITMQ_TEST_DIR}/certs/server_rabbitmq_key.pem +======= +ssl_options.cacertfile = ${RABBITMQ_CERTS}/ca_rabbitmq_certificate.pem +ssl_options.certfile = ${RABBITMQ_CERTS}/server_rabbitmq_certificate.pem +ssl_options.keyfile = ${RABBITMQ_CERTS}/server_rabbitmq_key.pem +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ssl_options.verify = verify_peer ssl_options.fail_if_no_peer_cert = true management.ssl.port = 15671 +<<<<<<< HEAD management.ssl.cacertfile = ${RABBITMQ_TEST_DIR}/certs/ca_rabbitmq_certificate.pem management.ssl.certfile = ${RABBITMQ_TEST_DIR}/certs/server_rabbitmq_certificate.pem management.ssl.keyfile = ${RABBITMQ_TEST_DIR}/certs/server_rabbitmq_key.pem +======= +management.ssl.cacertfile = ${RABBITMQ_CERTS}/ca_rabbitmq_certificate.pem +management.ssl.certfile = ${RABBITMQ_CERTS}/server_rabbitmq_certificate.pem +management.ssl.keyfile = ${RABBITMQ_CERTS}/server_rabbitmq_key.pem +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/selenium/test/oauth/uaa/server.xml b/selenium/test/oauth/uaa/server.xml new file mode 100644 index 000000000000..f86407ddf87a --- /dev/null +++ b/selenium/test/oauth/uaa/server.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/selenium/test/oauth/uaa/uaa.yml b/selenium/test/oauth/uaa/uaa.yml index 546a78402f2a..df6aca852f62 100644 --- a/selenium/test/oauth/uaa/uaa.yml +++ b/selenium/test/oauth/uaa/uaa.yml @@ -1,3 +1,9 @@ +<<<<<<< HEAD +======= +require_https: true +https_port: 8443 + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) logging: config: /uaa/log4j2.properties diff --git a/selenium/test/pageobjects/BasePage.js b/selenium/test/pageobjects/BasePage.js index e4a6da0fb81c..4e21ed8afaa6 100644 --- a/selenium/test/pageobjects/BasePage.js +++ b/selenium/test/pageobjects/BasePage.js @@ -125,9 +125,16 @@ module.exports = class BasePage { } +<<<<<<< HEAD async getTable(locator, firstNColumns) { const table = await this.waitForDisplayed(locator) const rows = await table.findElements(By.css('tbody tr')) +======= + async getTable(tableLocator, firstNColumns, rowClass) { + const table = await this.waitForDisplayed(tableLocator) + const rows = await table.findElements(rowClass == undefined ? + By.css('tbody tr') : By.css('tbody tr.' + rowClass)) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) let table_model = [] for (let row of rows) { let columns = await row.findElements(By.css('td')) diff --git a/selenium/test/pageobjects/ConnectionPage.js b/selenium/test/pageobjects/ConnectionPage.js new file mode 100644 index 000000000000..66e396afbc86 --- /dev/null +++ b/selenium/test/pageobjects/ConnectionPage.js @@ -0,0 +1,65 @@ +const { By, Key, until, Builder } = require('selenium-webdriver') + +const BasePage = require('./BasePage') + + +const OVERVIEW_SECTION = By.css('div#main div.section#connection-overview-section') +const SESSIONS_SECTION = By.css('div#main div.section#connection-sessions-section') +const SESSIONS_TABLE = By.css('div.section#connection-sessions-section table.list#sessions') +const INCOMING_LINKS_TABLE = By.css('div.section#connection-sessions-section table.list#incoming-links') +const OUTCOMING_LINKS_TABLE = By.css('div.section#connection-sessions-section table.list#outgoing-links') +const CONNECTION_NAME = By.css('div#main h2') + + +module.exports = class ConnectionPage extends BasePage { + async isLoaded() { + return this.waitForDisplayed(CONNECTION_NAME) + } + async getName() { + return this.getText(CONNECTION_NAME) + } + async getSessions() { + await this.waitForDisplayed(SESSIONS_SECTION) + return { + sessions : await this.getTable(SESSIONS_TABLE, 100, "session"), + incoming_links : await this.getTable(INCOMING_LINKS_TABLE, 100, "link"), + outgoing_links : await this.getTable(OUTCOMING_LINKS_TABLE, 100, "link") + } + } + getSessionInfo(sessions, index) { + return { + channelNumber: sessions[index][0], + handleMax: sessions[index][1], + nextIncomingId: sessions[index][2], + incomingWindow: sessions[index][3], + nextOutgoingId: sessions[index][4], + remoteIncomingWindow: sessions[index][5], + remoteOutgoingWindow: sessions[index][6], + outgoingUnsettledDeliveries: sessions[index][7] + } + } + getIncomingLinkInfo(links, index) { + return { + handle: links[index][0], + name: links[index][1], + targetAddress: links[index][2], + sndSettleMode: links[index][3], + maxMessageSize: Number(links[index][4]), + deliveryCount: Number(links[index][5]), + linkCredit: Number(links[index][6]), + unconfirmedMessages: Number(links[index][7]) + } + } + getOutgoingLinkInfo(links, index) { + return { + handle: links[index][0], + name: links[index][1], + sourceAddress: links[index][2], + queueName: links[index][3], + sendSettled: links[index][4] == "●" ? true : false, + maxMessageSize: links[index][5], + deliveryCount: Number(links[index][6]), + linkCredit: Number(links[index][7]) + } + } +} diff --git a/selenium/test/pageobjects/ConnectionsPage.js b/selenium/test/pageobjects/ConnectionsPage.js new file mode 100644 index 000000000000..0ef6a0b82c48 --- /dev/null +++ b/selenium/test/pageobjects/ConnectionsPage.js @@ -0,0 +1,25 @@ +const { By, Key, until, Builder } = require('selenium-webdriver') + +const BasePage = require('./BasePage') + + +const PAGING_SECTION = By.css('div#connections-paging-section') +const PAGING_SECTION_HEADER = By.css('div#connections-paging-section h2') + +const TABLE_SECTION = By.css('div#connections-table-section table') + +module.exports = class ConnectionsPage extends BasePage { + async isLoaded () { + return this.waitForDisplayed(PAGING_SECTION) + } + async getPagingSectionHeaderText() { + return this.getText(PAGING_SECTION_HEADER) + } + async getConnectionsTable(firstNColumns) { + return this.getTable(TABLE_SECTION, firstNColumns) + } + async clickOnConnection(index) { + return this.click(By.css( + "div#connections-table-section table tbody tr td:nth-child(" + index + ")")) + } +} diff --git a/selenium/test/pageobjects/OverviewPage.js b/selenium/test/pageobjects/OverviewPage.js index 59eb0758a255..f37866d1d821 100644 --- a/selenium/test/pageobjects/OverviewPage.js +++ b/selenium/test/pageobjects/OverviewPage.js @@ -26,6 +26,7 @@ module.exports = class OverviewPage extends BasePage { } async downloadBrokerDefinitions(filename) { return this.click(DOWNLOAD_DEFINITIONS_SECTION) +<<<<<<< HEAD /* await this.driver.sleep(1000) @@ -33,5 +34,7 @@ module.exports = class OverviewPage extends BasePage { await this.click(DOWNLOAD_BROKER_FILE) return driver.sleep(5000); */ +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) } }
<%= fmt_sort('Name', 'name') %>Specificities<%= fmt_sort('State', 'state') %> Description
<%= fmt_string(feature_flag.name) %> + <% if (feature_flag.callbacks.includes('enable')) { %> + + This feature flags has a migration function which might take some time and consume resources. + + + <% } %> + <% if (feature_flag.stability == 'experimental') { %> + + This is an experimental feature flag + + + <% } %> + <% if (feature_flag.experiment_level == 'unsupported') { %> + + This experimental feature flag is not yet supported at this stage and an upgrade path is not guaranteed + + + <% } %> + + + checked disabled + <% } %> + <% if (feature_flag.state == 'state_changing') { %> + disabled + <% } %> + onchange='handle_feature_flag(this, "<%= feature_flag.name %>");'/> +

<%= fmt_string(feature_flag.desc) %>

@@ -152,3 +464,62 @@ These flags can be enabled in production deployments after an appropriate amount +<<<<<<< HEAD +======= + + + + +

Enabling an experimental feature flag

+

+ The feature flag is experimental. + This means the functionality behind it is still a work in progress. Here + are a few important things to keep in mind: +

+
    +
  1. +

    + Before enabling it, make sure to try it in a test environment + first before enabling it in production. +

    +

    + The feature flag is supported even though it is still experimental. + Therefore, upgrades to a later version of RabbitMQ with this feature flag + enabled are supported. +

    +

    + + +

    +
  2. +
  3. +

    + This development of this feature is at an early stage. Support is not + provided and enabling it in production is not recommended. +

    +

    + Once it is enabled, upgrades to a future version of RabbitMQ is not + guaranteed! If there is no upgrade path, you will have to use a + blue-green migration + to upgrade RabbitMQ. +

    +

    + + +

    +

    + + +

    +
  4. +
  5. + If you enable it, + please give feedback, + this will help the RabbitMQ team polish it and make it stable as soon as + possible. +
  6. +
+ + +
+>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/sessions-list.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/sessions-list.ejs new file mode 100644 index 000000000000..1bd9558cdac4 --- /dev/null +++ b/deps/rabbitmq_management/priv/www/js/tmpl/sessions-list.ejs @@ -0,0 +1,112 @@ +<% if (sessions.length > 0) { %> + + + + + + + + + + + + + + + +<% + for (var i = 0; i < sessions.length; i++) { + var session = sessions[i]; +%> + + + + + + + + + + +<% if (session.incoming_links.length > 0) { %> + + + +<% } %> +<% if (session.outgoing_links.length > 0) { %> + + + +<% } %> +<% } %> + +
Channel numberhandle-maxnext-incoming-idincoming-windownext-outgoing-idremote-incoming-windowremote-outgoing-windowOutgoing unsettled deliveries
<%= fmt_string(session.channel_number) %><%= fmt_string(session.handle_max) %><%= fmt_string(session.next_incoming_id) %><%= fmt_string(session.incoming_window) %><%= fmt_string(session.next_outgoing_id) %><%= fmt_string(session.remote_incoming_window) %><%= fmt_string(session.remote_outgoing_window) %><%= fmt_string(session.outgoing_unsettled_deliveries) %>
+

Incoming Links (<%=(session.incoming_links.length)%>)

+ + + + + + + + + + + + + + +<% + for (var j = 0; j < session.incoming_links.length; j++) { + var in_link = session.incoming_links[j]; +%> + + + + + + + + + + +<% } %> + + +
+

Outgoing Links (<%=(session.outgoing_links.length)%>)

+ + + + + + + + + + + + + + +<% + for (var k = 0; k < session.outgoing_links.length; k++) { + var out_link = session.outgoing_links[k]; +%> + + + + + + + + + + +<% } %> + + +
+<% } else { %> +

No sessions

+<% } %> diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl b/deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl index c0bf84ce23b7..41b03a06b1cc 100644 --- a/deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl +++ b/deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl @@ -132,6 +132,10 @@ dispatcher() -> {"/connections/:connection", rabbit_mgmt_wm_connection, []}, {"/connections/username/:username", rabbit_mgmt_wm_connection_user_name, []}, {"/connections/:connection/channels", rabbit_mgmt_wm_connection_channels, []}, +<<<<<<< HEAD +======= + {"/connections/:connection/sessions", rabbit_mgmt_wm_connection_sessions, []}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {"/channels", rabbit_mgmt_wm_channels, []}, {"/channels/:channel", rabbit_mgmt_wm_channel, []}, {"/consumers", rabbit_mgmt_wm_consumers, []}, diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_schema.erl b/deps/rabbitmq_management/src/rabbit_mgmt_schema.erl new file mode 100644 index 000000000000..19e973a47748 --- /dev/null +++ b/deps/rabbitmq_management/src/rabbit_mgmt_schema.erl @@ -0,0 +1,67 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(rabbit_mgmt_schema). + + +-export([ + translate_oauth_resource_servers/1, + translate_endpoint_params/2 +]). + +extract_key_as_binary({Name,_}) -> list_to_binary(Name). +extract_value({_Name,V}) -> V. + +-spec translate_oauth_resource_servers([{list(), binary()}]) -> map(). +translate_oauth_resource_servers(Conf) -> + Settings = cuttlefish_variable:filter_by_prefix( + "management.oauth_resource_servers", Conf), + Map = merge_list_of_maps([ + extract_resource_server_properties(Settings), + extract_resource_server_endpoint_params(oauth_authorization_endpoint_params, Settings), + extract_resource_server_endpoint_params(oauth_token_endpoint_params, Settings) + ]), + Map0 = maps:map(fun(K,V) -> + case proplists:get_value(id, V) of + undefined -> V ++ [{id, K}]; + _ -> V + end end, Map), + ResourceServers = maps:values(Map0), + lists:foldl(fun(Elem,AccMap)-> maps:put(proplists:get_value(id, Elem), Elem, AccMap) end, #{}, + ResourceServers). + +-spec translate_endpoint_params(list(), [{list(), binary()}]) -> [{binary(), binary()}]. +translate_endpoint_params(Variable, Conf) -> + Params0 = cuttlefish_variable:filter_by_prefix("management." ++ Variable, Conf), + [{list_to_binary(Param), list_to_binary(V)} || {["management", _, Param], V} <- Params0]. + +merge_list_of_maps(ListOfMaps) -> + lists:foldl(fun(Elem, AccIn) -> maps:merge_with(fun(_K,V1,V2) -> V1 ++ V2 end, + Elem, AccIn) end, #{}, ListOfMaps). + +convert_list_to_binary(V) when is_list(V) -> + list_to_binary(V); +convert_list_to_binary(V) -> + V. + +extract_resource_server_properties(Settings) -> + KeyFun = fun extract_key_as_binary/1, + ValueFun = fun extract_value/1, + + OAuthResourceServers = [{Name, {list_to_atom(Key), convert_list_to_binary(V)}} + || {["management","oauth_resource_servers", Name, Key], V} <- Settings ], + maps:groups_from_list(KeyFun, ValueFun, OAuthResourceServers). + + +extract_resource_server_endpoint_params(Variable, Settings) -> + KeyFun = fun extract_key_as_binary/1, + + IndexedParams = [{Name, {list_to_binary(ParamName), list_to_binary(V)}} || + {["management","oauth_resource_servers", Name, EndpointVar, ParamName], V} + <- Settings, EndpointVar == atom_to_list(Variable) ], + maps:map(fun(_K,V)-> [{Variable, V}] end, + maps:groups_from_list(KeyFun, fun({_, V}) -> V end, IndexedParams)). diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl index cc3f0b3f486f..d3e61acfce8c 100644 --- a/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl +++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl @@ -23,6 +23,7 @@ variances(Req, Context) -> {[<<"accept-encoding">>, <<"origin">>], Req, Context}. content_types_provided(ReqData, Context) -> +<<<<<<< HEAD {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}. merge_oauth_provider_info(OAuthResourceServer, MgtResourceServer, ManagementProps) -> @@ -141,6 +142,203 @@ filter_empty_properties(ListOfProperties) -> end end, ListOfProperties). +======= + {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}. + +merge_property(Key, List, MapIn) -> + case proplists:get_value(Key, List) of + undefined -> MapIn; + V0 -> MapIn#{Key => V0} + end. + +extract_oauth_provider_info_props_as_map(ManagementProps) -> + lists:foldl(fun(K, Acc) -> + merge_property(K, ManagementProps, Acc) end, #{}, + [oauth_provider_url, + oauth_metadata_url, + oauth_authorization_endpoint_params, + oauth_token_endpoint_params]). + +merge_oauth_provider_info(OAuthResourceServer, MgtResourceServer, + ManagementProps) -> + OAuthProviderResult = + case proplists:get_value(oauth_provider_id, OAuthResourceServer) of + undefined -> + oauth2_client:get_oauth_provider([issuer]); + OauthProviderId -> + oauth2_client:get_oauth_provider(OauthProviderId, [issuer]) + end, + OAuthProviderInfo0 = + case OAuthProviderResult of + {ok, OAuthProvider} -> oauth_provider_to_map(OAuthProvider); + {error, _} -> #{} + end, + OAuthProviderInfo1 = maps:merge(OAuthProviderInfo0, + extract_oauth_provider_info_props_as_map(ManagementProps)), + maps:merge(OAuthProviderInfo1, proplists:to_map(MgtResourceServer)). + +oauth_provider_to_map(OAuthProvider) -> + % only include issuer and end_session_endpoint for now. + % The other endpoints are resolved by oidc-client library + Map0 = case OAuthProvider#oauth_provider.issuer of + undefined -> + #{}; + Issuer -> + #{ + oauth_provider_url => Issuer, + oauth_metadata_url => + OAuthProvider#oauth_provider.discovery_endpoint + } + end, + case OAuthProvider#oauth_provider.end_session_endpoint of + undefined -> Map0; + V -> maps:put(end_session_endpoint, V, Map0) + end. + +skip_unknown_mgt_resource_servers(ManagementProps, OAuth2Resources) -> + maps:filter(fun(Key, _Value) -> maps:is_key(Key, OAuth2Resources) end, + proplists:get_value(oauth_resource_servers, ManagementProps, #{})). +skip_disabled_mgt_resource_servers(MgtOauthResources) -> + maps:filter(fun(_Key, Value) -> + not proplists:get_value(disabled, Value, false) end, + MgtOauthResources). + +extract_oauth2_and_mgt_resources(OAuth2BackendProps, ManagementProps) -> + OAuth2Resources = getAllDeclaredOauth2Resources(OAuth2BackendProps), + MgtResources0 = skip_unknown_mgt_resource_servers(ManagementProps, + OAuth2Resources), + MgtResources1 = maps:merge(maps:filtermap(fun(K,_V) -> + case maps:is_key(K, MgtResources0) of + true -> false; + false -> {true, [{id, K}]} + end end, OAuth2Resources), MgtResources0), + MgtResources = maps:map( + fun(K,V) -> merge_oauth_provider_info( + maps:get(K, OAuth2Resources, #{}), V, ManagementProps) end, + skip_disabled_mgt_resource_servers(MgtResources1)), + case maps:size(MgtResources) of + 0 -> {}; + _ -> {MgtResources} + end. + +getAllDeclaredOauth2Resources(OAuth2BackendProps) -> + OAuth2Resources = proplists:get_value(resource_servers, OAuth2BackendProps, + #{}), + case proplists:get_value(resource_server_id, OAuth2BackendProps) of + undefined -> + OAuth2Resources; + Id -> + maps:put(Id, buildRootResourceServerIfAny(Id, OAuth2BackendProps), + OAuth2Resources) + end. +buildRootResourceServerIfAny(Id, Props) -> + [ + {id, Id}, + {oauth_provider_id, proplists:get_value(oauth_provider_id, Props)} + ]. + +authSettings() -> + ManagementProps = application:get_all_env(rabbitmq_management), + OAuth2BackendProps = application:get_all_env(rabbitmq_auth_backend_oauth2), + EnableOAUTH = proplists:get_value(oauth_enabled, ManagementProps, false), + case EnableOAUTH of + false -> [{oauth_enabled, false}]; + true -> + case extract_oauth2_and_mgt_resources(OAuth2BackendProps, + ManagementProps) of + {MgtResources} -> + produce_auth_settings(MgtResources, ManagementProps); + {} -> + [{oauth_enabled, false}] + end + end. + +% invalid -> those resources that dont have an oauth_client_id and +% their login_type is sp_initiated +skip_invalid_mgt_resource_servers(MgtResourceServers, ManagementProps) -> + DefaultOauthInitiatedLogonType = proplists:get_value( + oauth_initiated_logon_type, ManagementProps, sp_initiated), + maps:filter(fun(_K,ResourceServer) -> + SpInitiated = + case maps:get(oauth_initiated_logon_type, ResourceServer, + DefaultOauthInitiatedLogonType) of + sp_initiated -> true; + _ -> false + end, + not SpInitiated or not is_invalid([maps:get(oauth_client_id, + ResourceServer, undefined)]) + end, MgtResourceServers). + +% filter -> include only those resources with an oauth_client_id +% or those whose logon type is not sp_initiated +filter_out_invalid_mgt_resource_servers(MgtResourceServers, ManagementProps) -> + case is_invalid([proplists:get_value(oauth_client_id, ManagementProps)]) of + true -> + skip_invalid_mgt_resource_servers(MgtResourceServers, + ManagementProps); + false -> + MgtResourceServers + end. + +filter_mgt_resource_servers_without_oauth_provider_url(MgtResourceServers) -> + maps:filter(fun(_K1,V1) -> maps:is_key(oauth_provider_url, V1) end, + MgtResourceServers). + +ensure_oauth_resource_server_properties_are_binaries(Key, Value) -> + case Key of + oauth_authorization_endpoint_params -> Value; + oauth_token_endpoint_params -> Value; + _ -> to_binary(Value) + end. + +produce_auth_settings(MgtResourceServers, ManagementProps) -> + ConvertValuesToBinary = fun(_K,V) -> + [ + {K1, ensure_oauth_resource_server_properties_are_binaries(K1, V1)} + || {K1,V1} <- maps:to_list(V) + ] end, + FilteredMgtResourceServers = + filter_mgt_resource_servers_without_oauth_provider_url( + filter_out_invalid_mgt_resource_servers(MgtResourceServers, + ManagementProps)), + + case maps:size(FilteredMgtResourceServers) of + 0 -> + [{oauth_enabled, false}]; + _ -> + filter_empty_properties([ + {oauth_enabled, true}, + {oauth_resource_servers, + maps:map(ConvertValuesToBinary, FilteredMgtResourceServers)}, + to_tuple(oauth_disable_basic_auth, ManagementProps, + fun to_binary/1, true), + to_tuple(oauth_client_id, ManagementProps), + to_tuple(oauth_client_secret, ManagementProps), + to_tuple(oauth_scopes, ManagementProps), + case proplists:get_value(oauth_initiated_logon_type, + ManagementProps, sp_initiated) of + sp_initiated -> + {}; + idp_initiated -> + {oauth_initiated_logon_type, <<"idp_initiated">>} + end, + to_tuple(oauth_authorization_endpoint_params, ManagementProps, + undefined, undefined), + to_tuple(oauth_token_endpoint_params, ManagementProps, + undefined, undefined) + ]) + end. + +filter_empty_properties(ListOfProperties) -> + lists:filter(fun(Prop) -> + case Prop of + {} -> false; + _ -> true + end + end, ListOfProperties). + +to_binary(Value) when is_boolean(Value)-> Value; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) to_binary(Value) -> rabbit_data_coercion:to_binary(Value). to_json(ReqData, Context) -> @@ -158,9 +356,28 @@ is_invalid(List) -> end end, List). to_tuple(Key, Proplist) -> +<<<<<<< HEAD case proplists:is_defined(Key, Proplist) of true -> {Key, rabbit_data_coercion:to_binary(proplists:get_value(Key, Proplist))}; false -> {} end. to_tuple(Key, Proplist, DefaultValue) -> {Key, proplists:get_value(Key, Proplist, DefaultValue)}. +======= + to_tuple(Key, Proplist, fun to_binary/1, undefined). + +to_tuple(Key, Proplist, ConvertFun, DefaultValue) -> + case proplists:is_defined(Key, Proplist) of + true -> + {Key, case ConvertFun of + undefined -> proplists:get_value(Key, Proplist); + _ -> ConvertFun(proplists:get_value(Key, Proplist)) + end + }; + false -> + case DefaultValue of + undefined -> {}; + _ -> {Key, proplists:get_value(Key, Proplist, DefaultValue)} + end + end. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_connection_sessions.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_connection_sessions.erl new file mode 100644 index 000000000000..60768b20e136 --- /dev/null +++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_connection_sessions.erl @@ -0,0 +1,91 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(rabbit_mgmt_wm_connection_sessions). + +-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]). +-export([resource_exists/2]). +-export([variances/2]). + +-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +%%-------------------------------------------------------------------- + +init(Req, _State) -> + {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}. + +variances(Req, Context) -> + {[<<"accept-encoding">>, <<"origin">>], Req, Context}. + +content_types_provided(ReqData, Context) -> + {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}. + +resource_exists(ReqData, Context) -> + case conn(ReqData) of + not_found -> + {false, ReqData, Context}; + _Conn -> + {true, ReqData, Context} + end. + +to_json(ReqData, Context) -> + Conn = conn(ReqData), + case proplists:get_value(protocol, Conn) of + {1, 0} -> + ConnPid = proplists:get_value(pid, Conn), + try rabbit_amqp_reader:info(ConnPid, [session_pids]) of + [{session_pids, Pids}] -> + rabbit_mgmt_util:reply_list(session_infos(Pids), + ["channel_number"], + ReqData, + Context) + catch Type:Reason0 -> + Reason = unicode:characters_to_binary( + lists:flatten( + io_lib:format( + "failed to get sessions for connection ~p: ~s ~tp", + [ConnPid, Type, Reason0]))), + rabbit_mgmt_util:internal_server_error(Reason, ReqData, Context) + end; + _ -> + rabbit_mgmt_util:bad_request(<<"connection does not use AMQP 1.0">>, + ReqData, + Context) + end. + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_user(ReqData, Context, conn(ReqData)). + +%%-------------------------------------------------------------------- + +conn(Req) -> + case rabbit_connection_tracking:lookup(rabbit_mgmt_util:id(connection, Req)) of + #tracked_connection{name = Name, + pid = Pid, + protocol = Protocol, + username = Username} -> + [{name, Name}, + {pid, Pid}, + {protocol, Protocol}, + {user, Username}]; + not_found -> + not_found + end. + +session_infos(Pids) -> + lists:filtermap( + fun(Pid) -> + case rabbit_amqp_session:info(Pid) of + {ok, Infos} -> + {true, Infos}; + {error, Reason} -> + rabbit_log:warning("failed to get infos for session ~p: ~tp", + [Pid, Reason]), + false + end + end, Pids). diff --git a/deps/rabbitmq_management/test/clustering_prop_SUITE.erl b/deps/rabbitmq_management/test/clustering_prop_SUITE.erl index df27571f043a..9f9124864959 100644 --- a/deps/rabbitmq_management/test/clustering_prop_SUITE.erl +++ b/deps/rabbitmq_management/test/clustering_prop_SUITE.erl @@ -109,17 +109,29 @@ prop_connection_channel_counts(Config) -> {1, force_stats}])), begin % ensure we begin with no connections +<<<<<<< HEAD +======= + ct:pal("Init testcase"), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) true = validate_counts(Config, []), Cons = lists:foldl(fun (Op, Agg) -> execute_op(Config, Op, Agg) end, [], Ops), %% TODO retry a few times +<<<<<<< HEAD +======= + ct:pal("Check testcase"), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Res = retry_for( fun() -> force_stats(Config), validate_counts(Config, Cons) end, 60), +<<<<<<< HEAD cleanup(Cons), +======= + ct:pal("Cleanup testcase"), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbit_ct_helpers:await_condition( fun () -> cleanup(Cons), @@ -138,8 +150,21 @@ validate_counts(Config, Conns) -> Ch1 = length(http_get_from_node(Config, 0, "/channels")), Ch2 = length(http_get_from_node(Config, 1, "/channels")), Ch3 = length(http_get_from_node(Config, 2, "/channels")), +<<<<<<< HEAD [Expected, Expected, Expected, ChanCount, ChanCount, ChanCount] =:= [C1, C2, C3, Ch1, Ch2, Ch3]. +======= + Res = ([Expected, Expected, Expected, ChanCount, ChanCount, ChanCount] + =:= [C1, C2, C3, Ch1, Ch2, Ch3]), + case Res of + false -> + ct:pal("Validate counts connections: ~p channels: ~p got ~p", + [Expected, ChanCount, [C1, C2, C3, Ch1, Ch2, Ch3]]); + true -> + ok + end, + Res. +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) cleanup(Conns) -> diff --git a/deps/rabbitmq_management/test/config_schema_SUITE_data/rabbitmq_management.snippets b/deps/rabbitmq_management/test/config_schema_SUITE_data/rabbitmq_management.snippets index d26639620bb8..bd8eee506eb3 100644 --- a/deps/rabbitmq_management/test/config_schema_SUITE_data/rabbitmq_management.snippets +++ b/deps/rabbitmq_management/test/config_schema_SUITE_data/rabbitmq_management.snippets @@ -621,15 +621,33 @@ management.oauth_client_id = rabbitmq_client_code management.oauth_client_secret = rabbitmq_client_secret management.oauth_scopes = openid profile rabbitmq.* +<<<<<<< HEAD management.oauth_initiated_logon_type = idp_initiated", [ {rabbitmq_management, [ +======= + management.oauth_authorization_endpoint_params.param1 = value1 + management.oauth_token_endpoint_params.param2 = value2 + management.oauth_initiated_logon_type = idp_initiated", + [ + {rabbitmq_management, [ + {oauth_authorization_endpoint_params, [ + {<<"param1">>, <<"value1">>} + ]}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {oauth_enabled, true}, {oauth_provider_url, "http://localhost:8080"}, {oauth_client_id, "rabbitmq_client_code"}, {oauth_client_secret, "rabbitmq_client_secret"}, {oauth_scopes, "openid profile rabbitmq.*"}, +<<<<<<< HEAD {oauth_initiated_logon_type, idp_initiated} +======= + {oauth_initiated_logon_type, idp_initiated}, + {oauth_token_endpoint_params, [ + {<<"param2">>, <<"value2">>} + ]} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]} ], [rabbitmq_management] }, @@ -640,7 +658,13 @@ management.oauth_resource_servers.1.label = One management.oauth_resource_servers.1.oauth_client_id = one management.oauth_resource_servers.1.oauth_scopes = openid profile rabbitmq.* +<<<<<<< HEAD management.oauth_resource_servers.2.oauth_provider_url = http://two +======= + management.oauth_resource_servers.1.oauth_token_endpoint_params.param2 = value2 + management.oauth_resource_servers.2.oauth_provider_url = http://two + management.oauth_resource_servers.2.oauth_authorization_endpoint_params.param1 = value1 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) management.oauth_resource_servers.2.id = resource-two management.oauth_resource_servers.2.oauth_client_id = two management.oauth_resource_servers.3.oauth_initiated_logon_type = idp_initiated @@ -650,6 +674,7 @@ {oauth_enabled, true}, {oauth_resource_servers, #{ +<<<<<<< HEAD <<"resource-one">> => [ {oauth_scopes, "openid profile rabbitmq.*"}, {oauth_client_id, "one"}, @@ -665,6 +690,30 @@ <<"3">> => [ {oauth_initiated_logon_type, idp_initiated}, {oauth_provider_url, "http://three"} +======= + <<"3">> => [ + {oauth_provider_url, <<"http://three">>}, + {oauth_initiated_logon_type, idp_initiated}, + {id, <<"3">>} + ], + <<"resource-one">> => [ + {oauth_token_endpoint_params, [ + {<<"param2">>, <<"value2">>} + ]}, + {oauth_scopes, <<"openid profile rabbitmq.*">>}, + {oauth_client_id, <<"one">>}, + {label, <<"One">>}, + {id, <<"resource-one">>}, + {oauth_provider_url, <<"http://one:8080">>} + ], + <<"resource-two">> => [ + {oauth_authorization_endpoint_params, [ + {<<"param1">>, <<"value1">>} + ]}, + {oauth_client_id, <<"two">>}, + {id, <<"resource-two">>}, + {oauth_provider_url, <<"http://two">>} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ] } } diff --git a/deps/rabbitmq_management/test/js/.babelrc b/deps/rabbitmq_management/test/js/.babelrc new file mode 100644 index 000000000000..1320b9a3272a --- /dev/null +++ b/deps/rabbitmq_management/test/js/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-env"] +} diff --git a/deps/rabbitmq_management/test/js/package.json b/deps/rabbitmq_management/test/js/package.json new file mode 100644 index 000000000000..0748d98ba9c0 --- /dev/null +++ b/deps/rabbitmq_management/test/js/package.json @@ -0,0 +1,35 @@ +{ + "type":"module", + "dependencies": { + + + "json": "^11.0.0", + + + "mocha": "^10.7.3" + + }, + + "scripts": { + + + "test": "mocha --recursive --trace-warnings --require @babel/register" + + }, + + "devDependencies": { + + + "@babel/cli": "^7.25.6", + + + "@babel/core": "^7.25.2", + + + "@babel/preset-env": "^7.25.4", + + + "@babel/register": "^7.24.6" + + } +} diff --git a/deps/rabbitmq_management/test/js/test/oidc-oauth/helper.test.js b/deps/rabbitmq_management/test/js/test/oidc-oauth/helper.test.js new file mode 100644 index 000000000000..88431a0c9498 --- /dev/null +++ b/deps/rabbitmq_management/test/js/test/oidc-oauth/helper.test.js @@ -0,0 +1,22 @@ +const assert = require('assert') +import oidc_settings_from from '../../../../priv/www/js/oidc-oauth/helper.js' + +describe('oidc_settings_from', function () { + describe('single root resource', function () { + + describe('with minimum required settings', function () { + var resource = { + oauth_client_id : "some-client", + oauth_provider_url : "https://someurl", + oauth_metadata_url : "https://someurl/extra" + } + var oidc_settings = oidc_settings_from(resource) + + it('oidc_settings should have client_id ', function () { + assert.equal(resource.oauth_provider_url, oidc_settings.authority) + assert.equal(resource.oauth_metadata_url, oidc_settings.metadataUrl) + assert.equal(resource.oauth_client_id, oidc_settings.client_id) + }) + }) + }) +}) \ No newline at end of file diff --git a/deps/rabbitmq_management/test/rabbit_mgmt_http_SUITE.erl b/deps/rabbitmq_management/test/rabbit_mgmt_http_SUITE.erl index b63828d8b729..b065aabdc620 100644 --- a/deps/rabbitmq_management/test/rabbit_mgmt_http_SUITE.erl +++ b/deps/rabbitmq_management/test/rabbit_mgmt_http_SUITE.erl @@ -144,8 +144,11 @@ all_tests() -> [ permissions_validation_test, permissions_list_test, permissions_test, +<<<<<<< HEAD connections_test_amqpl, connections_test_amqp, +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) multiple_invalid_connections_test, quorum_queues_test, stream_queues_have_consumers_field, @@ -212,6 +215,14 @@ all_tests() -> [ qq_status_test, list_deprecated_features_test, list_used_deprecated_features_test, +<<<<<<< HEAD +======= + connections_amqpl, + connections_amqp, + amqp_sessions, + amqpl_sessions, + enable_plugin_amqp, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) cluster_and_node_tags_test, version_test ]. @@ -250,17 +261,26 @@ finish_init(Group, Config) -> merge_app_env(Config1). init_per_suite(Config) -> +<<<<<<< HEAD {ok, _} = application:ensure_all_started(amqp10_client), +======= + {ok, _} = application:ensure_all_started(rabbitmq_amqp_client), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Config. end_per_suite(Config) -> Config. +<<<<<<< HEAD init_per_group(Group, Config0) when Group == all_tests_with_prefix -> +======= +init_per_group(all_tests_with_prefix=Group, Config0) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) PathConfig = {rabbitmq_management, [{path_prefix, ?PATH_PREFIX}]}, Config1 = rabbit_ct_helpers:merge_app_env(Config0, PathConfig), Config2 = finish_init(Group, Config1), start_broker(Config2); +<<<<<<< HEAD init_per_group(Group, Config0) when Group == default_queue_type_group_tests -> case rabbit_ct_helpers:is_mixed_versions() of true -> @@ -269,6 +289,8 @@ init_per_group(Group, Config0) when Group == default_queue_type_group_tests -> Config1 = finish_init(Group, Config0), start_broker(Config1) end; +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) init_per_group(Group, Config0) -> Config1 = finish_init(Group, Config0), start_broker(Config1). @@ -317,6 +339,7 @@ init_per_testcase(queues_detailed_test, Config) -> true -> Config; false -> {skip, "The detailed queues endpoint is not available."} end; +<<<<<<< HEAD init_per_testcase(Testcase, Config) when Testcase == definitions_file_metadata_test -> case rabbit_ct_helpers:is_mixed_versions() of true -> @@ -324,6 +347,8 @@ init_per_testcase(Testcase, Config) when Testcase == definitions_file_metadata_t _ -> rabbit_ct_helpers:testcase_started(Config, Testcase) end; +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) init_per_testcase(Testcase, Config) -> rabbit_ct_broker_helpers:close_all_connections(Config, 0, <<"rabbit_mgmt_SUITE:init_per_testcase">>), rabbit_ct_helpers:testcase_started(Config, Testcase). @@ -1022,7 +1047,11 @@ topic_permissions_test(Config) -> http_delete(Config, "/vhosts/myvhost2", {group, '2xx'}), passed. +<<<<<<< HEAD connections_test_amqpl(Config) -> +======= +connections_amqpl(Config) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {Conn, _Ch} = open_connection_and_channel(Config), LocalPort = local_port(Conn), Path = binary_to_list( @@ -1055,7 +1084,11 @@ connections_test_amqpl(Config) -> passed. %% Test that AMQP 1.0 connection can be listed and closed via the rabbitmq_management plugin. +<<<<<<< HEAD connections_test_amqp(Config) -> +======= +connections_amqp(Config) -> +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Node = atom_to_binary(rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename)), Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp), User = <<"guest">>, @@ -1112,6 +1145,153 @@ connections_test_amqp(Config) -> eventually(?_assertEqual([], http_get(Config, "/connections")), 10, 5), ?assertEqual(0, length(rpc(Config, rabbit_amqp1_0, list_local, []))). +<<<<<<< HEAD +======= +%% Test that AMQP 1.0 sessions and links can be listed via the rabbitmq_management plugin. +amqp_sessions(Config) -> + Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp), + User = <<"guest">>, + OpnConf = #{address => ?config(rmq_hostname, Config), + port => Port, + container_id => <<"my container">>, + sasl => {plain, User, <<"guest">>}}, + {ok, C} = amqp10_client:open_connection(OpnConf), + receive {amqp10_event, {connection, C, opened}} -> ok + after 5000 -> ct:fail(opened_timeout) + end, + + {ok, Session1} = amqp10_client:begin_session_sync(C), + {ok, LinkPair} = rabbitmq_amqp_client:attach_management_link_pair_sync( + Session1, <<"my link pair">>), + QName = <<"my queue">>, + {ok, #{}} = rabbitmq_amqp_client:declare_queue(LinkPair, QName, #{}), + {ok, Sender} = amqp10_client:attach_sender_link_sync( + Session1, + <<"my sender">>, + rabbitmq_amqp_address:exchange(<<"amq.direct">>, <<"my key">>)), + {ok, Receiver} = amqp10_client:attach_receiver_link( + Session1, + <<"my receiver">>, + rabbitmq_amqp_address:queue(QName)), + receive {amqp10_event, {link, Receiver, attached}} -> ok + after 5000 -> ct:fail({missing_event, ?LINE}) + end, + ok = amqp10_client:flow_link_credit(Receiver, 5000, never), + + eventually(?_assertEqual(1, length(http_get(Config, "/connections"))), 1000, 10), + [Connection] = http_get(Config, "/connections"), + ConnectionName = maps:get(name, Connection), + Path = "/connections/" ++ binary_to_list(uri_string:quote(ConnectionName)) ++ "/sessions", + [Session] = http_get(Config, Path), + ?assertMatch( + #{channel_number := 0, + handle_max := HandleMax, + next_incoming_id := NextIncomingId, + incoming_window := IncomingWindow, + next_outgoing_id := NextOutgoingId, + remote_incoming_window := RemoteIncomingWindow, + remote_outgoing_window := RemoteOutgoingWindow, + outgoing_unsettled_deliveries := 0, + incoming_links := [#{handle := 0, + link_name := <<"my link pair">>, + target_address := <<"/management">>, + delivery_count := DeliveryCount1, + credit := Credit1, + snd_settle_mode := <<"settled">>, + max_message_size := IncomingMaxMsgSize, + unconfirmed_messages := 0}, + #{handle := 2, + link_name := <<"my sender">>, + target_address := <<"/exchanges/amq.direct/my%20key">>, + delivery_count := DeliveryCount2, + credit := Credit2, + snd_settle_mode := <<"mixed">>, + max_message_size := IncomingMaxMsgSize, + unconfirmed_messages := 0}], + outgoing_links := [#{handle := 1, + link_name := <<"my link pair">>, + source_address := <<"/management">>, + queue_name := <<>>, + delivery_count := DeliveryCount3, + credit := 0, + max_message_size := <<"unlimited">>, + send_settled := true}, + #{handle := 3, + link_name := <<"my receiver">>, + source_address := <<"/queues/my%20queue">>, + queue_name := <<"my queue">>, + delivery_count := DeliveryCount4, + credit := 5000, + max_message_size := <<"unlimited">>, + send_settled := true}] + } when is_integer(HandleMax) andalso + is_integer(NextIncomingId) andalso + is_integer(IncomingWindow) andalso + is_integer(NextOutgoingId) andalso + is_integer(RemoteIncomingWindow) andalso + is_integer(RemoteOutgoingWindow) andalso + is_integer(Credit1) andalso + is_integer(Credit2) andalso + is_integer(IncomingMaxMsgSize) andalso + is_integer(DeliveryCount1) andalso + is_integer(DeliveryCount2) andalso + is_integer(DeliveryCount3) andalso + is_integer(DeliveryCount4), + Session), + + {ok, _Session2} = amqp10_client:begin_session_sync(C), + Sessions = http_get(Config, Path), + ?assertEqual(2, length(Sessions)), + + ok = amqp10_client:detach_link(Sender), + ok = amqp10_client:detach_link(Receiver), + {ok, _} = rabbitmq_amqp_client:delete_queue(LinkPair, QName), + ok = rabbitmq_amqp_client:detach_management_link_pair_sync(LinkPair), + ok = amqp10_client:close_connection(C). + +%% Test that GET /connections/:name/sessions returns +%% 400 Bad Request for non-AMQP 1.0 connections. +amqpl_sessions(Config) -> + {Conn, _Ch} = open_connection_and_channel(Config), + LocalPort = local_port(Conn), + Path = binary_to_list( + rabbit_mgmt_format:print( + "/connections/127.0.0.1%3A~w%20-%3E%20127.0.0.1%3A~w/sessions", + [LocalPort, amqp_port(Config)])), + ok = await_condition( + fun() -> + http_get(Config, Path, 400), + true + end). + +%% Test that AMQP 1.0 connection can be listed if the rabbitmq_management plugin gets enabled +%% after the connection was established. +enable_plugin_amqp(Config) -> + ?assertEqual(0, length(http_get(Config, "/connections"))), + + ok = rabbit_ct_broker_helpers:disable_plugin(Config, 0, rabbitmq_management), + ok = rabbit_ct_broker_helpers:disable_plugin(Config, 0, rabbitmq_management_agent), + + Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp), + OpnConf = #{address => ?config(rmq_hostname, Config), + port => Port, + container_id => <<"my container">>, + sasl => anon}, + {ok, Conn} = amqp10_client:open_connection(OpnConf), + receive {amqp10_event, {connection, Conn, opened}} -> ok + after 5000 -> ct:fail(opened_timeout) + end, + + ok = rabbit_ct_broker_helpers:enable_plugin(Config, 0, rabbitmq_management_agent), + ok = rabbit_ct_broker_helpers:enable_plugin(Config, 0, rabbitmq_management), + eventually(?_assertEqual(1, length(http_get(Config, "/connections"))), 1000, 10), + + ok = amqp10_client:close_connection(Conn), + receive {amqp10_event, {connection, Conn, {closed, normal}}} -> ok + after 5000 -> ct:fail({connection_close_timeout, Conn}) + end. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) flush(Prefix) -> receive Msg -> diff --git a/deps/rabbitmq_management/test/rabbit_mgmt_schema_SUITE.erl b/deps/rabbitmq_management/test/rabbit_mgmt_schema_SUITE.erl new file mode 100644 index 000000000000..47c369978cb9 --- /dev/null +++ b/deps/rabbitmq_management/test/rabbit_mgmt_schema_SUITE.erl @@ -0,0 +1,76 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% +-module(rabbit_mgmt_schema_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-import(rabbit_mgmt_schema, [translate_endpoint_params/2, translate_oauth_resource_servers/1]). + +all() -> + [ + test_empty_endpoint_params, + test_invalid_endpoint_params, + test_translate_endpoint_params, + test_with_one_resource_server, + test_with_many_resource_servers + ]. + + +test_empty_endpoint_params(_) -> + [] = translate_endpoint_params("oauth_authorization_endpoint_params", []), + [] = translate_endpoint_params("oauth_token_endpoint_params", []). + +test_invalid_endpoint_params(_) -> + try translate_endpoint_params("oauth_authorization_endpoint_params", [ + {["param1","param2"], "some-value1"}]) of + _ -> {throw, should_have_failed} + catch + _ -> ok + end. + +test_translate_endpoint_params(_) -> + [ {<<"param1">>, <<"some-value1">>} ] = + translate_endpoint_params("oauth_authorization_endpoint_params", [ + {["management","oauth_authorization_endpoint_params","param1"], "some-value1"} + ]). + +test_with_one_resource_server(_) -> + Conf = [ + {["management","oauth_resource_servers","rabbitmq1","id"],"rabbitmq1"} + ], + #{ + <<"rabbitmq1">> := [ + {id, <<"rabbitmq1">>} + ] + } = translate_oauth_resource_servers(Conf). + +test_with_many_resource_servers(_) -> + Conf = [ + {["management","oauth_resource_servers","keycloak","label"],"Keycloak"}, + {["management","oauth_resource_servers","uaa","label"],"Uaa"} + ], + #{ + <<"keycloak">> := [ + {label, <<"Keycloak">>}, + {id, <<"keycloak">>} + ], + <<"uaa">> := [ + {label, <<"Uaa">>}, + {id, <<"uaa">>} + ] + } = translate_oauth_resource_servers(Conf). + + +cert_filename(Conf) -> + string:concat(?config(data_dir, Conf), "certs/cert.pem"). + +sort_settings(MapOfListOfSettings) -> + maps:map(fun(_K,List) -> + lists:sort(fun({K1,_}, {K2,_}) -> K1 < K2 end, List) end, MapOfListOfSettings). diff --git a/deps/rabbitmq_management/test/rabbit_mgmt_wm_auth_SUITE.erl b/deps/rabbitmq_management/test/rabbit_mgmt_wm_auth_SUITE.erl index d47350d2b926..955b98a7c616 100644 --- a/deps/rabbitmq_management/test/rabbit_mgmt_wm_auth_SUITE.erl +++ b/deps/rabbitmq_management/test/rabbit_mgmt_wm_auth_SUITE.erl @@ -9,7 +9,12 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). +<<<<<<< HEAD +======= +-import(application, [set_env/3, unset_env/2]). +-import(rabbit_mgmt_wm_auth, [authSettings/0]). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) -compile(export_all). all() -> @@ -21,14 +26,42 @@ all() -> {group, verify_mgt_oauth_provider_url_with_single_resource_and_another_resource}, {group, verify_end_session_endpoint_with_single_resource}, {group, verify_end_session_endpoint_with_single_resource_and_another_resource}, +<<<<<<< HEAD {group, verify_oauth_initiated_logon_type_for_sp_initiated}, {group, verify_oauth_initiated_logon_type_for_idp_initiated}, {group, verify_oauth_disable_basic_auth}, {group, verify_oauth_scopes} +======= + {group, verify_multi_resource_and_provider}, + {group, verify_oauth_initiated_logon_type_for_sp_initiated}, + {group, verify_oauth_initiated_logon_type_for_idp_initiated}, + {group, verify_oauth_disable_basic_auth}, + {group, verify_oauth_scopes}, + {group, verify_extra_endpoint_params} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]. groups() -> [ +<<<<<<< HEAD +======= + + {verify_multi_resource_and_provider, [], [ + {with_oauth_enabled, [], [ + {with_oauth_providers_idp1_idp2, [], [ + {with_default_oauth_provider_idp1, [], [ + {with_resource_server_a, [], [ + should_return_disabled_auth_settings, + {with_mgt_resource_server_a_with_client_id_x, [], [ + should_return_oauth_enabled, + should_return_oauth_resource_server_a_with_client_id_x + ]} + ]} + ]} + ]} + ]} + ]}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {without_any_settings, [], [ should_return_disabled_auth_settings ]}, @@ -74,8 +107,18 @@ groups() -> should_return_disabled_auth_settings, {with_mgt_oauth_client_id_z, [], [ should_return_mgt_oauth_provider_url_url1, +<<<<<<< HEAD {with_mgt_oauth_provider_url_url0, [], [ should_return_mgt_oauth_provider_url_url0 +======= + should_return_mgt_oauth_metadata_url_url1, + {with_mgt_oauth_provider_url_url0, [], [ + should_return_mgt_oauth_provider_url_url0, + should_return_mgt_oauth_metadata_url_url1, + {with_mgt_oauth_resource_server_rabbit_with_oauth_metadata_url_url1, [], [ + should_return_oauth_resource_server_rabbit_with_oauth_metadata_url_url1 + ]} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]} ]} ]} @@ -86,6 +129,10 @@ groups() -> should_return_disabled_auth_settings, {with_mgt_oauth_client_id_z, [], [ should_return_mgt_oauth_provider_url_idp1_url, +<<<<<<< HEAD +======= + should_return_mgt_oauth_matadata_url_idp1_url, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {with_root_issuer_url1, [], [ should_return_mgt_oauth_provider_url_idp1_url ]}, @@ -102,7 +149,11 @@ groups() -> {with_resource_server_id_rabbit, [], [ {with_root_issuer_url1, [], [ {with_oauth_enabled, [], [ +<<<<<<< HEAD {with_mgt_oauth_client_id_z, [], [ +======= + {with_mgt_oauth_client_id_z, [], [ +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) should_not_return_end_session_endpoint, {with_root_end_session_endpoint_0, [], [ should_return_end_session_endpoint_0 @@ -112,7 +163,11 @@ groups() -> ]}, {with_oauth_providers_idp1_idp2, [], [ {with_default_oauth_provider_idp1, [], [ +<<<<<<< HEAD {with_oauth_enabled, [], [ +======= + {with_oauth_enabled, [], [ +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {with_mgt_oauth_client_id_z, [], [ should_not_return_end_session_endpoint, {with_end_session_endpoint_for_idp1_1, [], [ @@ -141,7 +196,11 @@ groups() -> should_return_oauth_resource_server_a_without_end_session_endpoint, {with_root_end_session_endpoint_0, [], [ should_return_end_session_endpoint_0, +<<<<<<< HEAD should_return_oauth_resource_server_a_with_end_session_endpoint_0 +======= + should_return_oauth_resource_server_a_with_end_session_endpoint_0 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]}, {with_oauth_providers_idp1_idp2, [], [ {with_default_oauth_provider_idp1, [], [ @@ -151,11 +210,19 @@ groups() -> {with_oauth_provider_idp2_for_resource_server_a, [], [ {with_end_session_endpoint_for_idp2_2, [], [ should_return_oauth_resource_server_a_with_end_session_endpoint_2 +<<<<<<< HEAD ]} ]} ]} ]} ]} +======= + ]} + ]} + ]} + ]} + ]} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]} ]} ]} @@ -170,6 +237,7 @@ groups() -> should_return_disabled_auth_settings, {with_mgt_oauth_client_id_z, [], [ should_return_oauth_resource_server_rabbit_with_oauth_provider_url_url1, +<<<<<<< HEAD should_return_oauth_resource_server_a_with_oauth_provider_url_url1, {with_mgt_oauth_provider_url_url0, [], [ should_return_oauth_resource_server_rabbit_with_oauth_provider_url_url0, @@ -177,6 +245,19 @@ groups() -> {with_mgt_oauth_resource_server_a_with_oauth_provider_url_url1, [], [ should_return_oauth_resource_server_rabbit_with_oauth_provider_url_url0, should_return_oauth_resource_server_a_with_oauth_provider_url_url1 +======= + should_return_oauth_resource_server_rabbit_with_oauth_metadata_url_url1, + should_return_oauth_resource_server_a_with_oauth_provider_url_url1, + should_return_oauth_resource_server_a_with_oauth_metadata_url_url1, + {with_mgt_oauth_provider_url_url0, [], [ + should_return_oauth_resource_server_rabbit_with_oauth_provider_url_url0, + should_return_oauth_resource_server_rabbit_with_oauth_metadata_url_url1, + should_return_oauth_resource_server_a_with_oauth_provider_url_url0, + should_return_oauth_resource_server_a_with_oauth_metadata_url_url1, + {with_mgt_oauth_resource_server_a_with_oauth_provider_url_url1, [], [ + should_return_oauth_resource_server_rabbit_with_oauth_provider_url_url0, + should_return_oauth_resource_server_a_with_oauth_provider_url_url1 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]} ]} ]} @@ -188,14 +269,31 @@ groups() -> should_return_disabled_auth_settings, {with_mgt_oauth_client_id_z, [], [ should_return_oauth_resource_server_rabbit_with_oauth_provider_url_idp1_url, +<<<<<<< HEAD +======= + should_return_oauth_resource_server_rabbit_with_oauth_metadata_url_idp1_url, + {with_mgt_oauth_resource_server_rabbit_with_oauth_metadata_url_url1, [], [ + should_return_oauth_resource_server_rabbit_with_oauth_metadata_url_url1 + ]}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {with_root_issuer_url1, [], [ should_return_oauth_resource_server_rabbit_with_oauth_provider_url_idp1_url ]}, {with_mgt_oauth_provider_url_url0, [], [ should_return_oauth_resource_server_rabbit_with_oauth_provider_url_url0, +<<<<<<< HEAD {with_mgt_oauth_resource_server_a_with_oauth_provider_url_url1, [], [ should_return_oauth_resource_server_rabbit_with_oauth_provider_url_url0, should_return_oauth_resource_server_a_with_oauth_provider_url_url1 +======= + should_return_oauth_resource_server_rabbit_with_oauth_metadata_url_idp1_url, + {with_mgt_oauth_resource_server_a_with_oauth_provider_url_url1, [], [ + should_return_oauth_resource_server_rabbit_with_oauth_provider_url_url0, + should_return_oauth_resource_server_a_with_oauth_provider_url_url1, + {with_mgt_oauth_resource_server_a_with_oauth_metadata_url_url1, [], [ + should_return_oauth_resource_server_a_with_oauth_metadata_url_url1 + ]} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]} ]} ]} @@ -204,7 +302,11 @@ groups() -> ]} ]} ]} +<<<<<<< HEAD ]}, +======= + ]}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {verify_oauth_initiated_logon_type_for_sp_initiated, [], [ should_return_disabled_auth_settings, {with_resource_server_id_rabbit, [], [ @@ -287,6 +389,35 @@ groups() -> ]} ]} ]} +<<<<<<< HEAD +======= + ]}, + {verify_extra_endpoint_params, [], [ + {with_resource_server_id_rabbit, [], [ + {with_root_issuer_url1, [], [ + {with_oauth_enabled, [], [ + {with_mgt_oauth_client_id_z, [], [ + should_return_mgt_oauth_resource_rabbit_without_authorization_endpoint_params, + should_return_mgt_oauth_resource_rabbit_without_token_endpoint_params, + {with_authorization_endpoint_params_0, [], [ + should_return_mgt_oauth_resource_rabbit_with_authorization_endpoint_params_0 + ]}, + {with_token_endpoint_params_0, [], [ + should_return_mgt_oauth_resource_rabbit_with_token_endpoint_params_0 + ]}, + {with_resource_server_a, [], [ + {with_mgt_resource_server_a_with_authorization_endpoint_params_1, [], [ + should_return_mgt_oauth_resource_a_with_authorization_endpoint_params_1 + ]}, + {with_mgt_resource_server_a_with_token_endpoint_params_1, [], [ + should_return_mgt_oauth_resource_a_with_token_endpoint_params_1 + ]} + ]} + ]} + ]} + ]} + ]} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]} ]. @@ -299,10 +430,22 @@ init_per_suite(Config) -> {idp2, <<"idp2">>}, {idp3, <<"idp3">>}, {idp1_url, <<"https://idp1">>}, +<<<<<<< HEAD {idp2_url, <<"https://idp2">>}, {idp3_url, <<"https://idp3">>}, {url0, <<"https://url0">>}, {url1, <<"https://url1">>}, +======= + {meta_idp1_url, <<"https://idp1/.well-known/openid-configuration">>}, + {idp2_url, <<"https://idp2">>}, + {meta_idp2_url, <<"https://idp2/.well-known/openid-configuration">>}, + {idp3_url, <<"https://idp3">>}, + {meta_idp3_url, <<"https://idp3/.well-known/openid-configuration">>}, + {url0, <<"https://url0">>}, + {meta_url0, <<"https://url0/.well-known/openid-configuration">>}, + {url1, <<"https://url1">>}, + {meta_url1, <<"https://url1/.well-known/openid-configuration">>}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {logout_url_0, <<"https://logout_0">>}, {logout_url_1, <<"https://logout_1">>}, {logout_url_2, <<"https://logout_2">>}, @@ -312,6 +455,13 @@ init_per_suite(Config) -> {w, <<"w">>}, {z, <<"z">>}, {x, <<"x">>}, +<<<<<<< HEAD +======= + {authorization_params_0, [{<<"a-param0">>, <<"value0">>}]}, + {authorization_params_1, [{<<"a-param1">>, <<"value1">>}]}, + {token_params_0, [{<<"t-param0">>, <<"value0">>}]}, + {token_params_1, [{<<"t-param1">>, <<"value1">>}]}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {admin_mgt, <<"admin mgt">>}, {read_write, <<"read write">>} | Config]. @@ -319,6 +469,7 @@ end_per_suite(_Config) -> ok. init_per_group(with_oauth_disabled, Config) -> +<<<<<<< HEAD application:set_env(rabbitmq_management, oauth_enabled, false), Config; init_per_group(with_oauth_enabled, Config) -> @@ -329,12 +480,25 @@ init_per_group(with_resource_server_id_rabbit, Config) -> Config; init_per_group(with_mgt_oauth_client_id_z, Config) -> application:set_env(rabbitmq_management, oauth_client_id, ?config(z, Config)), +======= + set_env(rabbitmq_management, oauth_enabled, false), + Config; +init_per_group(with_oauth_enabled, Config) -> + set_env(rabbitmq_management, oauth_enabled, true), + Config; +init_per_group(with_resource_server_id_rabbit, Config) -> + set_env(rabbitmq_auth_backend_oauth2, resource_server_id, ?config(rabbit, Config)), + Config; +init_per_group(with_mgt_oauth_client_id_z, Config) -> + set_env(rabbitmq_management, oauth_client_id, ?config(z, Config)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Config; init_per_group(with_mgt_resource_server_a_with_client_secret_w, Config) -> set_attribute_in_entry_for_env_variable(rabbitmq_management, oauth_resource_servers, ?config(a, Config), oauth_client_secret, ?config(w, Config)), Config; init_per_group(with_mgt_oauth_client_secret_q, Config) -> +<<<<<<< HEAD application:set_env(rabbitmq_management, oauth_client_secret, ?config(q, Config)), Config; init_per_group(with_mgt_oauth_provider_url_url0, Config) -> @@ -354,6 +518,27 @@ init_per_group(with_oauth_initiated_logon_type_idp_initiated, Config) -> Config; init_per_group(with_oauth_initiated_logon_type_sp_initiated, Config) -> application:set_env(rabbitmq_management, oauth_initiated_logon_type, sp_initiated), +======= + set_env(rabbitmq_management, oauth_client_secret, ?config(q, Config)), + Config; +init_per_group(with_mgt_oauth_provider_url_url0, Config) -> + set_env(rabbitmq_management, oauth_provider_url, ?config(url0, Config)), + Config; +init_per_group(with_root_issuer_url1, Config) -> + set_env(rabbitmq_auth_backend_oauth2, issuer, ?config(url1, Config)), + Config; +init_per_group(with_oauth_scopes_admin_mgt, Config) -> + set_env(rabbitmq_management, oauth_scopes, ?config(admin_mgt, Config)), + Config; +init_per_group(with_oauth_scopes_write_read, Config) -> + set_env(rabbitmq_management, oauth_scopes, ?config(write_read, Config)), + Config; +init_per_group(with_oauth_initiated_logon_type_idp_initiated, Config) -> + set_env(rabbitmq_management, oauth_initiated_logon_type, idp_initiated), + Config; +init_per_group(with_oauth_initiated_logon_type_sp_initiated, Config) -> + set_env(rabbitmq_management, oauth_initiated_logon_type, sp_initiated), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Config; init_per_group(with_mgt_resource_server_a_with_oauth_initiated_logon_type_sp_initiated, Config) -> set_attribute_in_entry_for_env_variable(rabbitmq_management, oauth_resource_servers, @@ -364,10 +549,17 @@ init_per_group(with_mgt_resource_server_a_with_oauth_initiated_logon_type_idp_in ?config(a, Config), oauth_initiated_logon_type, idp_initiated), Config; init_per_group(with_oauth_disable_basic_auth_false, Config) -> +<<<<<<< HEAD application:set_env(rabbitmq_management, oauth_disable_basic_auth, false), Config; init_per_group(with_oauth_providers_idp1_idp2, Config) -> application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{ +======= + set_env(rabbitmq_management, oauth_disable_basic_auth, false), + Config; +init_per_group(with_oauth_providers_idp1_idp2, Config) -> + set_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{ +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ?config(idp1, Config) => [ { issuer, ?config(idp1_url, Config)} ], ?config(idp2, Config) => [ { issuer, ?config(idp2_url, Config)} ] }), @@ -392,6 +584,7 @@ init_per_group(with_mgt_resource_server_a_with_client_id_x, Config) -> set_attribute_in_entry_for_env_variable(rabbitmq_management, oauth_resource_servers, ?config(a, Config), oauth_client_id, ?config(x, Config)), Config; +<<<<<<< HEAD init_per_group(with_default_oauth_provider_idp1, Config) -> application:set_env(rabbitmq_auth_backend_oauth2, default_oauth_provider, ?config(idp1, Config)), Config; @@ -400,6 +593,20 @@ init_per_group(with_default_oauth_provider_idp3, Config) -> Config; init_per_group(with_root_end_session_endpoint_0, Config) -> application:set_env(rabbitmq_auth_backend_oauth2, end_session_endpoint, ?config(logout_url_0, Config)), +======= + +init_per_group(with_default_oauth_provider_idp1, Config) -> + set_env(rabbitmq_auth_backend_oauth2, default_oauth_provider, ?config(idp1, Config)), + Config; +init_per_group(with_mgt_oauth_resource_server_rabbit_with_oauth_metadata_url_url1, Config) -> + set_env(rabbitmq_management, oauth_metadata_url, ?config(meta_url1, Config)), + Config; +init_per_group(with_default_oauth_provider_idp3, Config) -> + set_env(rabbitmq_auth_backend_oauth2, default_oauth_provider, ?config(idp3, Config)), + Config; +init_per_group(with_root_end_session_endpoint_0, Config) -> + set_env(rabbitmq_auth_backend_oauth2, end_session_endpoint, ?config(logout_url_0, Config)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Config; init_per_group(with_end_session_endpoint_for_idp1_1, Config) -> set_attribute_in_entry_for_env_variable(rabbitmq_auth_backend_oauth2, oauth_providers, @@ -409,16 +616,44 @@ init_per_group(with_end_session_endpoint_for_idp2_2, Config) -> set_attribute_in_entry_for_env_variable(rabbitmq_auth_backend_oauth2, oauth_providers, ?config(idp2, Config), end_session_endpoint, ?config(logout_url_2, Config)), Config; +<<<<<<< HEAD +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) init_per_group(with_oauth_provider_idp2_for_resource_server_a, Config) -> set_attribute_in_entry_for_env_variable(rabbitmq_auth_backend_oauth2, resource_servers, ?config(a, Config), oauth_provider_id, ?config(idp2, Config)), Config; +<<<<<<< HEAD +======= +init_per_group(with_authorization_endpoint_params_0, Config) -> + set_env(rabbitmq_management, oauth_authorization_endpoint_params, + ?config(authorization_params_0, Config)), + Config; +init_per_group(with_token_endpoint_params_0, Config) -> + set_env(rabbitmq_management, oauth_token_endpoint_params, + ?config(token_params_0, Config)), + Config; +init_per_group(with_mgt_resource_server_a_with_authorization_endpoint_params_1, Config) -> + set_attribute_in_entry_for_env_variable(rabbitmq_management, oauth_resource_servers, + ?config(a, Config), oauth_authorization_endpoint_params, ?config(authorization_params_1, Config)), + Config; +init_per_group(with_mgt_oauth_resource_server_a_with_oauth_metadata_url_url1, Config) -> + set_attribute_in_entry_for_env_variable(rabbitmq_management, oauth_resource_servers, + ?config(a, Config), oauth_metadata_url, ?config(meta_url1, Config)), + Config; +init_per_group(with_mgt_resource_server_a_with_token_endpoint_params_1, Config) -> + set_attribute_in_entry_for_env_variable(rabbitmq_management, oauth_resource_servers, + ?config(a, Config), oauth_token_endpoint_params, ?config(token_params_1, Config)), + Config; + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) init_per_group(_, Config) -> Config. end_per_group(with_oauth_providers_idp1_idp2, Config) -> +<<<<<<< HEAD application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), Config; end_per_group(with_mgt_oauth_client_secret_q, Config) -> @@ -456,6 +691,52 @@ end_per_group(with_oauth_initiated_logon_type_idp_initiated, Config) -> Config; end_per_group(with_oauth_initiated_logon_type_sp_initiated, Config) -> application:unset_env(rabbitmq_management, oauth_initiated_logon_type), +======= + unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), + Config; +end_per_group(with_mgt_oauth_client_secret_q, Config) -> + unset_env(rabbitmq_management, oauth_client_secret), + Config; +end_per_group(with_oauth_scopes_admin_mgt, Config) -> + unset_env(rabbitmq_management, oauth_scopes), + Config; +end_per_group(with_oauth_scopes_write_read, Config) -> + unset_env(rabbitmq_management, oauth_scopes), + Config; +end_per_group(with_oauth_disabled, Config) -> + unset_env(rabbitmq_management, oauth_enabled), + Config; +end_per_group(with_oauth_enabled, Config) -> + unset_env(rabbitmq_management, oauth_enabled), + Config; +end_per_group(with_oauth_disable_basic_auth_false, Config) -> + unset_env(rabbitmq_management, oauth_disable_basic_auth), + Config; +end_per_group(with_resource_server_id_rabbit, Config) -> + unset_env(rabbitmq_auth_backend_oauth2, resource_server_id), + Config; +end_per_group(with_default_oauth_provider_idp1, Config) -> + unset_env(rabbitmq_auth_backend_oauth2, default_oauth_provider), + Config; +end_per_group(with_mgt_oauth_provider_url_url0, Config) -> + unset_env(rabbitmq_management, oauth_provider_url), + Config; +end_per_group(with_mgt_oauth_resource_server_rabbit_with_oauth_metadata_url_url1, Config) -> + unset_env(rabbitmq_management, oauth_metadata_url), + Config; +end_per_group(with_root_issuer_url1, Config) -> + unset_env(rabbitmq_auth_backend_oauth2, issuer), + unset_env(rabbitmq_auth_backend_oauth2, discovery_endpoint), + Config; +end_per_group(with_mgt_oauth_client_id_z, Config) -> + unset_env(rabbitmq_management, oauth_client_id), + Config; +end_per_group(with_oauth_initiated_logon_type_idp_initiated, Config) -> + unset_env(rabbitmq_management, oauth_initiated_logon_type), + Config; +end_per_group(with_oauth_initiated_logon_type_sp_initiated, Config) -> + unset_env(rabbitmq_management, oauth_initiated_logon_type), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Config; end_per_group(with_mgt_resource_server_a_with_client_secret_w, Config) -> remove_attribute_from_entry_from_env_variable(rabbitmq_management, oauth_resource_servers, @@ -477,6 +758,13 @@ end_per_group(with_mgt_oauth_resource_server_a_with_oauth_provider_url_url1, Con remove_attribute_from_entry_from_env_variable(rabbitmq_management, oauth_resource_servers, ?config(a, Config), oauth_provider_url), Config; +<<<<<<< HEAD +======= +end_per_group(with_mgt_oauth_resource_server_a_with_oauth_metadata_url_url1, Config) -> + remove_attribute_from_entry_from_env_variable(rabbitmq_management, oauth_resource_servers, + ?config(a, Config), oauth_metadata_url), + Config; +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end_per_group(with_mgt_resource_server_a_with_oauth_initiated_logon_type_sp_initiated, Config) -> remove_attribute_from_entry_from_env_variable(rabbitmq_management, oauth_resource_servers, ?config(a, Config), oauth_initiated_logon_type), @@ -490,6 +778,7 @@ end_per_group(with_mgt_resource_server_a_with_client_id_x, Config) -> ?config(a, Config), oauth_client_id), Config; end_per_group(with_default_oauth_provider_idp1, Config) -> +<<<<<<< HEAD application:unset_env(rabbitmq_auth_backend_oauth2, default_oauth_provider), Config; end_per_group(with_default_oauth_provider_idp3, Config) -> @@ -497,6 +786,15 @@ end_per_group(with_default_oauth_provider_idp3, Config) -> Config; end_per_group(with_root_end_session_endpoint_0, Config) -> application:unset_env(rabbitmq_auth_backend_oauth2, end_session_endpoint), +======= + unset_env(rabbitmq_auth_backend_oauth2, default_oauth_provider), + Config; +end_per_group(with_default_oauth_provider_idp3, Config) -> + unset_env(rabbitmq_auth_backend_oauth2, default_oauth_provider), + Config; +end_per_group(with_root_end_session_endpoint_0, Config) -> + unset_env(rabbitmq_auth_backend_oauth2, end_session_endpoint), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Config; end_per_group(with_end_session_endpoint_for_idp1_1, Config) -> remove_attribute_from_entry_from_env_variable(rabbitmq_auth_backend_oauth2, oauth_providers, @@ -510,6 +808,24 @@ end_per_group(with_oauth_provider_idp2_for_resource_server_a, Config) -> remove_attribute_from_entry_from_env_variable(rabbitmq_auth_backend_oauth2, resource_servers, ?config(a, Config), oauth_provider_id), Config; +<<<<<<< HEAD +======= +end_per_group(with_authorization_endpoint_params_0, Config) -> + unset_env(rabbitmq_management, oauth_authorization_endpoint_params), + Config; +end_per_group(with_token_endpoint_params_0, Config) -> + unset_env(rabbitmq_management, oauth_token_endpoint_params), + Config; +end_per_group(with_mgt_resource_server_a_with_authorization_endpoint_params_1, Config) -> + remove_attribute_from_entry_from_env_variable(rabbitmq_management, oauth_resource_servers, + ?config(a, Config), oauth_authorization_endpoint_params), + Config; +end_per_group(with_mgt_resource_server_a_with_token_endpoint_params_1, Config) -> + remove_attribute_from_entry_from_env_variable(rabbitmq_management, oauth_resource_servers, + ?config(a, Config), oauth_token_endpoint_params), + Config; + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end_per_group(_, Config) -> Config. @@ -519,6 +835,7 @@ end_per_group(_, Config) -> %% Test cases. %% ------------------------------------------------------------------- should_not_return_oauth_client_secret(_Config) -> +<<<<<<< HEAD Actual = rabbit_mgmt_wm_auth:authSettings(), ?assertEqual(false, proplists:is_defined(oauth_client_secret, Actual)). should_return_oauth_client_secret_q(Config) -> @@ -664,6 +981,205 @@ should_return_oauth_resource_server_a_with_end_session_endpoint_2(Config) -> assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), Config, a, end_session_endpoint, ?config(logout_url_2, Config)). +======= + Actual = authSettings(), + ?assertEqual(false, proplists:is_defined(oauth_client_secret, Actual)). +should_return_oauth_client_secret_q(Config) -> + Actual = authSettings(), + ?assertEqual(?config(q, Config), proplists:get_value(oauth_client_secret, Actual)). +should_return_oauth_resource_server_a_with_client_id_x(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, oauth_client_id, x). +should_return_oauth_resource_server_a_with_client_secret_w(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, oauth_client_secret, w). +should_not_return_oauth_resource_server_a_with_client_secret(Config) -> + assert_attribute_not_defined_for_oauth_resource_server(authSettings(), + Config, a, oauth_client_secret). + +should_return_mgt_oauth_provider_url_idp1_url(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_provider_url, idp1_url). + +should_return_mgt_oauth_matadata_url_idp1_url(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_metadata_url, meta_idp1_url). + +should_return_mgt_oauth_provider_url_url1(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_provider_url, url1). + +should_return_mgt_oauth_metadata_url_url1(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_metadata_url, meta_url1). + +should_return_mgt_oauth_metadata_url_url0(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_metadata_url, meta_url0). + +should_return_mgt_oauth_provider_url_url0(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_provider_url, url0). + +should_return_oauth_scopes_admin_mgt(Config) -> + Actual = authSettings(), + ?assertEqual(?config(admin_mgt, Config), proplists:get_value(oauth_scopes, Actual)). + +should_return_mgt_oauth_resource_server_a_with_scopes_read_write(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, scopes, read_write). + +should_return_disabled_auth_settings(_Config) -> + [{oauth_enabled, false}] = authSettings(). + +should_return_mgt_resource_server_a_oauth_provider_url_url0(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, oauth_provider_url, url0). + +should_return_mgt_oauth_resource_server_a_with_client_id_x(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, oauth_client_id, x). + +should_return_oauth_resource_server_a_with_oauth_provider_url_idp1_url(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, oauth_provider_url, idp1_url). + +should_return_oauth_resource_server_a_with_oauth_provider_url_url1(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, oauth_provider_url, url1). + +should_return_oauth_resource_server_a_with_oauth_metadata_url_url1(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, oauth_metadata_url, meta_url1). + +should_return_oauth_resource_server_a_with_oauth_provider_url_url0(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, oauth_provider_url, url0). + +should_return_oauth_resource_server_rabbit_with_oauth_provider_url_idp1_url(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_provider_url, idp1_url). + +should_return_oauth_resource_server_rabbit_with_oauth_metadata_url_idp1_url(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_metadata_url, meta_idp1_url). + +should_return_oauth_resource_server_rabbit_with_oauth_provider_url_url1(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_provider_url, url1). + +should_return_oauth_resource_server_rabbit_with_oauth_metadata_url_url1(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_metadata_url, meta_url1 ). + +should_return_oauth_resource_server_rabbit_with_oauth_provider_url_url0(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_provider_url, url0). + +should_return_oauth_resource_server_rabbit_with_oauth_metadata_url_url0(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_metadata_url, meta_url0). + +should_not_return_oauth_initiated_logon_type(_Config) -> + Actual = authSettings(), + ?assertEqual(false, proplists:is_defined(oauth_initiated_logon_type, Actual)). +should_return_oauth_initiated_logon_type_idp_initiated(_Config) -> + Actual = authSettings(), + ?assertEqual(<<"idp_initiated">>, proplists:get_value(oauth_initiated_logon_type, Actual)). + +should_not_return_oauth_resource_server_a(Config) -> + Actual = authSettings(), + assert_not_defined_oauth_resource_server(Actual, Config, a). + +should_not_return_oauth_resource_server_a_with_oauth_initiated_logon_type(Config) -> + assert_attribute_not_defined_for_oauth_resource_server(authSettings(), + Config, a, oauth_initiated_logon_type). + +should_return_oauth_resource_server_a_with_oauth_initiated_logon_type_idp_initiated(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, oauth_initiated_logon_type, <<"idp_initiated">>). +should_return_oauth_resource_server_a_with_oauth_initiated_logon_type_sp_initiated(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, oauth_initiated_logon_type, <<"sp_initiated">>). + +should_not_return_oauth_scopes(_Config) -> + Actual = authSettings(), + ?assertEqual(false, proplists:is_defined(scopes, Actual)). + +should_return_oauth_enabled(_Config) -> + Actual = authSettings(), + ?assertEqual(true, proplists:get_value(oauth_enabled, Actual)). + + +should_return_oauth_idp_initiated_logon(_Config) -> + Actual = authSettings(), + ?assertEqual(<<"idp_initiated">>, proplists:get_value(oauth_initiated_logon_type, Actual)). + +should_return_oauth_disable_basic_auth_true(_Config) -> + Actual = authSettings(), + ?assertEqual(true, proplists:get_value(oauth_disable_basic_auth, Actual)). + +should_return_oauth_disable_basic_auth_false(_Config) -> + Actual = authSettings(), + ?assertEqual(false, proplists:get_value(oauth_disable_basic_auth, Actual)). + +should_return_oauth_client_id_z(Config) -> + Actual = authSettings(), + ?assertEqual(?config(z, Config), proplists:get_value(oauth_client_id, Actual)). + +should_not_return_end_session_endpoint(Config) -> + assert_attribute_not_defined_for_oauth_resource_server(authSettings(), + Config, rabbit, end_session_endpoint). + +should_return_end_session_endpoint_0(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, end_session_endpoint, ?config(logout_url_0, Config)). + +should_return_end_session_endpoint_1(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, end_session_endpoint, ?config(logout_url_1, Config)). + +should_return_oauth_resource_server_a_without_end_session_endpoint(Config) -> + assert_attribute_not_defined_for_oauth_resource_server(authSettings(), + Config, a, end_session_endpoint). + +should_return_oauth_resource_server_a_with_end_session_endpoint_0(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, end_session_endpoint, ?config(logout_url_0, Config)). + +should_return_oauth_resource_server_a_with_end_session_endpoint_1(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, end_session_endpoint, ?config(logout_url_1, Config)). + +should_return_oauth_resource_server_a_with_end_session_endpoint_2(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, end_session_endpoint, ?config(logout_url_2, Config)). + +should_return_mgt_oauth_resource_rabbit_without_authorization_endpoint_params(Config) -> + assert_attribute_not_defined_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_authorization_endpoint_params). + +should_return_mgt_oauth_resource_rabbit_without_token_endpoint_params(Config) -> + assert_attribute_not_defined_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_token_endpoint_params). + +should_return_mgt_oauth_resource_rabbit_with_authorization_endpoint_params_0(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_authorization_endpoint_params, authorization_params_0). + +should_return_mgt_oauth_resource_rabbit_with_token_endpoint_params_0(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_token_endpoint_params, token_params_0). + +should_return_mgt_oauth_resource_a_with_authorization_endpoint_params_1(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, oauth_authorization_endpoint_params, authorization_params_1). + +should_return_mgt_oauth_resource_a_with_token_endpoint_params_1(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, oauth_token_endpoint_params, token_params_1). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% ------------------------------------------------------------------- %% Utility/helper functions %% ------------------------------------------------------------------- @@ -677,16 +1193,26 @@ remove_entry_from_env_variable(Application, EnvVar, Key) -> Map = application:get_env(Application, EnvVar, #{}), NewMap = maps:remove(Key, Map), case maps:size(NewMap) of +<<<<<<< HEAD 0 -> application:unset_env(Application, EnvVar); _ -> application:set_env(Application, EnvVar, NewMap) +======= + 0 -> unset_env(Application, EnvVar); + _ -> set_env(Application, EnvVar, NewMap) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end. remove_attribute_from_entry_from_env_variable(Application, EnvVar, Key, Attribute) -> Map = application:get_env(Application, EnvVar, #{}), Proplist = proplists:delete(Attribute, maps:get(Key, Map, [])), NewMap = delete_key_with_empty_proplist(Key, maps:put(Key, Proplist, Map)), case maps:size(NewMap) of +<<<<<<< HEAD 0 -> application:unset_env(Application, EnvVar); _ -> application:set_env(Application, EnvVar, NewMap) +======= + 0 -> unset_env(Application, EnvVar); + _ -> set_env(Application, EnvVar, NewMap) +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) end. assertEqual_on_attribute_for_oauth_resource_server(Actual, Config, ConfigKey, Attribute, ConfigValue) -> @@ -699,6 +1225,15 @@ assertEqual_on_attribute_for_oauth_resource_server(Actual, Config, ConfigKey, At end, ?assertEqual(Value, proplists:get_value(Attribute, OauthResource)). +<<<<<<< HEAD +======= +assert_attribute_is_defined_for_oauth_resource_server(Actual, Config, ConfigKey, Attribute) -> + log(Actual), + OAuthResourceServers = proplists:get_value(oauth_resource_servers, Actual), + OauthResource = maps:get(?config(ConfigKey, Config), OAuthResourceServers), + ?assertEqual(true, proplists:is_defined(Attribute, OauthResource)). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) assert_attribute_not_defined_for_oauth_resource_server(Actual, Config, ConfigKey, Attribute) -> log(Actual), OAuthResourceServers = proplists:get_value(oauth_resource_servers, Actual), @@ -715,7 +1250,11 @@ set_attribute_in_entry_for_env_variable(Application, EnvVar, Key, Attribute, Val ct:log("set_attribute_in_entry_for_env_variable before ~p", [Map]), Map1 = maps:put(Key, [ { Attribute, Value} | maps:get(Key, Map, []) ], Map), ct:log("set_attribute_in_entry_for_env_variable after ~p", [Map1]), +<<<<<<< HEAD application:set_env(Application, EnvVar, Map1). +======= + set_env(Application, EnvVar, Map1). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) log(AuthSettings) -> logEnvVars(), diff --git a/deps/rabbitmq_management_agent/src/rabbit_mgmt_ff.erl b/deps/rabbitmq_management_agent/src/rabbit_mgmt_ff.erl index 5022adc020b3..9d47f3f6704a 100644 --- a/deps/rabbitmq_management_agent/src/rabbit_mgmt_ff.erl +++ b/deps/rabbitmq_management_agent/src/rabbit_mgmt_ff.erl @@ -10,6 +10,7 @@ -rabbit_feature_flag( {empty_basic_get_metric, #{desc => "Count AMQP `basic.get` on empty queues in stats", +<<<<<<< HEAD stability => required }}). @@ -18,3 +19,15 @@ #{desc => "Count unroutable publishes to be dropped in stats", stability => required }}). +======= + stability => required, + require_level => hard + }}). + +-rabbit_feature_flag( + {drop_unroutable_metric, + #{desc => "Count unroutable publishes to be dropped in stats", + stability => required, + require_level => hard + }}). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbitmq_mqtt/BUILD.bazel b/deps/rabbitmq_mqtt/BUILD.bazel index ca0c97809625..36db88e459c0 100644 --- a/deps/rabbitmq_mqtt/BUILD.bazel +++ b/deps/rabbitmq_mqtt/BUILD.bazel @@ -49,7 +49,12 @@ APP_ENV = """[ {mailbox_soft_limit, 200}, {max_packet_size_unauthenticated, 65536}, %% 256 MB is upper limit defined by MQTT spec +<<<<<<< HEAD {max_packet_size_authenticated, 268435455}, +======= + %% We set 16 MB as defined in deps/rabbit/Makefile max_message_size + {max_packet_size_authenticated, 16777216}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {topic_alias_maximum, 16} ] """ @@ -231,7 +236,11 @@ rabbitmq_integration_suite( ":test_util_beam", ":test_event_recorder_beam", ], +<<<<<<< HEAD shard_count = 10, +======= + shard_count = 5, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) runtime_deps = [ "//deps/rabbitmq_management_agent:erlang_app", "@emqtt//:erlang_app", @@ -246,7 +255,11 @@ rabbitmq_integration_suite( additional_beam = [ ":test_util_beam", ], +<<<<<<< HEAD shard_count = 4, +======= + shard_count = 2, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) runtime_deps = [ "@emqtt//:erlang_app", "@gun//:erlang_app", diff --git a/deps/rabbitmq_mqtt/Makefile b/deps/rabbitmq_mqtt/Makefile index c8ebda54547b..89ae9499a91c 100644 --- a/deps/rabbitmq_mqtt/Makefile +++ b/deps/rabbitmq_mqtt/Makefile @@ -27,7 +27,12 @@ define PROJECT_ENV {mailbox_soft_limit, 200}, {max_packet_size_unauthenticated, 65536}, %% 256 MB is upper limit defined by MQTT spec +<<<<<<< HEAD {max_packet_size_authenticated, 268435455}, +======= + %% We set 16 MB as defined in deps/rabbit/Makefile max_message_size + {max_packet_size_authenticated, 16777216}, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) {topic_alias_maximum, 16} ] endef diff --git a/deps/rabbitmq_mqtt/src/mc_mqtt.erl b/deps/rabbitmq_mqtt/src/mc_mqtt.erl index 27f4e9cf81e1..8958e21e8b2b 100644 --- a/deps/rabbitmq_mqtt/src/mc_mqtt.erl +++ b/deps/rabbitmq_mqtt/src/mc_mqtt.erl @@ -14,6 +14,10 @@ init/1, size/1, x_header/2, +<<<<<<< HEAD +======= + x_headers/1, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) property/2, routing_headers/2, convert_to/3, @@ -390,6 +394,14 @@ x_header(Key, #mqtt_msg{props = #{'User-Property' := UserProp}}) -> x_header(_Key, #mqtt_msg{}) -> undefined. +<<<<<<< HEAD +======= +x_headers(#mqtt_msg{props = #{'User-Property' := UserProp}}) -> + #{Key => {utf8, Val} || {<<"x-", _/binary>> = Key, Val} <- UserProp}; +x_headers(#mqtt_msg{}) -> + #{}. + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) property(correlation_id, #mqtt_msg{props = #{'Correlation-Data' := Corr}}) -> case mc_util:urn_string_to_uuid(Corr) of {ok, UUId} -> diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt.erl index c5ea59abedea..fc09405a40be 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt.erl @@ -87,7 +87,12 @@ init_global_counters(ProtoVer) -> rabbit_global_counters:init([Proto]), rabbit_global_counters:init([Proto, {queue_type, rabbit_classic_queue}]), rabbit_global_counters:init([Proto, {queue_type, rabbit_quorum_queue}]), +<<<<<<< HEAD rabbit_global_counters:init([Proto, {queue_type, ?QUEUE_TYPE_QOS_0}]). +======= + rabbit_global_counters:init([Proto, {queue_type, ?QUEUE_TYPE_QOS_0}]), + rabbit_msg_size_metrics:init(ProtoVer). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) persist_static_configuration() -> rabbit_mqtt_util:init_sparkplug(), @@ -112,6 +117,11 @@ persist_static_configuration() -> {ok, MaxSizeAuth} = application:get_env(?APP_NAME, max_packet_size_authenticated), assert_valid_max_packet_size(MaxSizeAuth), +<<<<<<< HEAD +======= + {ok, MaxMsgSize} = application:get_env(rabbit, max_message_size), + ?assert(MaxSizeAuth =< MaxMsgSize), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ok = persistent_term:put(?PERSISTENT_TERM_MAX_PACKET_SIZE_AUTHENTICATED, MaxSizeAuth). assert_valid_max_packet_size(Val) -> diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_ff.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_ff.erl index 3b35c794af39..88ea896cef14 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_ff.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_ff.erl @@ -16,13 +16,23 @@ -rabbit_feature_flag( {?QUEUE_TYPE_QOS_0, #{desc => "Support pseudo queue type for MQTT QoS 0 subscribers omitting a queue process", +<<<<<<< HEAD stability => required +======= + stability => required, + require_level => hard +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }}). -rabbit_feature_flag( {delete_ra_cluster_mqtt_node, #{desc => "Delete Ra cluster 'mqtt_node' since MQTT client IDs are tracked locally", +<<<<<<< HEAD stability => required +======= + stability => required, + require_level => hard +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) }}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -38,6 +48,10 @@ {mqtt_v5, #{desc => "Support MQTT 5.0", stability => required, +<<<<<<< HEAD +======= + require_level => hard, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) depends_on => [ %% MQTT 5.0 feature Will Delay Interval depends on client ID tracking in pg local. delete_ra_cluster_mqtt_node, diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl index 147a4467207a..0d741646af62 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl @@ -397,6 +397,10 @@ process_request(?PUBLISH, {ok, Topic, Props, State1} -> EffectiveQos = maybe_downgrade_qos(Qos), rabbit_global_counters:messages_received(ProtoVer, 1), +<<<<<<< HEAD +======= + rabbit_msg_size_metrics:observe(ProtoVer, iolist_size(Payload)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) State = maybe_increment_publisher(State1), Msg = #mqtt_msg{retain = Retain, qos = EffectiveQos, diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_reader.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_reader.erl index 54435849214a..86eaf2279fe4 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_reader.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_reader.erl @@ -37,7 +37,11 @@ rabbit_mqtt_processor:state(), connection_state :: running | blocked, conserve :: boolean(), +<<<<<<< HEAD stats_timer :: option(rabbit_event:state()), +======= + stats_timer :: rabbit_event:state(), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) keepalive = rabbit_mqtt_keepalive:init() :: rabbit_mqtt_keepalive:state(), conn_name :: binary() }). @@ -87,9 +91,15 @@ init(Ref) -> await_recv = false, connection_state = running, conserve = false, +<<<<<<< HEAD parse_state = rabbit_mqtt_packet:init_state()}, State1 = control_throttle(State0), State = rabbit_event:init_stats_timer(State1, #state.stats_timer), +======= + parse_state = rabbit_mqtt_packet:init_state(), + stats_timer = rabbit_event:init_stats_timer()}, + State = control_throttle(State0), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) gen_server:enter_loop(?MODULE, [], State); {error, Reason = enotconn} -> ?LOG_INFO("MQTT could not get connection string: ~s", [Reason]), @@ -128,6 +138,7 @@ handle_cast({close_connection, Reason}, handle_cast(QueueEvent = {queue_event, _, _}, State = #state{proc_state = PState0}) -> +<<<<<<< HEAD try case rabbit_mqtt_processor:handle_queue_event(QueueEvent, PState0) of {ok, PState} -> @@ -135,6 +146,13 @@ handle_cast(QueueEvent = {queue_event, _, _}, {error, Reason0, PState} -> {stop, Reason0, pstate(State, PState)} end +======= + try rabbit_mqtt_processor:handle_queue_event(QueueEvent, PState0) of + {ok, PState} -> + maybe_process_deferred_recv(control_throttle(pstate(State, PState))); + {error, Reason0, PState} -> + {stop, Reason0, pstate(State, PState)} +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) catch throw:{send_failed, Reason1} -> network_error(Reason1, State) end; @@ -442,8 +460,11 @@ maybe_process_deferred_recv(State = #state{ deferred_recv = Data, socket = Sock handle_info({tcp, Sock, Data}, State#state{ deferred_recv = undefined }). +<<<<<<< HEAD maybe_emit_stats(#state{stats_timer = undefined}) -> ok; +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) maybe_emit_stats(State) -> rabbit_event:if_enabled(State, #state.stats_timer, fun() -> emit_stats(State) end). diff --git a/deps/rabbitmq_mqtt/test/java_SUITE_data/pom.xml b/deps/rabbitmq_mqtt/test/java_SUITE_data/pom.xml index 8a84a4b3b1b0..6bc186146a02 100644 --- a/deps/rabbitmq_mqtt/test/java_SUITE_data/pom.xml +++ b/deps/rabbitmq_mqtt/test/java_SUITE_data/pom.xml @@ -15,11 +15,19 @@ [1.2.5,) [1.2.5,) +<<<<<<< HEAD 5.21.0 5.10.3 3.26.3 1.2.13 3.3.0 +======= + 5.23.0 + 5.11.3 + 3.26.3 + 1.2.13 + 3.5.2 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) 2.1.1 2.4.21 3.12.1 diff --git a/deps/rabbitmq_mqtt/test/mc_mqtt_SUITE.erl b/deps/rabbitmq_mqtt/test/mc_mqtt_SUITE.erl index 57e306ab857c..2f1507dd02e2 100644 --- a/deps/rabbitmq_mqtt/test/mc_mqtt_SUITE.erl +++ b/deps/rabbitmq_mqtt/test/mc_mqtt_SUITE.erl @@ -62,6 +62,13 @@ roundtrip_amqp(_Config) -> PayloadSize = 10, ExpectedSize = {MetaDataSize, PayloadSize}, ?assertEqual(ExpectedSize, mc:size(Mc0)), +<<<<<<< HEAD +======= + ?assertEqual(#{<<"x-key-1">> => {utf8, <<"val-1">>}, + <<"x-key-2">> => {utf8, <<"val-2">>}, + <<"x-key-3">> => {utf8, <<"val-3">>}}, + mc:x_headers(Mc0)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) Env = #{}, ?assertEqual(Msg, mc_mqtt:convert_to(mc_mqtt, Msg, Env)), @@ -323,6 +330,10 @@ mqtt_amqpl_alt(_Config) -> }, Anns = #{?ANN_ROUTING_KEYS => [rabbit_mqtt_util:mqtt_to_amqp(Msg#mqtt_msg.topic)]}, Mc = mc:init(mc_mqtt, Msg, Anns), +<<<<<<< HEAD +======= + ?assertEqual(#{}, mc:x_headers(Mc)), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) MsgL = mc:convert(mc_amqpl, Mc), #content{properties = #'P_basic'{headers = HL} = Props} = diff --git a/deps/rabbitmq_mqtt/test/mqtt_shared_SUITE.erl b/deps/rabbitmq_mqtt/test/mqtt_shared_SUITE.erl index fab5013f5413..218f0259ac11 100644 --- a/deps/rabbitmq_mqtt/test/mqtt_shared_SUITE.erl +++ b/deps/rabbitmq_mqtt/test/mqtt_shared_SUITE.erl @@ -80,6 +80,10 @@ cluster_size_1_tests_v3() -> cluster_size_1_tests() -> [ global_counters %% must be the 1st test case +<<<<<<< HEAD +======= + ,message_size_metrics +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ,block_only_publisher ,many_qos1_messages ,session_expiry @@ -697,6 +701,37 @@ global_counters(Config) -> messages_unroutable_returned_total => 1}, get_global_counters(Config, ProtoVer))). +<<<<<<< HEAD +======= +message_size_metrics(Config) -> + Protocol = case ?config(mqtt_version, Config) of + v4 -> mqtt311; + v5 -> mqtt50 + end, + BucketsBefore = rpc(Config, rabbit_msg_size_metrics, raw_buckets, [Protocol]), + + Topic = ClientId = atom_to_binary(?FUNCTION_NAME), + C = connect(ClientId, Config), + {ok, _, [0]} = emqtt:subscribe(C, Topic, qos0), + Payload1B = <<255>>, + Payload500B = binary:copy(Payload1B, 500), + Payload5KB = binary:copy(Payload1B, 5_000), + Payload2MB = binary:copy(Payload1B, 2_000_000), + Payloads = [Payload2MB, Payload5KB, Payload500B, Payload1B, Payload500B], + [ok = emqtt:publish(C, Topic, P, qos0) || P <- Payloads], + ok = expect_publishes(C, Topic, Payloads), + + BucketsAfter = rpc(Config, rabbit_msg_size_metrics, raw_buckets, [Protocol]), + ?assertEqual( + [{100, 1}, + {1000, 2}, + {10_000, 1}, + {10_000_000, 1}], + rabbit_msg_size_metrics:diff_raw_buckets(BucketsAfter, BucketsBefore)), + + ok = emqtt:disconnect(C). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) pubsub(Config) -> Topic0 = <<"t/0">>, Topic1 = <<"t/1">>, diff --git a/deps/rabbitmq_prometheus/BUILD.bazel b/deps/rabbitmq_prometheus/BUILD.bazel index fc7195381644..e6869ebdb792 100644 --- a/deps/rabbitmq_prometheus/BUILD.bazel +++ b/deps/rabbitmq_prometheus/BUILD.bazel @@ -1,12 +1,19 @@ load("@rules_erlang//:eunit2.bzl", "eunit") load("@rules_erlang//:xref2.bzl", "xref") load("@rules_erlang//:dialyze.bzl", "dialyze", "plt") +<<<<<<< HEAD load("//:rabbitmq_home.bzl", "rabbitmq_home") load("//:rabbitmq_run.bzl", "rabbitmq_run") +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) load( "//:rabbitmq.bzl", "RABBITMQ_DIALYZER_OPTS", "assert_suites", +<<<<<<< HEAD +======= + "broker_for_integration_suites", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "rabbitmq_app", "rabbitmq_integration_suite", ) @@ -86,6 +93,7 @@ eunit( target = ":test_erlang_app", ) +<<<<<<< HEAD rabbitmq_home( name = "broker-for-tests-home", plugins = [ @@ -99,6 +107,9 @@ rabbitmq_run( name = "rabbitmq-for-tests-run", home = ":broker-for-tests-home", ) +======= +broker_for_integration_suites() +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) rabbitmq_integration_suite( name = "config_schema_SUITE", @@ -109,10 +120,13 @@ rabbitmq_integration_suite( name = "rabbit_prometheus_http_SUITE", size = "medium", flaky = True, +<<<<<<< HEAD runtime_deps = [ "//deps/rabbitmq_stream:erlang_app", "//deps/rabbitmq_stream_common:erlang_app", ], +======= +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ) assert_suites() diff --git a/deps/rabbitmq_prometheus/app.bzl b/deps/rabbitmq_prometheus/app.bzl index a77dcbb9bb09..0194ff04ddf1 100644 --- a/deps/rabbitmq_prometheus/app.bzl +++ b/deps/rabbitmq_prometheus/app.bzl @@ -14,6 +14,10 @@ def all_beam_files(name = "all_beam_files"): "src/collectors/prometheus_rabbitmq_core_metrics_collector.erl", "src/collectors/prometheus_rabbitmq_dynamic_collector.erl", "src/collectors/prometheus_rabbitmq_global_metrics_collector.erl", +<<<<<<< HEAD +======= + "src/collectors/prometheus_rabbitmq_message_size_metrics_collector.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_prometheus_app.erl", "src/rabbit_prometheus_dispatcher.erl", "src/rabbit_prometheus_handler.erl", @@ -44,6 +48,10 @@ def all_test_beam_files(name = "all_test_beam_files"): "src/collectors/prometheus_rabbitmq_core_metrics_collector.erl", "src/collectors/prometheus_rabbitmq_dynamic_collector.erl", "src/collectors/prometheus_rabbitmq_global_metrics_collector.erl", +<<<<<<< HEAD +======= + "src/collectors/prometheus_rabbitmq_message_size_metrics_collector.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_prometheus_app.erl", "src/rabbit_prometheus_dispatcher.erl", "src/rabbit_prometheus_handler.erl", @@ -85,6 +93,10 @@ def all_srcs(name = "all_srcs"): "src/collectors/prometheus_rabbitmq_core_metrics_collector.erl", "src/collectors/prometheus_rabbitmq_dynamic_collector.erl", "src/collectors/prometheus_rabbitmq_global_metrics_collector.erl", +<<<<<<< HEAD +======= + "src/collectors/prometheus_rabbitmq_message_size_metrics_collector.erl", +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) "src/rabbit_prometheus_app.erl", "src/rabbit_prometheus_dispatcher.erl", "src/rabbit_prometheus_handler.erl", diff --git a/deps/rabbitmq_prometheus/src/collectors/prometheus_rabbitmq_global_metrics_collector.erl b/deps/rabbitmq_prometheus/src/collectors/prometheus_rabbitmq_global_metrics_collector.erl index af2073737724..8ecadd2c392f 100644 --- a/deps/rabbitmq_prometheus/src/collectors/prometheus_rabbitmq_global_metrics_collector.erl +++ b/deps/rabbitmq_prometheus/src/collectors/prometheus_rabbitmq_global_metrics_collector.erl @@ -29,6 +29,7 @@ register() -> ok = prometheus_registry:register_collector(?MODULE). +<<<<<<< HEAD deregister_cleanup(_) -> ok. collect_mf(_Registry, Callback) -> @@ -48,3 +49,18 @@ collect_mf(_Registry, Callback) -> %% =================================================================== %% Private functions %% =================================================================== +======= +deregister_cleanup(_) -> + ok. + +collect_mf(_Registry, Callback) -> + maps:foreach( + fun(Name, #{type := Type, help := Help, values := Values}) -> + Callback( + create_mf(?METRIC_NAME(Name), + Help, + Type, + maps:to_list(Values))) + end, + rabbit_global_counters:prometheus_format()). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) diff --git a/deps/rabbitmq_prometheus/src/collectors/prometheus_rabbitmq_message_size_metrics_collector.erl b/deps/rabbitmq_prometheus/src/collectors/prometheus_rabbitmq_message_size_metrics_collector.erl new file mode 100644 index 000000000000..54a349547744 --- /dev/null +++ b/deps/rabbitmq_prometheus/src/collectors/prometheus_rabbitmq_message_size_metrics_collector.erl @@ -0,0 +1,33 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% +-module(prometheus_rabbitmq_message_size_metrics_collector). + +-behaviour(prometheus_collector). +-include_lib("prometheus/include/prometheus.hrl"). + +-export([register/0, + deregister_cleanup/1, + collect_mf/2]). + +-define(METRIC_NAME_PREFIX, "rabbitmq_"). + +register() -> + ok = prometheus_registry:register_collector(?MODULE). + +deregister_cleanup(_) -> + ok. + +collect_mf(_Registry, Callback) -> + maps:foreach( + fun(Name, #{type := Type, + help := Help, + values := Values}) -> + MetricsFamily = prometheus_model_helpers:create_mf( + ?METRIC_NAME(Name), Help, Type, Values), + Callback(MetricsFamily) + end, + rabbit_msg_size_metrics:prometheus_format()). diff --git a/deps/rabbitmq_prometheus/src/rabbit_prometheus_dispatcher.erl b/deps/rabbitmq_prometheus/src/rabbit_prometheus_dispatcher.erl index 850494e00666..1a8def472d4a 100644 --- a/deps/rabbitmq_prometheus/src/rabbit_prometheus_dispatcher.erl +++ b/deps/rabbitmq_prometheus/src/rabbit_prometheus_dispatcher.erl @@ -16,6 +16,10 @@ build_dispatcher() -> prometheus_registry:register_collectors([ prometheus_rabbitmq_core_metrics_collector, prometheus_rabbitmq_global_metrics_collector, +<<<<<<< HEAD +======= + prometheus_rabbitmq_message_size_metrics_collector, +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) prometheus_rabbitmq_alarm_metrics_collector, prometheus_rabbitmq_dynamic_collector, prometheus_process_collector]), @@ -27,7 +31,12 @@ build_dispatcher() -> prometheus_vm_statistics_collector, prometheus_vm_msacc_collector, prometheus_rabbitmq_core_metrics_collector, +<<<<<<< HEAD prometheus_rabbitmq_global_metrics_collector +======= + prometheus_rabbitmq_global_metrics_collector, + prometheus_rabbitmq_message_size_metrics_collector +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]), prometheus_registry:register_collectors('detailed', [ prometheus_rabbitmq_core_metrics_collector diff --git a/deps/rabbitmq_prometheus/test/rabbit_prometheus_http_SUITE.erl b/deps/rabbitmq_prometheus/test/rabbit_prometheus_http_SUITE.erl index 4aaf622bdcd4..df2246233590 100644 --- a/deps/rabbitmq_prometheus/test/rabbit_prometheus_http_SUITE.erl +++ b/deps/rabbitmq_prometheus/test/rabbit_prometheus_http_SUITE.erl @@ -39,13 +39,23 @@ groups() -> aggregated_metrics_test, specific_erlang_metrics_present_test, global_metrics_present_test, +<<<<<<< HEAD global_metrics_single_metric_family_test +======= + global_metrics_single_metric_family_test, + message_size_metrics_present +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]}, {per_object_metrics, [], [ globally_configure_per_object_metrics_test, specific_erlang_metrics_present_test, global_metrics_present_test, +<<<<<<< HEAD global_metrics_single_metric_family_test +======= + global_metrics_single_metric_family_test, + message_size_metrics_present +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ]}, {per_object_endpoint_metrics, [], [ endpoint_per_object_metrics, @@ -492,6 +502,38 @@ global_metrics_present_test(Config) -> ?assertEqual(match, re:run(Body, "^rabbitmq_global_publishers{", [{capture, none}, multiline])), ?assertEqual(match, re:run(Body, "^rabbitmq_global_consumers{", [{capture, none}, multiline])). +<<<<<<< HEAD +======= +message_size_metrics_present(Config) -> + {_Headers, Body} = http_get_with_pal(Config, [], 200), + + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp091\",le=\"100\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp091\",le=\"1000\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp091\",le=\"10000\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp091\",le=\"100000\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp091\",le=\"1000000\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp091\",le=\"10000000\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp091\",le=\"10000000\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp091\",le=\"50000000\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp091\",le=\"100000000\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp091\",le=\"\\+Inf\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_count{protocol=\"amqp091\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_sum{protocol=\"amqp091\"}", [{capture, none}, multiline])), + + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp10\",le=\"100\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp10\",le=\"1000\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp10\",le=\"10000\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp10\",le=\"100000\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp10\",le=\"1000000\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp10\",le=\"10000000\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp10\",le=\"10000000\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp10\",le=\"50000000\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp10\",le=\"100000000\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_bucket{protocol=\"amqp10\",le=\"\\+Inf\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_count{protocol=\"amqp10\"}", [{capture, none}, multiline])), + ?assertEqual(match, re:run(Body, "^rabbitmq_message_size_bytes_sum{protocol=\"amqp10\"}", [{capture, none}, multiline])). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) global_metrics_single_metric_family_test(Config) -> {_Headers, Body} = http_get_with_pal(Config, [], 200), {match, MetricFamilyMatches} = re:run(Body, "TYPE rabbitmq_global_messages_acknowledged_total", [global]), diff --git a/deps/rabbitmq_stream/test/protocol_interop_SUITE.erl b/deps/rabbitmq_stream/test/protocol_interop_SUITE.erl index 17d28e2f93d6..5ec5a735468a 100644 --- a/deps/rabbitmq_stream/test/protocol_interop_SUITE.erl +++ b/deps/rabbitmq_stream/test/protocol_interop_SUITE.erl @@ -14,6 +14,10 @@ -include_lib("eunit/include/eunit.hrl"). -include_lib("amqp_client/include/amqp_client.hrl"). -include_lib("amqp10_common/include/amqp10_framing.hrl"). +<<<<<<< HEAD +======= +-include_lib("amqp10_common/include/amqp10_filtex.hrl"). +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) all() -> [{group, tests}]. @@ -24,7 +28,12 @@ groups() -> amqpl, amqp_credit_multiple_grants, amqp_credit_single_grant, +<<<<<<< HEAD amqp_attach_sub_batch +======= + amqp_attach_sub_batch, + amqp_filter_expression +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) ] }]. @@ -270,6 +279,54 @@ amqp_attach_sub_batch(Config) -> ok = amqp10_client:detach_link(Receiver), ok = amqp10_client:close_connection(Connection). +<<<<<<< HEAD +======= +%% Test that AMQP filter expressions work when messages +%% are published via the stream protocol and consumed via AMQP. +amqp_filter_expression(Config) -> + Stream = atom_to_binary(?FUNCTION_NAME), + publish_via_stream_protocol(Stream, Config), + + %% Consume from the stream via AMQP 1.0. + OpnConf = connection_config(Config), + {ok, Connection} = amqp10_client:open_connection(OpnConf), + {ok, Session} = amqp10_client:begin_session_sync(Connection), + Address = <<"/queue/", Stream/binary>>, + + AppPropsFilter = [{{utf8, <<"my key">>}, + {utf8, <<"my value">>}}], + {ok, Receiver} = amqp10_client:attach_receiver_link( + Session, <<"test-receiver">>, Address, settled, configuration, + #{<<"rabbitmq:stream-offset-spec">> => <<"first">>, + ?DESCRIPTOR_NAME_APPLICATION_PROPERTIES_FILTER => {map, AppPropsFilter} + }), + + ok = amqp10_client:flow_link_credit(Receiver, 100, never), + receive {amqp10_msg, Receiver, M2} -> + ?assertEqual([<<"m2">>], amqp10_msg:body(M2)) + after 5000 -> ct:fail({missing_msg, ?LINE}) + end, + receive {amqp10_msg, Receiver, M4} -> + ?assertEqual([<<"m4">>], amqp10_msg:body(M4)) + after 5000 -> ct:fail({missing_msg, ?LINE}) + end, + receive {amqp10_msg, Receiver, M5} -> + ?assertEqual([<<"m5">>], amqp10_msg:body(M5)) + after 5000 -> ct:fail({missing_msg, ?LINE}) + end, + receive {amqp10_msg, Receiver, M6} -> + ?assertEqual([<<"m6">>], amqp10_msg:body(M6)) + after 5000 -> ct:fail({missing_msg, ?LINE}) + end, + receive {amqp10_msg, _, _} = Msg -> + ct:fail({received_unexpected_msg, Msg}) + after 10 -> ok + end, + + ok = amqp10_client:detach_link(Receiver), + ok = amqp10_client:close_connection(Connection). + +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) %% ------------------------------------------------------------------- %% Helpers %% ------------------------------------------------------------------- @@ -283,7 +340,13 @@ publish_via_stream_protocol(Stream, Config) -> {ok, C2} = stream_test_utils:declare_publisher(S, C1, Stream, PublisherId), M1 = stream_test_utils:simple_entry(1, <<"m1">>), +<<<<<<< HEAD M2 = stream_test_utils:simple_entry(2, <<"m2">>), +======= + M2 = stream_test_utils:simple_entry(2, <<"m2">>, #'v1_0.application_properties'{ + content = [{{utf8, <<"my key">>}, + {utf8, <<"my value">>}}]}), +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) M3 = stream_test_utils:simple_entry(3, <<"m3">>), Messages1 = [M1, M2, M3], diff --git a/deps/rabbitmq_stream/test/rabbit_stream_SUITE_data/pom.xml b/deps/rabbitmq_stream/test/rabbit_stream_SUITE_data/pom.xml index 6add40e5c989..6de4d2ae589b 100644 --- a/deps/rabbitmq_stream/test/rabbit_stream_SUITE_data/pom.xml +++ b/deps/rabbitmq_stream/test/rabbit_stream_SUITE_data/pom.xml @@ -27,11 +27,19 @@ [0.12.0-SNAPSHOT,) +<<<<<<< HEAD 5.10.3 3.26.3 1.2.13 3.12.1 3.3.0 +======= + 5.11.3 + 3.26.3 + 1.2.13 + 3.12.1 + 3.5.2 +>>>>>>> 5086e283b (Allow building CLI with elixir 1.18.x) 2.43.0 1.17.0 UTF-8 diff --git a/deps/rabbitmq_stream_management/priv/www/js/tmpl/streamConnection.ejs b/deps/rabbitmq_stream_management/priv/www/js/tmpl/streamConnection.ejs index 571293bf4837..c962b21e0aa1 100644 --- a/deps/rabbitmq_stream_management/priv/www/js/tmpl/streamConnection.ejs +++ b/deps/rabbitmq_stream_management/priv/www/js/tmpl/streamConnection.ejs @@ -17,7 +17,11 @@ <% if (connection.client_properties.connection_name) { %>
Client-provided nameClient-provided connection name<%= fmt_string(connection.client_properties.connection_name) %>