From 1eb75b43c444c579c1f758ce7322cec1fe728ae6 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Wed, 23 Aug 2023 21:48:44 +0300 Subject: [PATCH] chore(auth): split emqx_authn and emqx_authz apps --- .github/CODEOWNERS | 4 +- .github/workflows/run_jmeter_tests.yaml | 4 +- apps/emqx/src/emqx_hocon.erl | 14 +- apps/emqx/src/emqx_schema.erl | 22 +- apps/emqx/src/emqx_schema_hooks.erl | 11 +- apps/emqx/test/emqx_common_test_helpers.erl | 4 +- apps/emqx/test/emqx_cth_suite.erl | 4 +- .../data/user-credentials.csv | 0 .../data/user-credentials.json | 0 .../etc/emqx_auth.conf} | 0 apps/emqx_auth/include/emqx_auth.hrl | 22 + .../include/emqx_authn.hrl | 10 +- .../include/emqx_authn_chains.hrl} | 4 +- apps/emqx_auth/include/emqx_authn_schema.hrl | 37 ++ .../include/emqx_authz.hrl | 4 +- apps/emqx_auth/include/emqx_authz_schema.hrl | 34 ++ apps/{emqx_authn => emqx_auth}/rebar.config | 9 +- apps/emqx_auth/src/emqx_auth.app.src | 17 + .../src/emqx_auth.appup.src} | 0 .../src/emqx_auth_app.erl} | 51 +- apps/emqx_auth/src/emqx_auth_schema.erl | 28 + .../src/emqx_auth_sup.erl} | 38 +- .../src/emqx_authn}/emqx_authn.erl | 91 +-- .../src/emqx_authn}/emqx_authn_api.erl | 56 +- .../src/emqx_authn/emqx_authn_chains.erl} | 235 ++++---- .../src/emqx_authn/emqx_authn_config.erl} | 26 +- .../src/emqx_authn/emqx_authn_enterprise.erl | 36 ++ .../emqx_authn_password_hashing.erl | 0 .../src/emqx_authn/emqx_authn_provider.erl | 98 ++++ .../src/emqx_authn}/emqx_authn_schema.erl | 153 ++--- .../src/emqx_authn}/emqx_authn_sup.erl | 24 +- .../emqx_authn_user_import_api.erl | 6 +- .../src/emqx_authn}/emqx_authn_utils.erl | 71 ++- .../emqx_authn}/proto/emqx_authn_proto_v1.erl | 0 .../src/emqx_authz}/emqx_authz.erl | 308 +++++----- .../src/emqx_authz}/emqx_authz_api_cache.erl | 0 .../emqx_authz}/emqx_authz_api_settings.erl | 2 - .../emqx_authz}/emqx_authz_api_sources.erl | 124 ++-- .../src/emqx_authz}/emqx_authz_app.erl | 0 .../emqx_authz}/emqx_authz_client_info.erl | 2 +- .../src/emqx_authz/emqx_authz_enterprise.erl | 24 + .../src/emqx_authz}/emqx_authz_rule.erl | 0 .../src/emqx_authz}/emqx_authz_rule_raw.erl | 0 .../src/emqx_authz/emqx_authz_schema.erl | 231 ++++++++ .../src/emqx_authz/emqx_authz_source.erl | 69 +++ .../emqx_authz/emqx_authz_source_registry.erl | 78 +++ .../src/emqx_authz}/emqx_authz_sup.erl | 0 .../src/emqx_authz}/emqx_authz_utils.erl | 8 +- .../emqx_authz}/proto/emqx_authz_proto_v1.erl | 0 .../test/data/certs/ca.crt | 0 .../test/data/certs/client.crt | 0 .../test/data/certs/client.key | 0 .../test/data/certs/server.crt | 0 .../test/data/certs/server.key | 0 .../test/data/private_key.pem | 0 .../test/data/public_key.pem | 0 .../data/user-credentials-malformed-0.json | 0 .../data/user-credentials-malformed-1.json | 0 .../test/data/user-credentials-malformed.csv | 0 .../test/data/user-credentials.csv | 0 .../test/data/user-credentials.json | 0 .../test/emqx_authn}/emqx_authn_SUITE.erl | 116 ++-- .../test/emqx_authn}/emqx_authn_api_SUITE.erl | 272 +-------- .../emqx_authn/emqx_authn_chains_SUITE.erl} | 30 +- .../emqx_authn_enable_flag_SUITE.erl | 3 +- .../emqx_authn/emqx_authn_fake_provider.erl | 72 +++ .../test/emqx_authn/emqx_authn_init_SUITE.erl | 85 +++ .../emqx_authn_listeners_SUITE.erl | 26 +- .../emqx_authn_password_hashing_SUITE.erl | 0 .../emqx_authn}/emqx_authn_schema_SUITE.erl | 14 +- .../emqx_authn}/emqx_authn_schema_tests.erl | 0 .../test/emqx_authn}/emqx_authn_test_lib.erl | 15 +- .../test/emqx_authz}/emqx_authz_SUITE.erl | 43 +- .../emqx_authz_api_cache_SUITE.erl | 6 +- .../emqx_authz_api_settings_SUITE.erl | 6 +- .../emqx_authz_api_sources_SUITE.erl | 52 +- .../emqx_authz_rich_actions_SUITE.erl | 6 +- .../emqx_authz}/emqx_authz_rule_SUITE.erl | 6 +- .../emqx_authz}/emqx_authz_rule_raw_SUITE.erl | 0 .../emqx_authz}/emqx_authz_schema_tests.erl | 0 .../test/emqx_authz}/emqx_authz_test_lib.erl | 0 .../etc/acl.conf | 0 .../emqx_auth_file/include/emqx_auth_file.hrl | 23 + apps/emqx_auth_file/rebar.config | 7 + .../emqx_auth_file/src/emqx_auth_file.app.src | 18 + .../emqx_auth_file/src/emqx_auth_file_app.erl | 32 ++ .../emqx_auth_file/src/emqx_auth_file_sup.erl | 37 ++ .../src/emqx_authz_file.erl | 90 ++- .../src/emqx_authz_file_schema.erl | 80 +++ .../test/emqx_authz_file_SUITE.erl | 8 +- .../emqx_auth_http/include/emqx_auth_http.hrl | 29 + apps/emqx_auth_http/rebar.config | 6 + .../emqx_auth_http/src/emqx_auth_http.app.src | 19 + .../emqx_auth_http/src/emqx_auth_http_app.erl | 34 ++ .../emqx_auth_http/src/emqx_auth_http_sup.erl | 37 ++ .../src}/emqx_authn_http.erl | 299 ++-------- .../src/emqx_authn_http_schema.erl | 132 +++++ .../src/emqx_authz_http.erl | 25 +- .../src/emqx_authz_http_schema.erl | 179 ++++++ .../test/emqx_authn_http_SUITE.erl | 10 +- .../test/emqx_authn_http_test_server.erl | 0 .../test/emqx_authn_https_SUITE.erl | 6 +- .../test/emqx_authz_http_SUITE.erl | 25 +- .../test/emqx_authz_http_test_server.erl | 0 apps/emqx_auth_jwt/include/emqx_auth_jwt.hrl | 27 + apps/emqx_auth_jwt/rebar.config | 6 + apps/emqx_auth_jwt/src/emqx_auth_jwt.app.src | 21 + apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl | 32 ++ apps/emqx_auth_jwt/src/emqx_auth_jwt_sup.erl | 37 ++ .../src}/emqx_authn_jwks_client.erl | 0 .../src}/emqx_authn_jwks_connector.erl | 0 .../src}/emqx_authn_jwt.erl | 242 +------- .../src/emqx_authn_jwt_schema.erl | 217 +++++++ .../test/emqx_authn_jwt_SUITE.erl | 4 +- .../test/emqx_authz_jwt_SUITE.erl | 26 +- apps/emqx_auth_ldap/BSL.txt | 94 ++++ apps/emqx_auth_ldap/README.md | 0 apps/emqx_auth_ldap/docker-ct | 1 + .../emqx_auth_ldap/include/emqx_auth_ldap.hrl | 35 ++ apps/emqx_auth_ldap/rebar.config | 6 + .../emqx_auth_ldap/src/emqx_auth_ldap.app.src | 17 + .../emqx_auth_ldap/src/emqx_auth_ldap_app.erl | 36 ++ .../emqx_auth_ldap/src/emqx_auth_ldap_sup.erl | 37 ++ .../src/emqx_authn_ldap.erl} | 63 +-- .../src/emqx_authn_ldap_bind.erl} | 58 +- .../src/emqx_authn_ldap_bind_schema.erl | 62 ++ .../src/emqx_authn_ldap_schema.erl | 72 +++ .../src/emqx_authz_ldap.erl} | 44 +- .../src/emqx_authz_ldap_schema.erl | 75 +++ .../test/emqx_authn_ldap_SUITE.erl} | 21 +- .../test/emqx_authn_ldap_bind_SUITE.erl} | 6 +- .../test/emqx_authz_ldap_SUITE.erl} | 8 +- .../include/emqx_auth_mnesia.hrl | 37 ++ apps/emqx_auth_mnesia/rebar.config | 6 + .../src/emqx_auth_mnesia.app.src | 18 + .../src/emqx_auth_mnesia_app.erl | 38 ++ .../src/emqx_auth_mnesia_sup.erl | 37 ++ .../src}/emqx_authn_mnesia.erl | 63 +-- .../src/emqx_authn_mnesia_schema.erl | 58 ++ .../src/emqx_authn_scram_mnesia.erl} | 61 +- .../src/emqx_authn_scram_mnesia_schema.erl | 68 +++ .../src/emqx_authz_api_mnesia.erl | 2 +- .../src/emqx_authz_mnesia.erl | 4 +- .../src/emqx_authz_mnesia_schema.erl | 48 ++ .../test/emqx_authn_api_mnesia_SUITE.erl | 346 ++++++++++++ .../test/emqx_authn_mnesia_SUITE.erl | 4 +- .../test/emqx_authn_mqtt_test_client.erl | 0 .../test/emqx_authn_scram_mnesia_SUITE.erl} | 84 +-- .../test/emqx_authz_api_mnesia_SUITE.erl | 46 +- .../test/emqx_authz_mnesia_SUITE.erl | 20 +- apps/emqx_auth_mongodb/docker-ct | 1 + .../include/emqx_auth_mongodb.hrl | 31 + apps/emqx_auth_mongodb/rebar.config | 6 + .../src/emqx_auth_mongodb.app.src | 19 + .../src/emqx_auth_mongodb_app.erl | 34 ++ .../src/emqx_auth_mongodb_sup.erl | 37 ++ .../src}/emqx_authn_mongodb.erl | 121 +--- .../src/emqx_authn_mongodb_schema.erl | 124 ++++ .../src/emqx_authz_mongodb.erl | 6 +- .../src/emqx_authz_mongodb_schema.erl | 93 +++ .../test/emqx_authn_mongodb_SUITE.erl} | 13 +- .../test/emqx_authn_mongodb_tls_SUITE.erl} | 7 +- .../test/emqx_authz_mongodb_SUITE.erl | 28 +- apps/emqx_auth_mysql/docker-ct | 1 + .../include/emqx_auth_mysql.hrl | 31 + apps/emqx_auth_mysql/rebar.config | 6 + .../src/emqx_auth_mysql.app.src | 19 + .../src/emqx_auth_mysql_app.erl | 34 ++ .../src/emqx_auth_mysql_sup.erl | 37 ++ .../src}/emqx_authn_mysql.erl | 57 +- .../src/emqx_authn_mysql_schema.erl | 71 +++ .../src/emqx_authz_mysql.erl | 6 +- .../src/emqx_authz_mysql_schema.erl | 66 +++ .../test/emqx_authn_mysql_SUITE.erl | 13 +- .../test/emqx_authn_mysql_tls_SUITE.erl | 5 +- .../test/emqx_authz_mysql_SUITE.erl | 35 +- apps/emqx_auth_postgresql/docker-ct | 1 + .../include/emqx_auth_postgresql.hrl | 31 + apps/emqx_auth_postgresql/rebar.config | 6 + .../src/emqx_auth_postgresql.app.src | 19 + .../src/emqx_auth_postgresql_app.erl | 34 ++ .../src/emqx_auth_postgresql_sup.erl | 37 ++ .../src/emqx_authn_postgresql.erl} | 53 +- .../src/emqx_authn_postgresql_schema.erl | 66 +++ .../src/emqx_authz_postgresql.erl | 6 +- .../src/emqx_authz_postgresql_schema.erl | 66 +++ .../test/emqx_authn_postgresql_SUITE.erl} | 25 +- .../test/emqx_authn_postgresql_tls_SUITE.erl} | 7 +- .../test/emqx_authz_postgresql_SUITE.erl | 27 +- apps/emqx_auth_redis/docker-ct | 1 + .../include/emqx_auth_redis.hrl | 31 + apps/emqx_auth_redis/rebar.config | 6 + .../src/emqx_auth_redis.app.src | 19 + .../src/emqx_auth_redis_app.erl | 34 ++ .../src/emqx_auth_redis_sup.erl | 37 ++ .../src}/emqx_authn_redis.erl | 12 +- .../src/emqx_authn_redis_schema.erl | 91 +++ .../src/emqx_authz_redis.erl | 6 +- .../src/emqx_authz_redis_schema.erl | 93 +++ .../test/emqx_authn_redis_SUITE.erl | 23 +- .../test/emqx_authn_redis_tls_SUITE.erl | 5 +- .../test/emqx_authz_redis_SUITE.erl | 31 +- apps/emqx_authn/docker-ct | 4 - apps/emqx_authn/src/emqx_authn.app.src | 26 - apps/emqx_authn/src/emqx_authn_enterprise.erl | 28 - apps/emqx_authz/.gitignore | 19 - apps/emqx_authz/README.md | 145 ----- apps/emqx_authz/docker-ct | 4 - apps/emqx_authz/etc/emqx_authz.conf | 1 - apps/emqx_authz/rebar.config | 19 - apps/emqx_authz/src/emqx_authz.app.src | 23 - apps/emqx_authz/src/emqx_authz.appup.src | 11 - apps/emqx_authz/src/emqx_authz_api_schema.erl | 293 ---------- apps/emqx_authz/src/emqx_authz_enterprise.erl | 66 --- apps/emqx_authz/src/emqx_authz_schema.erl | 531 ------------------ .../test/emqx_bridge_api_SUITE.erl | 6 +- apps/emqx_conf/rebar.config | 2 +- apps/emqx_conf/src/emqx_conf_cli.erl | 2 +- apps/emqx_conf/src/emqx_conf_schema.erl | 3 +- apps/emqx_conf/test/emqx_conf_cli_SUITE.erl | 4 +- apps/emqx_gateway/README.md | 2 +- apps/emqx_gateway/rebar.config | 2 +- apps/emqx_gateway/src/emqx_gateway.app.src | 2 +- apps/emqx_gateway/src/emqx_gateway.erl | 2 +- .../emqx_gateway_api_authn_user_import.erl | 4 +- apps/emqx_gateway/src/emqx_gateway_conf.erl | 14 +- apps/emqx_gateway/src/emqx_gateway_http.erl | 4 +- .../src/emqx_gateway_insta_sup.erl | 10 +- apps/emqx_gateway/src/emqx_gateway_schema.erl | 2 +- apps/emqx_gateway/src/emqx_gateway_utils.erl | 2 +- apps/emqx_gateway/test/emqx_gateway_SUITE.erl | 4 +- .../test/emqx_gateway_api_SUITE.erl | 8 +- .../test/emqx_gateway_auth_ct.erl | 2 +- .../test/emqx_gateway_authn_SUITE.erl | 4 +- .../test/emqx_gateway_authz_SUITE.erl | 8 +- .../test/emqx_gateway_cli_SUITE.erl | 4 +- .../test/emqx_gateway_conf_SUITE.erl | 4 +- .../test/emqx_gateway_registry_SUITE.erl | 4 +- .../test/emqx_coap_SUITE.erl | 4 +- .../test/emqx_coap_api_SUITE.erl | 4 +- .../test/emqx_exproto_SUITE.erl | 4 +- .../test/emqx_lwm2m_api_SUITE.erl | 4 +- .../test/emqx_sn_protocol_SUITE.erl | 4 +- .../test/emqx_stomp_SUITE.erl | 4 +- .../include/emqx_gcp_device.hrl | 25 + apps/emqx_gcp_device/rebar.config | 2 +- .../src/emqx_gcp_device.app.src | 2 +- apps/emqx_gcp_device/src/emqx_gcp_device.erl | 9 +- .../src/emqx_gcp_device_app.erl | 5 +- .../src/emqx_gcp_device_authn.erl | 45 +- .../src/emqx_gcp_device_authn_schema.erl | 45 ++ .../test/emqx_gcp_device_SUITE.erl | 4 +- .../test/emqx_gcp_device_api_SUITE.erl | 4 +- .../test/emqx_gcp_device_authn_SUITE.erl | 10 +- apps/emqx_ldap/README.md | 2 +- apps/emqx_ldap/rebar.config | 4 +- apps/emqx_ldap/src/emqx_ldap.app.src | 4 +- apps/emqx_machine/priv/reboot_lists.eterm | 12 +- apps/emqx_machine/test/emqx_machine_SUITE.erl | 19 +- .../src/emqx_mgmt_data_backup.erl | 4 +- .../test/emqx_mgmt_data_backup_SUITE.erl | 15 +- apps/emqx_mongodb/README.md | 2 +- apps/emqx_mysql/README.md | 3 +- apps/emqx_mysql/src/emqx_mysql.app.src | 4 +- apps/emqx_redis/README.md | 3 +- apps/emqx_redis/src/emqx_redis.app.src | 6 +- apps/emqx_redis/src/emqx_redis.erl | 6 +- .../test/emqx_rule_engine_SUITE.erl | 4 +- .../test/emqx_telemetry_SUITE.erl | 62 +- .../test/emqx_telemetry_api_SUITE.erl | 4 +- changes/v5.0.15-en.md | 8 +- changes/v5.0.15-zh.md | 8 +- dev | 2 +- mix.exs | 5 +- rebar.config.erl | 2 +- ...ttp.hocon => emqx_authn_http_schema.hocon} | 2 +- ..._jwt.hocon => emqx_authn_jwt_schema.hocon} | 2 +- ...ocon => emqx_authn_ldap_bind_schema.hocon} | 2 +- ...thn.hocon => emqx_authn_ldap_schema.hocon} | 2 +- ...a.hocon => emqx_authn_mnesia_schema.hocon} | 2 +- ....hocon => emqx_authn_mongodb_schema.hocon} | 2 +- ...ql.hocon => emqx_authn_mysql_schema.hocon} | 2 +- ...con => emqx_authn_postgresql_schema.hocon} | 2 +- ...is.hocon => emqx_authn_redis_schema.hocon} | 2 +- rel/i18n/emqx_authz_api_schema.hocon | 90 --- rel/i18n/emqx_authz_file_schema.hocon | 22 + rel/i18n/emqx_authz_http_schema.hocon | 51 ++ ...thz.hocon => emqx_authz_ldap_schema.hocon} | 2 +- rel/i18n/emqx_authz_mnesia_schema.hocon | 9 + rel/i18n/emqx_authz_mongodb_schema.hocon | 36 ++ rel/i18n/emqx_authz_mysql_schema.hocon | 15 + rel/i18n/emqx_authz_postgresql_schema.hocon | 15 + rel/i18n/emqx_authz_redis_schema.hocon | 27 + rel/i18n/emqx_authz_schema.hocon | 148 ----- scripts/merge-config.escript | 2 +- 295 files changed, 5746 insertions(+), 3974 deletions(-) rename apps/{emqx_authn => emqx_auth}/data/user-credentials.csv (100%) rename apps/{emqx_authn => emqx_auth}/data/user-credentials.json (100%) rename apps/{emqx_authn/etc/emqx_authn.conf => emqx_auth/etc/emqx_auth.conf} (100%) create mode 100644 apps/emqx_auth/include/emqx_auth.hrl rename apps/{emqx_authn => emqx_auth}/include/emqx_authn.hrl (86%) rename apps/{emqx_authn/include/emqx_authentication.hrl => emqx_auth/include/emqx_authn_chains.hrl} (96%) create mode 100644 apps/emqx_auth/include/emqx_authn_schema.hrl rename apps/{emqx_authz => emqx_auth}/include/emqx_authz.hrl (98%) create mode 100644 apps/emqx_auth/include/emqx_authz_schema.hrl rename apps/{emqx_authn => emqx_auth}/rebar.config (62%) create mode 100644 apps/emqx_auth/src/emqx_auth.app.src rename apps/{emqx_authn/src/emqx_authn.appup.src => emqx_auth/src/emqx_auth.appup.src} (100%) rename apps/{emqx_authn/src/emqx_authn_app.erl => emqx_auth/src/emqx_auth_app.erl} (54%) create mode 100644 apps/emqx_auth/src/emqx_auth_schema.erl rename apps/{emqx_authn/src/emqx_authentication_sup.erl => emqx_auth/src/emqx_auth_sup.erl} (61%) rename apps/{emqx_authn/src => emqx_auth/src/emqx_authn}/emqx_authn.erl (56%) rename apps/{emqx_authn/src => emqx_auth/src/emqx_authn}/emqx_authn_api.erl (96%) rename apps/{emqx_authn/src/emqx_authentication.erl => emqx_auth/src/emqx_authn/emqx_authn_chains.erl} (88%) rename apps/{emqx_authn/src/emqx_authentication_config.erl => emqx_auth/src/emqx_authn/emqx_authn_config.erl} (94%) create mode 100644 apps/emqx_auth/src/emqx_authn/emqx_authn_enterprise.erl rename apps/{emqx_authn/src => emqx_auth/src/emqx_authn}/emqx_authn_password_hashing.erl (100%) create mode 100644 apps/emqx_auth/src/emqx_authn/emqx_authn_provider.erl rename apps/{emqx_authn/src => emqx_auth/src/emqx_authn}/emqx_authn_schema.erl (62%) rename apps/{emqx_authn/src => emqx_auth/src/emqx_authn}/emqx_authn_sup.erl (73%) rename apps/{emqx_authn/src => emqx_auth/src/emqx_authn}/emqx_authn_user_import_api.erl (94%) rename apps/{emqx_authn/src => emqx_auth/src/emqx_authn}/emqx_authn_utils.erl (76%) rename apps/{emqx_authn/src => emqx_auth/src/emqx_authn}/proto/emqx_authn_proto_v1.erl (100%) rename apps/{emqx_authz/src => emqx_auth/src/emqx_authz}/emqx_authz.erl (80%) rename apps/{emqx_authz/src => emqx_auth/src/emqx_authz}/emqx_authz_api_cache.erl (100%) rename apps/{emqx_authz/src => emqx_auth/src/emqx_authz}/emqx_authz_api_settings.erl (98%) rename apps/{emqx_authz/src => emqx_auth/src/emqx_authz}/emqx_authz_api_sources.erl (86%) rename apps/{emqx_authz/src => emqx_auth/src/emqx_authz}/emqx_authz_app.erl (100%) rename apps/{emqx_authz/src => emqx_auth/src/emqx_authz}/emqx_authz_client_info.erl (99%) create mode 100644 apps/emqx_auth/src/emqx_authz/emqx_authz_enterprise.erl rename apps/{emqx_authz/src => emqx_auth/src/emqx_authz}/emqx_authz_rule.erl (100%) rename apps/{emqx_authz/src => emqx_auth/src/emqx_authz}/emqx_authz_rule_raw.erl (100%) create mode 100644 apps/emqx_auth/src/emqx_authz/emqx_authz_schema.erl create mode 100644 apps/emqx_auth/src/emqx_authz/emqx_authz_source.erl create mode 100644 apps/emqx_auth/src/emqx_authz/emqx_authz_source_registry.erl rename apps/{emqx_authz/src => emqx_auth/src/emqx_authz}/emqx_authz_sup.erl (100%) rename apps/{emqx_authz/src => emqx_auth/src/emqx_authz}/emqx_authz_utils.erl (97%) rename apps/{emqx_authz/src => emqx_auth/src/emqx_authz}/proto/emqx_authz_proto_v1.erl (100%) rename apps/{emqx_authn => emqx_auth}/test/data/certs/ca.crt (100%) rename apps/{emqx_authn => emqx_auth}/test/data/certs/client.crt (100%) rename apps/{emqx_authn => emqx_auth}/test/data/certs/client.key (100%) rename apps/{emqx_authn => emqx_auth}/test/data/certs/server.crt (100%) rename apps/{emqx_authn => emqx_auth}/test/data/certs/server.key (100%) rename apps/{emqx_authn => emqx_auth}/test/data/private_key.pem (100%) rename apps/{emqx_authn => emqx_auth}/test/data/public_key.pem (100%) rename apps/{emqx_authn => emqx_auth}/test/data/user-credentials-malformed-0.json (100%) rename apps/{emqx_authn => emqx_auth}/test/data/user-credentials-malformed-1.json (100%) rename apps/{emqx_authn => emqx_auth}/test/data/user-credentials-malformed.csv (100%) rename apps/{emqx_authn => emqx_auth}/test/data/user-credentials.csv (100%) rename apps/{emqx_authn => emqx_auth}/test/data/user-credentials.json (100%) rename apps/{emqx_authn/test => emqx_auth/test/emqx_authn}/emqx_authn_SUITE.erl (79%) rename apps/{emqx_authn/test => emqx_auth/test/emqx_authn}/emqx_authn_api_SUITE.erl (64%) rename apps/{emqx_authn/test/emqx_authentication_SUITE.erl => emqx_auth/test/emqx_authn/emqx_authn_chains_SUITE.erl} (95%) rename apps/{emqx_authn/test => emqx_auth/test/emqx_authn}/emqx_authn_enable_flag_SUITE.erl (95%) create mode 100644 apps/emqx_auth/test/emqx_authn/emqx_authn_fake_provider.erl create mode 100644 apps/emqx_auth/test/emqx_authn/emqx_authn_init_SUITE.erl rename apps/{emqx_authn/test => emqx_auth/test/emqx_authn}/emqx_authn_listeners_SUITE.erl (90%) rename apps/{emqx_authn/test => emqx_auth/test/emqx_authn}/emqx_authn_password_hashing_SUITE.erl (100%) rename apps/{emqx_authn/test => emqx_auth/test/emqx_authn}/emqx_authn_schema_SUITE.erl (93%) rename apps/{emqx_authn/test => emqx_auth/test/emqx_authn}/emqx_authn_schema_tests.erl (100%) rename apps/{emqx_authn/test => emqx_auth/test/emqx_authn}/emqx_authn_test_lib.erl (82%) rename apps/{emqx_authz/test => emqx_auth/test/emqx_authz}/emqx_authz_SUITE.erl (95%) rename apps/{emqx_authz/test => emqx_auth/test/emqx_authz}/emqx_authz_api_cache_SUITE.erl (95%) rename apps/{emqx_authz/test => emqx_auth/test/emqx_authz}/emqx_authz_api_settings_SUITE.erl (95%) rename apps/{emqx_authz/test => emqx_auth/test/emqx_authz}/emqx_authz_api_sources_SUITE.erl (94%) rename apps/{emqx_authz/test => emqx_auth/test/emqx_authz}/emqx_authz_rich_actions_SUITE.erl (96%) rename apps/{emqx_authz/test => emqx_auth/test/emqx_authz}/emqx_authz_rule_SUITE.erl (99%) rename apps/{emqx_authz/test => emqx_auth/test/emqx_authz}/emqx_authz_rule_raw_SUITE.erl (100%) rename apps/{emqx_authz/test => emqx_auth/test/emqx_authz}/emqx_authz_schema_tests.erl (100%) rename apps/{emqx_authz/test => emqx_auth/test/emqx_authz}/emqx_authz_test_lib.erl (100%) rename apps/{emqx_authz => emqx_auth_file}/etc/acl.conf (100%) create mode 100644 apps/emqx_auth_file/include/emqx_auth_file.hrl create mode 100644 apps/emqx_auth_file/rebar.config create mode 100644 apps/emqx_auth_file/src/emqx_auth_file.app.src create mode 100644 apps/emqx_auth_file/src/emqx_auth_file_app.erl create mode 100644 apps/emqx_auth_file/src/emqx_auth_file_sup.erl rename apps/{emqx_authz => emqx_auth_file}/src/emqx_authz_file.erl (54%) create mode 100644 apps/emqx_auth_file/src/emqx_authz_file_schema.erl rename apps/{emqx_authz => emqx_auth_file}/test/emqx_authz_file_SUITE.erl (96%) create mode 100644 apps/emqx_auth_http/include/emqx_auth_http.hrl create mode 100644 apps/emqx_auth_http/rebar.config create mode 100644 apps/emqx_auth_http/src/emqx_auth_http.app.src create mode 100644 apps/emqx_auth_http/src/emqx_auth_http_app.erl create mode 100644 apps/emqx_auth_http/src/emqx_auth_http_sup.erl rename apps/{emqx_authn/src/simple_authn => emqx_auth_http/src}/emqx_authn_http.erl (51%) create mode 100644 apps/emqx_auth_http/src/emqx_authn_http_schema.erl rename apps/{emqx_authz => emqx_auth_http}/src/emqx_authz_http.erl (91%) create mode 100644 apps/emqx_auth_http/src/emqx_authz_http_schema.erl rename apps/{emqx_authn => emqx_auth_http}/test/emqx_authn_http_SUITE.erl (98%) rename apps/{emqx_authn => emqx_auth_http}/test/emqx_authn_http_test_server.erl (100%) rename apps/{emqx_authn => emqx_auth_http}/test/emqx_authn_https_SUITE.erl (98%) rename apps/{emqx_authz => emqx_auth_http}/test/emqx_authz_http_SUITE.erl (97%) rename apps/{emqx_authz => emqx_auth_http}/test/emqx_authz_http_test_server.erl (100%) create mode 100644 apps/emqx_auth_jwt/include/emqx_auth_jwt.hrl create mode 100644 apps/emqx_auth_jwt/rebar.config create mode 100644 apps/emqx_auth_jwt/src/emqx_auth_jwt.app.src create mode 100644 apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl create mode 100644 apps/emqx_auth_jwt/src/emqx_auth_jwt_sup.erl rename apps/{emqx_authn/src/simple_authn => emqx_auth_jwt/src}/emqx_authn_jwks_client.erl (100%) rename apps/{emqx_authn/src/simple_authn => emqx_auth_jwt/src}/emqx_authn_jwks_connector.erl (100%) rename apps/{emqx_authn/src/simple_authn => emqx_auth_jwt/src}/emqx_authn_jwt.erl (64%) create mode 100644 apps/emqx_auth_jwt/src/emqx_authn_jwt_schema.erl rename apps/{emqx_authn => emqx_auth_jwt}/test/emqx_authn_jwt_SUITE.erl (99%) rename apps/{emqx_authz => emqx_auth_jwt}/test/emqx_authz_jwt_SUITE.erl (95%) create mode 100644 apps/emqx_auth_ldap/BSL.txt create mode 100644 apps/emqx_auth_ldap/README.md create mode 100644 apps/emqx_auth_ldap/docker-ct create mode 100644 apps/emqx_auth_ldap/include/emqx_auth_ldap.hrl create mode 100644 apps/emqx_auth_ldap/rebar.config create mode 100644 apps/emqx_auth_ldap/src/emqx_auth_ldap.app.src create mode 100644 apps/emqx_auth_ldap/src/emqx_auth_ldap_app.erl create mode 100644 apps/emqx_auth_ldap/src/emqx_auth_ldap_sup.erl rename apps/{emqx_ldap/src/emqx_ldap_authn.erl => emqx_auth_ldap/src/emqx_authn_ldap.erl} (80%) rename apps/{emqx_ldap/src/emqx_ldap_authn_bind.erl => emqx_auth_ldap/src/emqx_authn_ldap_bind.erl} (61%) create mode 100644 apps/emqx_auth_ldap/src/emqx_authn_ldap_bind_schema.erl create mode 100644 apps/emqx_auth_ldap/src/emqx_authn_ldap_schema.erl rename apps/{emqx_ldap/src/emqx_ldap_authz.erl => emqx_auth_ldap/src/emqx_authz_ldap.erl} (71%) create mode 100644 apps/emqx_auth_ldap/src/emqx_authz_ldap_schema.erl rename apps/{emqx_ldap/test/emqx_ldap_authn_SUITE.erl => emqx_auth_ldap/test/emqx_authn_ldap_SUITE.erl} (92%) rename apps/{emqx_ldap/test/emqx_ldap_authn_bind_SUITE.erl => emqx_auth_ldap/test/emqx_authn_ldap_bind_SUITE.erl} (98%) rename apps/{emqx_ldap/test/emqx_ldap_authz_SUITE.erl => emqx_auth_ldap/test/emqx_authz_ldap_SUITE.erl} (97%) create mode 100644 apps/emqx_auth_mnesia/include/emqx_auth_mnesia.hrl create mode 100644 apps/emqx_auth_mnesia/rebar.config create mode 100644 apps/emqx_auth_mnesia/src/emqx_auth_mnesia.app.src create mode 100644 apps/emqx_auth_mnesia/src/emqx_auth_mnesia_app.erl create mode 100644 apps/emqx_auth_mnesia/src/emqx_auth_mnesia_sup.erl rename apps/{emqx_authn/src/simple_authn => emqx_auth_mnesia/src}/emqx_authn_mnesia.erl (90%) create mode 100644 apps/emqx_auth_mnesia/src/emqx_authn_mnesia_schema.erl rename apps/{emqx_authn/src/enhanced_authn/emqx_enhanced_authn_scram_mnesia.erl => emqx_auth_mnesia/src/emqx_authn_scram_mnesia.erl} (88%) create mode 100644 apps/emqx_auth_mnesia/src/emqx_authn_scram_mnesia_schema.erl rename apps/{emqx_authz => emqx_auth_mnesia}/src/emqx_authz_api_mnesia.erl (99%) rename apps/{emqx_authz => emqx_auth_mnesia}/src/emqx_authz_mnesia.erl (98%) create mode 100644 apps/emqx_auth_mnesia/src/emqx_authz_mnesia_schema.erl create mode 100644 apps/emqx_auth_mnesia/test/emqx_authn_api_mnesia_SUITE.erl rename apps/{emqx_authn => emqx_auth_mnesia}/test/emqx_authn_mnesia_SUITE.erl (98%) rename apps/{emqx_authn => emqx_auth_mnesia}/test/emqx_authn_mqtt_test_client.erl (100%) rename apps/{emqx_authn/test/emqx_enhanced_authn_scram_mnesia_SUITE.erl => emqx_auth_mnesia/test/emqx_authn_scram_mnesia_SUITE.erl} (79%) rename apps/{emqx_authz => emqx_auth_mnesia}/test/emqx_authz_api_mnesia_SUITE.erl (91%) rename apps/{emqx_authz => emqx_auth_mnesia}/test/emqx_authz_mnesia_SUITE.erl (95%) create mode 100644 apps/emqx_auth_mongodb/docker-ct create mode 100644 apps/emqx_auth_mongodb/include/emqx_auth_mongodb.hrl create mode 100644 apps/emqx_auth_mongodb/rebar.config create mode 100644 apps/emqx_auth_mongodb/src/emqx_auth_mongodb.app.src create mode 100644 apps/emqx_auth_mongodb/src/emqx_auth_mongodb_app.erl create mode 100644 apps/emqx_auth_mongodb/src/emqx_auth_mongodb_sup.erl rename apps/{emqx_authn/src/simple_authn => emqx_auth_mongodb/src}/emqx_authn_mongodb.erl (62%) create mode 100644 apps/emqx_auth_mongodb/src/emqx_authn_mongodb_schema.erl rename apps/{emqx_authz => emqx_auth_mongodb}/src/emqx_authz_mongodb.erl (96%) create mode 100644 apps/emqx_auth_mongodb/src/emqx_authz_mongodb_schema.erl rename apps/{emqx_authn/test/emqx_authn_mongo_SUITE.erl => emqx_auth_mongodb/test/emqx_authn_mongodb_SUITE.erl} (97%) rename apps/{emqx_authn/test/emqx_authn_mongo_tls_SUITE.erl => emqx_auth_mongodb/test/emqx_authn_mongodb_tls_SUITE.erl} (97%) rename apps/{emqx_authz => emqx_auth_mongodb}/test/emqx_authz_mongodb_SUITE.erl (96%) create mode 100644 apps/emqx_auth_mysql/docker-ct create mode 100644 apps/emqx_auth_mysql/include/emqx_auth_mysql.hrl create mode 100644 apps/emqx_auth_mysql/rebar.config create mode 100644 apps/emqx_auth_mysql/src/emqx_auth_mysql.app.src create mode 100644 apps/emqx_auth_mysql/src/emqx_auth_mysql_app.erl create mode 100644 apps/emqx_auth_mysql/src/emqx_auth_mysql_sup.erl rename apps/{emqx_authn/src/simple_authn => emqx_auth_mysql/src}/emqx_authn_mysql.erl (72%) create mode 100644 apps/emqx_auth_mysql/src/emqx_authn_mysql_schema.erl rename apps/{emqx_authz => emqx_auth_mysql}/src/emqx_authz_mysql.erl (96%) create mode 100644 apps/emqx_auth_mysql/src/emqx_authz_mysql_schema.erl rename apps/{emqx_authn => emqx_auth_mysql}/test/emqx_authn_mysql_SUITE.erl (97%) rename apps/{emqx_authn => emqx_auth_mysql}/test/emqx_authn_mysql_tls_SUITE.erl (97%) rename apps/{emqx_authz => emqx_auth_mysql}/test/emqx_authz_mysql_SUITE.erl (96%) create mode 100644 apps/emqx_auth_postgresql/docker-ct create mode 100644 apps/emqx_auth_postgresql/include/emqx_auth_postgresql.hrl create mode 100644 apps/emqx_auth_postgresql/rebar.config create mode 100644 apps/emqx_auth_postgresql/src/emqx_auth_postgresql.app.src create mode 100644 apps/emqx_auth_postgresql/src/emqx_auth_postgresql_app.erl create mode 100644 apps/emqx_auth_postgresql/src/emqx_auth_postgresql_sup.erl rename apps/{emqx_authn/src/simple_authn/emqx_authn_pgsql.erl => emqx_auth_postgresql/src/emqx_authn_postgresql.erl} (74%) create mode 100644 apps/emqx_auth_postgresql/src/emqx_authn_postgresql_schema.erl rename apps/{emqx_authz => emqx_auth_postgresql}/src/emqx_authz_postgresql.erl (96%) create mode 100644 apps/emqx_auth_postgresql/src/emqx_authz_postgresql_schema.erl rename apps/{emqx_authn/test/emqx_authn_pgsql_SUITE.erl => emqx_auth_postgresql/test/emqx_authn_postgresql_SUITE.erl} (96%) rename apps/{emqx_authn/test/emqx_authn_pgsql_tls_SUITE.erl => emqx_auth_postgresql/test/emqx_authn_postgresql_tls_SUITE.erl} (96%) rename apps/{emqx_authz => emqx_auth_postgresql}/test/emqx_authz_postgresql_SUITE.erl (96%) create mode 100644 apps/emqx_auth_redis/docker-ct create mode 100644 apps/emqx_auth_redis/include/emqx_auth_redis.hrl create mode 100644 apps/emqx_auth_redis/rebar.config create mode 100644 apps/emqx_auth_redis/src/emqx_auth_redis.app.src create mode 100644 apps/emqx_auth_redis/src/emqx_auth_redis_app.erl create mode 100644 apps/emqx_auth_redis/src/emqx_auth_redis_sup.erl rename apps/{emqx_authn/src/simple_authn => emqx_auth_redis/src}/emqx_authn_redis.erl (98%) create mode 100644 apps/emqx_auth_redis/src/emqx_authn_redis_schema.erl rename apps/{emqx_authz => emqx_auth_redis}/src/emqx_authz_redis.erl (97%) create mode 100644 apps/emqx_auth_redis/src/emqx_authz_redis_schema.erl rename apps/{emqx_authn => emqx_auth_redis}/test/emqx_authn_redis_SUITE.erl (96%) rename apps/{emqx_authn => emqx_auth_redis}/test/emqx_authn_redis_tls_SUITE.erl (97%) rename apps/{emqx_authz => emqx_auth_redis}/test/emqx_authz_redis_SUITE.erl (94%) delete mode 100644 apps/emqx_authn/docker-ct delete mode 100644 apps/emqx_authn/src/emqx_authn.app.src delete mode 100644 apps/emqx_authn/src/emqx_authn_enterprise.erl delete mode 100644 apps/emqx_authz/.gitignore delete mode 100644 apps/emqx_authz/README.md delete mode 100644 apps/emqx_authz/docker-ct delete mode 100644 apps/emqx_authz/etc/emqx_authz.conf delete mode 100644 apps/emqx_authz/rebar.config delete mode 100644 apps/emqx_authz/src/emqx_authz.app.src delete mode 100644 apps/emqx_authz/src/emqx_authz.appup.src delete mode 100644 apps/emqx_authz/src/emqx_authz_api_schema.erl delete mode 100644 apps/emqx_authz/src/emqx_authz_enterprise.erl delete mode 100644 apps/emqx_authz/src/emqx_authz_schema.erl create mode 100644 apps/emqx_gcp_device/include/emqx_gcp_device.hrl create mode 100644 apps/emqx_gcp_device/src/emqx_gcp_device_authn_schema.erl rename rel/i18n/{emqx_authn_http.hocon => emqx_authn_http_schema.hocon} (96%) rename rel/i18n/{emqx_authn_jwt.hocon => emqx_authn_jwt_schema.hocon} (99%) rename rel/i18n/{emqx_ldap_authn_bind.hocon => emqx_authn_ldap_bind_schema.hocon} (87%) rename rel/i18n/{emqx_ldap_authn.hocon => emqx_authn_ldap_schema.hocon} (95%) rename rel/i18n/{emqx_authn_mnesia.hocon => emqx_authn_mnesia_schema.hocon} (89%) rename rel/i18n/{emqx_authn_mongodb.hocon => emqx_authn_mongodb_schema.hocon} (97%) rename rel/i18n/{emqx_authn_mysql.hocon => emqx_authn_mysql_schema.hocon} (92%) rename rel/i18n/{emqx_authn_pgsql.hocon => emqx_authn_postgresql_schema.hocon} (87%) rename rel/i18n/{emqx_authn_redis.hocon => emqx_authn_redis_schema.hocon} (95%) delete mode 100644 rel/i18n/emqx_authz_api_schema.hocon create mode 100644 rel/i18n/emqx_authz_file_schema.hocon create mode 100644 rel/i18n/emqx_authz_http_schema.hocon rename rel/i18n/{emqx_ldap_authz.hocon => emqx_authz_ldap_schema.hocon} (96%) create mode 100644 rel/i18n/emqx_authz_mnesia_schema.hocon create mode 100644 rel/i18n/emqx_authz_mongodb_schema.hocon create mode 100644 rel/i18n/emqx_authz_mysql_schema.hocon create mode 100644 rel/i18n/emqx_authz_postgresql_schema.hocon create mode 100644 rel/i18n/emqx_authz_redis_schema.hocon diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8bb31ab71..23911f9a8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,9 +3,9 @@ ## apps /apps/emqx/ @emqx/emqx-review-board @lafirest -/apps/emqx_authn/ @emqx/emqx-review-board @JimMoen @savonarola -/apps/emqx_authz/ @emqx/emqx-review-board @JimMoen @savonarola /apps/emqx_connector/ @emqx/emqx-review-board +/apps/emqx_auth/ @emqx/emqx-review-board @JimMoen @savonarola +/apps/emqx_connector/ @emqx/emqx-review-board @JimMoen /apps/emqx_dashboard/ @emqx/emqx-review-board @JimMoen @lafirest /apps/emqx_dashboard_rbac/ @emqx/emqx-review-board @lafirest /apps/emqx_dashboard_sso/ @emqx/emqx-review-board @JimMoen @lafirest diff --git a/.github/workflows/run_jmeter_tests.yaml b/.github/workflows/run_jmeter_tests.yaml index d45b66324..9a36d8796 100644 --- a/.github/workflows/run_jmeter_tests.yaml +++ b/.github/workflows/run_jmeter_tests.yaml @@ -132,7 +132,7 @@ jobs: -Dpgsql_user="root" \ -Dpgsql_pwd="public" \ -Ddbname="mqtt" \ - -Droute="apps/emqx_authn/test/data/certs" \ + -Droute="apps/emqx_auth/test/data/certs" \ -Dca_name="ca.crt" \ -Dkey_name="client.key" \ -Dcert_name="client.crt" \ @@ -195,7 +195,7 @@ jobs: -Dmysql_user="root" \ -Dmysql_pwd="public" \ -Ddbname="mqtt" \ - -Droute="apps/emqx_authn/test/data/certs" \ + -Droute="apps/emqx_auth/test/data/certs" \ -Dca_name="ca.crt" \ -Dkey_name="client.key" \ -Dcert_name="client.crt" \ diff --git a/apps/emqx/src/emqx_hocon.erl b/apps/emqx/src/emqx_hocon.erl index 4423ad29d..68594ad9d 100644 --- a/apps/emqx/src/emqx_hocon.erl +++ b/apps/emqx/src/emqx_hocon.erl @@ -37,25 +37,25 @@ format_path([Name | Rest]) -> [iol(Name), "." | format_path(Rest)]. %% @doc Plain check the input config. %% The input can either be `richmap' or plain `map'. %% Always return plain map with atom keys. --spec check(module(), hocon:config() | iodata()) -> +-spec check(hocon_schema:schema(), hocon:config() | iodata()) -> {ok, hocon:config()} | {error, any()}. -check(SchemaModule, Conf) -> +check(Schema, Conf) -> %% TODO: remove required %% fields should state required or not in their schema Opts = #{atom_key => true, required => false}, - check(SchemaModule, Conf, Opts). + check(Schema, Conf, Opts). -check(SchemaModule, Conf, Opts) when is_map(Conf) -> +check(Schema, Conf, Opts) when is_map(Conf) -> try - {ok, hocon_tconf:check_plain(SchemaModule, Conf, Opts)} + {ok, hocon_tconf:check_plain(Schema, Conf, Opts)} catch throw:Errors:Stacktrace -> compact_errors(Errors, Stacktrace) end; -check(SchemaModule, HoconText, Opts) -> +check(Schema, HoconText, Opts) -> case hocon:binary(HoconText, #{format => map}) of {ok, MapConfig} -> - check(SchemaModule, MapConfig, Opts); + check(Schema, MapConfig, Opts); {error, Reason} -> {error, Reason} end. diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl index beb8c2567..d9fd12ab5 100644 --- a/apps/emqx/src/emqx_schema.erl +++ b/apps/emqx/src/emqx_schema.erl @@ -216,16 +216,18 @@ roots(high) -> } )} ] ++ - emqx_schema_hooks:injection_point('roots.high') ++ - [ - %% NOTE: authorization schema here is only to keep emqx app pure - %% the full schema for EMQX node is injected in emqx_conf_schema. - {?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME, - sc( - ref(?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME), - #{importance => ?IMPORTANCE_HIDDEN} - )} - ]; + emqx_schema_hooks:injection_point( + 'roots.high', + [ + %% NOTE: authorization schema here is only to keep emqx app pure + %% the full schema for EMQX node is injected in emqx_conf_schema. + {?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME, + sc( + ref(?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME), + #{importance => ?IMPORTANCE_HIDDEN} + )} + ] + ); roots(medium) -> [ {"broker", diff --git a/apps/emqx/src/emqx_schema_hooks.erl b/apps/emqx/src/emqx_schema_hooks.erl index e704af6cc..cc87e35a7 100644 --- a/apps/emqx/src/emqx_schema_hooks.erl +++ b/apps/emqx/src/emqx_schema_hooks.erl @@ -30,6 +30,7 @@ -export([ injection_point/1, + injection_point/2, inject_from_modules/1 ]). @@ -43,9 +44,15 @@ %% API %%-------------------------------------------------------------------- +-spec injection_point(hookpoint()) -> [hocon_schema:field()]. injection_point(PointName) -> - persistent_term:get(?HOOKPOINT_PT_KEY(PointName), []). + injection_point(PointName, []). +-spec injection_point(hookpoint(), [hocon_schema:field()]) -> [hocon_schema:field()]. +injection_point(PointName, Default) -> + persistent_term:get(?HOOKPOINT_PT_KEY(PointName), Default). + +-spec erase_injections() -> ok. erase_injections() -> lists:foreach( fun @@ -57,6 +64,7 @@ erase_injections() -> persistent_term:get() ). +-spec any_injections() -> boolean(). any_injections() -> lists:any( fun @@ -68,6 +76,7 @@ any_injections() -> persistent_term:get() ). +-spec inject_from_modules([module() | {module(), term()}]) -> ok. inject_from_modules(Modules) -> Injections = lists:foldl( diff --git a/apps/emqx/test/emqx_common_test_helpers.erl b/apps/emqx/test/emqx_common_test_helpers.erl index 3645fa06b..2693be2a8 100644 --- a/apps/emqx/test/emqx_common_test_helpers.erl +++ b/apps/emqx/test/emqx_common_test_helpers.erl @@ -527,11 +527,11 @@ copy_certs(_, _) -> copy_acl_conf() -> Dest = filename:join([code:lib_dir(emqx), "etc/acl.conf"]), - case code:lib_dir(emqx_authz) of + case code:lib_dir(emqx_auth_file) of {error, bad_name} -> (not filelib:is_regular(Dest)) andalso file:write_file(Dest, <<"">>); _ -> - {ok, _} = file:copy(deps_path(emqx_authz, "etc/acl.conf"), Dest) + {ok, _} = file:copy(deps_path(emqx_auth_file, "etc/acl.conf"), Dest) end, ok. diff --git a/apps/emqx/test/emqx_cth_suite.erl b/apps/emqx/test/emqx_cth_suite.erl index 5cbca3243..001dfd646 100644 --- a/apps/emqx/test/emqx_cth_suite.erl +++ b/apps/emqx/test/emqx_cth_suite.erl @@ -330,7 +330,7 @@ default_appspec(emqx, SuiteOpts) -> % overwrite everything with a default configuration. before_start => fun inhibit_config_loader/2 }; -default_appspec(emqx_authz, _SuiteOpts) -> +default_appspec(emqx_auth, _SuiteOpts) -> #{ config => #{ % NOTE @@ -356,7 +356,7 @@ default_appspec(emqx_conf, SuiteOpts) -> Config, [ emqx, - emqx_authz + emqx_auth ] ), #{ diff --git a/apps/emqx_authn/data/user-credentials.csv b/apps/emqx_auth/data/user-credentials.csv similarity index 100% rename from apps/emqx_authn/data/user-credentials.csv rename to apps/emqx_auth/data/user-credentials.csv diff --git a/apps/emqx_authn/data/user-credentials.json b/apps/emqx_auth/data/user-credentials.json similarity index 100% rename from apps/emqx_authn/data/user-credentials.json rename to apps/emqx_auth/data/user-credentials.json diff --git a/apps/emqx_authn/etc/emqx_authn.conf b/apps/emqx_auth/etc/emqx_auth.conf similarity index 100% rename from apps/emqx_authn/etc/emqx_authn.conf rename to apps/emqx_auth/etc/emqx_auth.conf diff --git a/apps/emqx_auth/include/emqx_auth.hrl b/apps/emqx_auth/include/emqx_auth.hrl new file mode 100644 index 000000000..98aee8367 --- /dev/null +++ b/apps/emqx_auth/include/emqx_auth.hrl @@ -0,0 +1,22 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-ifndef(EMQX_AUTH_HRL). +-define(EMQX_AUTH_HRL, true). + +-define(APP, emqx_auth). + +-endif. diff --git a/apps/emqx_authn/include/emqx_authn.hrl b/apps/emqx_auth/include/emqx_authn.hrl similarity index 86% rename from apps/emqx_authn/include/emqx_authn.hrl rename to apps/emqx_auth/include/emqx_authn.hrl index 9574d092f..29aa2e262 100644 --- a/apps/emqx_authn/include/emqx_authn.hrl +++ b/apps/emqx_auth/include/emqx_authn.hrl @@ -17,16 +17,12 @@ -ifndef(EMQX_AUTHN_HRL). -define(EMQX_AUTHN_HRL, true). --include_lib("emqx_authentication.hrl"). +-include("emqx_authn_chains.hrl"). --define(APP, emqx_authn). - --define(AUTHN, emqx_authentication). +-define(AUTHN, emqx_authn_chains). -define(RE_PLACEHOLDER, "\\$\\{[a-z0-9\\-]+\\}"). --define(AUTH_SHARD, emqx_authn_shard). - %% has to be the same as the root field name defined in emqx_schema -define(CONF_NS, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME). -define(CONF_NS_ATOM, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM). @@ -34,6 +30,6 @@ -type authenticator_id() :: binary(). --define(RESOURCE_GROUP, <<"emqx_authn">>). +-define(AUTHN_RESOURCE_GROUP, <<"emqx_authn">>). -endif. diff --git a/apps/emqx_authn/include/emqx_authentication.hrl b/apps/emqx_auth/include/emqx_authn_chains.hrl similarity index 96% rename from apps/emqx_authn/include/emqx_authentication.hrl rename to apps/emqx_auth/include/emqx_authn_chains.hrl index c294b8d99..d56677f0a 100644 --- a/apps/emqx_authn/include/emqx_authentication.hrl +++ b/apps/emqx_auth/include/emqx_authn_chains.hrl @@ -14,8 +14,8 @@ %% limitations under the License. %%-------------------------------------------------------------------- --ifndef(EMQX_AUTHENTICATION_HRL). --define(EMQX_AUTHENTICATION_HRL, true). +-ifndef(EMQX_AUTHN_CHAINS_HRL). +-define(EMQX_AUTHN_CHAINS_HRL, true). -include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/emqx_access_control.hrl"). diff --git a/apps/emqx_auth/include/emqx_authn_schema.hrl b/apps/emqx_auth/include/emqx_authn_schema.hrl new file mode 100644 index 000000000..6ab74fcb7 --- /dev/null +++ b/apps/emqx_auth/include/emqx_authn_schema.hrl @@ -0,0 +1,37 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-ifndef(EMQX_AUTHN_SOURCES_HRL). +-define(EMQX_AUTHN_SOURCES_HRL, true). + +-define(PROVIDER_SCHEMA_MODS, [ + emqx_authn_mnesia_schema, + emqx_authn_mysql_schema, + emqx_authn_postgresql_schema, + emqx_authn_mongodb_schema, + emqx_authn_redis_schema, + emqx_authn_http_schema, + emqx_authn_jwt_schema, + emqx_authn_scram_mnesia_schema +]). + +-define(EE_PROVIDER_SCHEMA_MODS, [ + emqx_authn_ldap_schema, + emqx_authn_ldap_bind_schema, + emqx_gcp_device_authn_schema +]). + +-endif. diff --git a/apps/emqx_authz/include/emqx_authz.hrl b/apps/emqx_auth/include/emqx_authz.hrl similarity index 98% rename from apps/emqx_authz/include/emqx_authz.hrl rename to apps/emqx_auth/include/emqx_authz.hrl index 5cab24fab..f017b9be5 100644 --- a/apps/emqx_authz/include/emqx_authz.hrl +++ b/apps/emqx_auth/include/emqx_authz.hrl @@ -16,7 +16,7 @@ -include_lib("emqx/include/emqx_access_control.hrl"). --define(APP, emqx_authz). +-include("emqx_auth.hrl"). %% authz_mnesia -define(ACL_TABLE, emqx_acl). @@ -157,7 +157,7 @@ count => 1 }). --define(RESOURCE_GROUP, <<"emqx_authz">>). +-define(AUTHZ_RESOURCE_GROUP, <<"emqx_authz">>). -define(AUTHZ_FEATURES, [rich_actions]). diff --git a/apps/emqx_auth/include/emqx_authz_schema.hrl b/apps/emqx_auth/include/emqx_authz_schema.hrl new file mode 100644 index 000000000..fd78dfd4c --- /dev/null +++ b/apps/emqx_auth/include/emqx_authz_schema.hrl @@ -0,0 +1,34 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-ifndef(EMQX_AUTHZ_SCHEMA_HRL). +-define(EMQX_AUTHZ_SCHEMA_HRL, true). + +-define(SOURCE_SCHEMA_MODS, [ + emqx_authz_file_schema, + emqx_authz_mnesia_schema, + emqx_authz_http_schema, + emqx_authz_redis_schema, + emqx_authz_mysql_schema, + emqx_authz_postgresql_schema, + emqx_authz_mongodb_schema +]). + +-define(EE_SOURCE_SCHEMA_MODS, [ + emqx_authz_ldap_schema +]). + +-endif. diff --git a/apps/emqx_authn/rebar.config b/apps/emqx_auth/rebar.config similarity index 62% rename from apps/emqx_authn/rebar.config rename to apps/emqx_auth/rebar.config index 5bc8d3e91..da8140f0f 100644 --- a/apps/emqx_authn/rebar.config +++ b/apps/emqx_auth/rebar.config @@ -2,12 +2,7 @@ {deps, [ {emqx, {path, "../emqx"}}, - {emqx_utils, {path, "../emqx_utils"}}, - {emqx_connector, {path, "../emqx_connector"}}, - {emqx_mongodb, {path, "../emqx_mongodb"}}, - {emqx_redis, {path, "../emqx_redis"}}, - {emqx_mysql, {path, "../emqx_mysql"}}, - {emqx_bridge_http, {path, "../emqx_bridge_http"}} + {emqx_utils, {path, "../emqx_utils"}} ]}. {edoc_opts, [{preprocess, true}]}. @@ -34,6 +29,4 @@ {cover_opts, [verbose]}. {cover_export_enabled, true}. -{erl_first_files, ["src/emqx_authentication.erl"]}. - {project_plugins, [erlfmt]}. diff --git a/apps/emqx_auth/src/emqx_auth.app.src b/apps/emqx_auth/src/emqx_auth.app.src new file mode 100644 index 000000000..cfd2aa447 --- /dev/null +++ b/apps/emqx_auth/src/emqx_auth.app.src @@ -0,0 +1,17 @@ +%% -*- mode: erlang -*- +{application, emqx_auth, [ + {description, "EMQX Authentication and authorization"}, + {vsn, "0.1.27"}, + {modules, []}, + {registered, [emqx_auth_sup]}, + {applications, [ + kernel, + stdlib, + emqx + ]}, + {mod, {emqx_auth_app, []}}, + {env, []}, + {licenses, ["Apache-2.0"]}, + {maintainers, ["EMQX Team "]}, + {links, [{"Homepage", "https://emqx.io/"}]} +]}. diff --git a/apps/emqx_authn/src/emqx_authn.appup.src b/apps/emqx_auth/src/emqx_auth.appup.src similarity index 100% rename from apps/emqx_authn/src/emqx_authn.appup.src rename to apps/emqx_auth/src/emqx_auth.appup.src diff --git a/apps/emqx_authn/src/emqx_authn_app.erl b/apps/emqx_auth/src/emqx_auth_app.erl similarity index 54% rename from apps/emqx_authn/src/emqx_authn_app.erl rename to apps/emqx_auth/src/emqx_auth_app.erl index 689f6619a..526b5b120 100644 --- a/apps/emqx_authn/src/emqx_authn_app.erl +++ b/apps/emqx_auth/src/emqx_auth_app.erl @@ -14,7 +14,7 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_authn_app). +-module(emqx_auth_app). -include("emqx_authn.hrl"). @@ -26,7 +26,7 @@ stop/1 ]). --include_lib("emqx_authentication.hrl"). +-include_lib("emqx_authn_chains.hrl"). -dialyzer({nowarn_function, [start/2]}). @@ -37,56 +37,17 @@ start(_StartType, _StartArgs) -> %% required by test cases, ensure the injection of schema _ = emqx_conf_schema:roots(), - ok = mria_rlog:wait_for_shards([?AUTH_SHARD], infinity), {ok, Sup} = emqx_authn_sup:start_link(), - case initialize() of - ok -> {ok, Sup}; - {error, Reason} -> {error, Reason} - end. + ok = emqx_authz:init(), + {ok, Sup}. stop(_State) -> - ok = deinitialize(). + ok = deinitialize(), + ok = emqx_authz:deinit(). %%------------------------------------------------------------------------------ %% Internal functions %%------------------------------------------------------------------------------ -initialize() -> - ok = ?AUTHN:register_providers(emqx_authn:providers()), - lists:foreach( - fun({ChainName, AuthConfig}) -> - ?AUTHN:initialize_authentication( - ChainName, - AuthConfig - ) - end, - chain_configs() - ). - deinitialize() -> - ok = ?AUTHN:deregister_providers(provider_types()), ok = emqx_authn_utils:cleanup_resources(). - -chain_configs() -> - [global_chain_config() | listener_chain_configs()]. - -global_chain_config() -> - {?GLOBAL, emqx:get_config([?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM], [])}. - -listener_chain_configs() -> - lists:map( - fun({ListenerID, _}) -> - {ListenerID, emqx:get_config(auth_config_path(ListenerID), [])} - end, - emqx_listeners:list() - ). - -auth_config_path(ListenerID) -> - Names = [ - binary_to_existing_atom(N, utf8) - || N <- binary:split(atom_to_binary(ListenerID), <<":">>) - ], - [listeners] ++ Names ++ [?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM]. - -provider_types() -> - lists:map(fun({Type, _Module}) -> Type end, emqx_authn:providers()). diff --git a/apps/emqx_auth/src/emqx_auth_schema.erl b/apps/emqx_auth/src/emqx_auth_schema.erl new file mode 100644 index 000000000..1fe8fe365 --- /dev/null +++ b/apps/emqx_auth/src/emqx_auth_schema.erl @@ -0,0 +1,28 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_schema). + +-export([ + namespace/0, + roots/0 +]). + +namespace() -> auth. + +%% @doc auth schema is not exported +%% but directly used by emqx_schema +roots() -> []. diff --git a/apps/emqx_authn/src/emqx_authentication_sup.erl b/apps/emqx_auth/src/emqx_auth_sup.erl similarity index 61% rename from apps/emqx_authn/src/emqx_authentication_sup.erl rename to apps/emqx_auth/src/emqx_auth_sup.erl index d28101fe7..7d78361eb 100644 --- a/apps/emqx_authn/src/emqx_authentication_sup.erl +++ b/apps/emqx_auth/src/emqx_auth_sup.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2017-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2021-2023 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -14,25 +14,18 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_authentication_sup). +-module(emqx_auth_sup). -behaviour(supervisor). --export([start_link/0]). - --export([init/1]). - -%%-------------------------------------------------------------------- -%% API -%%-------------------------------------------------------------------- +-export([ + start_link/0, + init/1 +]). start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). -%%-------------------------------------------------------------------- -%% Supervisor callbacks -%%-------------------------------------------------------------------- - init([]) -> SupFlags = #{ strategy => one_for_one, @@ -41,12 +34,21 @@ init([]) -> }, AuthN = #{ - id => emqx_authentication, - start => {emqx_authentication, start_link, []}, + id => emqx_authn_sup, + start => {emqx_authn_sup, start_link, []}, restart => permanent, shutdown => 1000, - type => worker, - modules => [emqx_authentication] + type => supervisor }, - {ok, {SupFlags, [AuthN]}}. + AuthZ = #{ + id => emqx_authz_sup, + start => {emqx_authz_sup, start_link, []}, + restart => permanent, + shutdown => 1000, + type => supervisor + }, + + ChildSpecs = [AuthN, AuthZ], + + {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/emqx_authn/src/emqx_authn.erl b/apps/emqx_auth/src/emqx_authn/emqx_authn.erl similarity index 56% rename from apps/emqx_authn/src/emqx_authn.erl rename to apps/emqx_auth/src/emqx_authn/emqx_authn.erl index ed6f0a095..ddf5c537d 100644 --- a/apps/emqx_authn/src/emqx_authn.erl +++ b/apps/emqx_auth/src/emqx_authn/emqx_authn.erl @@ -19,95 +19,42 @@ -behaviour(emqx_config_backup). -export([ - providers/0, - check_config/1, - check_config/2, + fill_defaults/1, %% for telemetry information - get_enabled_authns/0 + get_enabled_authns/0, + + register_provider/2, + deregister_provider/1 ]). -export([merge_config/1, merge_config_local/2, import_config/1]). -include("emqx_authn.hrl"). -providers() -> - [ - {{password_based, built_in_database}, emqx_authn_mnesia}, - {{password_based, mysql}, emqx_authn_mysql}, - {{password_based, postgresql}, emqx_authn_pgsql}, - {{password_based, mongodb}, emqx_authn_mongodb}, - {{password_based, redis}, emqx_authn_redis}, - {{password_based, http}, emqx_authn_http}, - {jwt, emqx_authn_jwt}, - {{scram, built_in_database}, emqx_enhanced_authn_scram_mnesia} - ] ++ - emqx_authn_enterprise:providers(). +fill_defaults(Config) -> + #{?CONF_NS_BINARY := WithDefaults} = do_fill_defaults(Config), + WithDefaults. -check_config(Config) -> - check_config(Config, #{}). - -check_config(Config, Opts) -> - case do_check_config(Config, Opts) of - #{?CONF_NS_ATOM := Checked} -> Checked; - #{?CONF_NS_BINARY := WithDefaults} -> WithDefaults - end. - -do_check_config(#{<<"mechanism">> := Mec0} = Config, Opts) -> - Mec = atom(Mec0, #{error => unknown_mechanism}), - Key = - case maps:get(<<"backend">>, Config, false) of - false -> Mec; - Backend -> {Mec, atom(Backend, #{error => unknown_backend})} - end, - case lists:keyfind(Key, 1, providers()) of - false -> - Reason = - case Key of - {M, B} -> - #{mechanism => M, backend => B}; - M -> - #{mechanism => M} - end, - throw(Reason#{error => unknown_authn_provider}); - {_, ProviderModule} -> - do_check_config_maybe_throw(ProviderModule, Config, Opts) - end; -do_check_config(Config, _Opts) when is_map(Config) -> - throw(#{ - error => invalid_config, - reason => "mechanism_field_required" - }). - -do_check_config_maybe_throw(ProviderModule, Config0, Opts) -> +do_fill_defaults(Config0) -> Config = #{?CONF_NS_BINARY => Config0}, - case emqx_hocon:check(ProviderModule, Config, Opts#{atom_key => true}) of + Schema = #{roots => [{?CONF_NS, hoconsc:mk(emqx_authn_schema:authenticator_type())}]}, + case emqx_hocon:check(Schema, Config, #{make_serializable => true}) of {ok, Checked} -> Checked; {error, Reason} -> throw(Reason) end. -%% The atoms have to be loaded already, -%% which might be an issue for plugins which are loaded after node boot -%% but they should really manage their own configs in that case. -atom(Bin, ErrorContext) -> - try - binary_to_existing_atom(Bin, utf8) - catch - _:_ -> - throw(ErrorContext#{value => Bin}) - end. - -spec get_enabled_authns() -> #{ authenticators => [authenticator_id()], overridden_listeners => #{authenticator_id() => pos_integer()} }. get_enabled_authns() -> - %% at the moment of writing, `emqx_authentication:list_chains/0' + %% at the moment of writing, `emqx_authn_chains:list_chains/0' %% result is always wrapped in `{ok, _}', and it cannot return any %% error values. - {ok, Chains} = emqx_authentication:list_chains(), + {ok, Chains} = emqx_authn_chains:list_chains(), AuthnTypes = lists:usort([ Type || #{authenticators := As} <- Chains, @@ -132,6 +79,12 @@ get_enabled_authns() -> tally_authenticators(#{id := AuthenticatorName}, Acc) -> maps:update_with(AuthenticatorName, fun(N) -> N + 1 end, 1, Acc). +register_provider(ProviderType, ProviderModule) -> + ok = ?AUTHN:register_provider(ProviderType, ProviderModule). + +deregister_provider(ProviderType) -> + ok = ?AUTHN:deregister_provider(ProviderType). + %%------------------------------------------------------------------------------ %% Data backup %%------------------------------------------------------------------------------ @@ -142,7 +95,7 @@ import_config(RawConf) -> AuthnList = authn_list(maps:get(?CONF_NS_BINARY, RawConf, [])), OldAuthnList = emqx:get_raw_config([?CONF_NS_BINARY], []), MergedAuthnList = emqx_utils:merge_lists( - OldAuthnList, AuthnList, fun emqx_authentication:authenticator_id/1 + OldAuthnList, AuthnList, fun emqx_authn_chains:authenticator_id/1 ), case emqx_conf:update([?CONF_NS_ATOM], MergedAuthnList, ?IMPORT_OPTS) of {ok, #{raw_config := NewRawConf}} -> @@ -152,9 +105,9 @@ import_config(RawConf) -> end. changed_paths(OldAuthnList, NewAuthnList) -> - KeyFun = fun emqx_authentication:authenticator_id/1, + KeyFun = fun emqx_authn_chains:authenticator_id/1, Changed = maps:get(changed, emqx_utils:diff_lists(NewAuthnList, OldAuthnList, KeyFun)), - [[?CONF_NS_BINARY, emqx_authentication:authenticator_id(OldAuthn)] || {OldAuthn, _} <- Changed]. + [[?CONF_NS_BINARY, emqx_authn_chains:authenticator_id(OldAuthn)] || {OldAuthn, _} <- Changed]. authn_list(Authn) when is_list(Authn) -> Authn; diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_auth/src/emqx_authn/emqx_authn_api.erl similarity index 96% rename from apps/emqx_authn/src/emqx_authn_api.erl rename to apps/emqx_auth/src/emqx_authn/emqx_authn_api.erl index ce4647110..9938a3018 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/apps/emqx_auth/src/emqx_authn/emqx_authn_api.erl @@ -30,7 +30,7 @@ -define(NOT_FOUND, 'NOT_FOUND'). -define(ALREADY_EXISTS, 'ALREADY_EXISTS'). -define(INTERNAL_ERROR, 'INTERNAL_ERROR'). --define(CONFIG, emqx_authentication_config). +-define(CONFIG, emqx_authn_config). % Swagger @@ -823,7 +823,7 @@ find_listener(ListenerID) -> end. with_chain(ListenerID, Fun) -> - {ok, ChainNames} = emqx_authentication:list_chain_names(), + {ok, ChainNames} = emqx_authn_chains:list_chain_names(), ListenerChainName = [Name || Name <- ChainNames, atom_to_binary(Name) =:= ListenerID], case ListenerChainName of @@ -852,7 +852,7 @@ list_authenticators(ConfKeyPath) -> NAuthenticators = [ maps:put( id, - emqx_authentication:authenticator_id(AuthenticatorConfig), + emqx_authn_chains:authenticator_id(AuthenticatorConfig), convert_certs(AuthenticatorConfig) ) || AuthenticatorConfig <- AuthenticatorsConfig @@ -868,33 +868,25 @@ list_authenticator(_, ConfKeyPath, AuthenticatorID) -> end ). -resource_provider() -> - [ - emqx_authn_mysql, - emqx_authn_pgsql, - emqx_authn_mongodb, - emqx_authn_redis, - emqx_authn_http - ] ++ - emqx_authn_enterprise:resource_provider(). - +%% TODO +%% This breaks encapsulation, resource_id should be obtained +%% through provider callback lookup_from_local_node(ChainName, AuthenticatorID) -> NodeId = node(self()), - case emqx_authentication:lookup_authenticator(ChainName, AuthenticatorID) of - {ok, #{provider := Provider, state := State}} -> - MetricsId = emqx_authentication:metrics_id(ChainName, AuthenticatorID), + case emqx_authn_chains:lookup_authenticator(ChainName, AuthenticatorID) of + {ok, #{state := State}} -> + MetricsId = emqx_authn_chains:metrics_id(ChainName, AuthenticatorID), Metrics = emqx_metrics_worker:get_metrics(authn_metrics, MetricsId), - case lists:member(Provider, resource_provider()) of - false -> - {ok, {NodeId, connected, Metrics, #{}}}; - true -> - #{resource_id := ResourceId} = State, + case State of + #{resource_id := ResourceId} -> case emqx_resource:get_instance(ResourceId) of {error, not_found} -> {error, {NodeId, not_found_resource}}; {ok, _, #{status := Status}} -> {ok, {NodeId, Status, Metrics, emqx_resource:get_metrics(ResourceId)}} - end + end; + _ -> + {ok, {NodeId, connected, Metrics, #{}}} end; {error, Reason} -> {error, {NodeId, list_to_binary(io_lib:format("~p", [Reason]))}} @@ -1064,7 +1056,7 @@ add_user( ) -> IsSuperuser = maps:get(<<"is_superuser">>, UserInfo, false), case - emqx_authentication:add_user( + emqx_authn_chains:add_user( ChainName, AuthenticatorID, #{ @@ -1090,7 +1082,7 @@ update_user(ChainName, AuthenticatorID, UserID, UserInfo0) -> serialize_error({missing_parameter, password}); false -> UserInfo = emqx_utils_maps:safe_atom_key_map(UserInfo0), - case emqx_authentication:update_user(ChainName, AuthenticatorID, UserID, UserInfo) of + case emqx_authn_chains:update_user(ChainName, AuthenticatorID, UserID, UserInfo) of {ok, User} -> {200, User}; {error, Reason} -> @@ -1099,7 +1091,7 @@ update_user(ChainName, AuthenticatorID, UserID, UserInfo0) -> end. find_user(ChainName, AuthenticatorID, UserID) -> - case emqx_authentication:lookup_user(ChainName, AuthenticatorID, UserID) of + case emqx_authn_chains:lookup_user(ChainName, AuthenticatorID, UserID) of {ok, User} -> {200, User}; {error, Reason} -> @@ -1107,7 +1099,7 @@ find_user(ChainName, AuthenticatorID, UserID) -> end. delete_user(ChainName, AuthenticatorID, UserID) -> - case emqx_authentication:delete_user(ChainName, AuthenticatorID, UserID) of + case emqx_authn_chains:delete_user(ChainName, AuthenticatorID, UserID) of ok -> {204}; {error, Reason} -> @@ -1115,7 +1107,7 @@ delete_user(ChainName, AuthenticatorID, UserID) -> end. list_users(ChainName, AuthenticatorID, QueryString) -> - case emqx_authentication:list_users(ChainName, AuthenticatorID, QueryString) of + case emqx_authn_chains:list_users(ChainName, AuthenticatorID, QueryString) of {error, page_limit_invalid} -> {400, #{code => <<"INVALID_PARAMETER">>, message => <<"page_limit_invalid">>}}; {error, Reason} -> @@ -1143,7 +1135,7 @@ find_config(AuthenticatorID, AuthenticatorsConfig) -> [ AC || AC <- ensure_list(AuthenticatorsConfig), - AuthenticatorID =:= emqx_authentication:authenticator_id(AC) + AuthenticatorID =:= emqx_authn_chains:authenticator_id(AC) ], case MatchingACs of [] -> {error, {not_found, {authenticator, AuthenticatorID}}}; @@ -1153,17 +1145,19 @@ find_config(AuthenticatorID, AuthenticatorsConfig) -> fill_defaults(Configs) when is_list(Configs) -> lists:map(fun fill_defaults/1, Configs); fill_defaults(Config) -> - emqx_authn:check_config(merge_default_headers(Config), #{make_serializable => true}). + emqx_authn:fill_defaults(merge_default_headers(Config)). +%% TODO +%% this breaks encapsulation, this should be done through provider callbacks merge_default_headers(Config) -> case maps:find(<<"headers">>, Config) of {ok, Headers} -> NewHeaders = case Config of #{<<"method">> := <<"get">>} -> - (emqx_authn_http:headers_no_content_type(converter))(Headers); + emqx_authn_utils:convert_headers_no_content_type(Headers); #{<<"method">> := <<"post">>} -> - (emqx_authn_http:headers(converter))(Headers); + emqx_authn_utils:convert_headers(Headers); _ -> Headers end, diff --git a/apps/emqx_authn/src/emqx_authentication.erl b/apps/emqx_auth/src/emqx_authn/emqx_authn_chains.erl similarity index 88% rename from apps/emqx_authn/src/emqx_authentication.erl rename to apps/emqx_auth/src/emqx_authn/emqx_authn_chains.erl index 8f055e049..d0e132a45 100644 --- a/apps/emqx_authn/src/emqx_authentication.erl +++ b/apps/emqx_auth/src/emqx_authn/emqx_authn_chains.erl @@ -18,11 +18,11 @@ %% Authentication is a core functionality of MQTT, %% the 'emqx' APP provides APIs for other APPs to implement %% the authentication callbacks. --module(emqx_authentication). +-module(emqx_authn_chains). -behaviour(gen_server). --include("emqx_authentication.hrl"). +-include("emqx_authn_chains.hrl"). -include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/emqx_hooks.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). @@ -43,7 +43,8 @@ %% The authentication entrypoint. -export([ - authenticate/2 + authenticate/2, + authenticate_deny/2 ]). %% Authenticator manager process start/stop @@ -55,7 +56,6 @@ %% Authenticator management APIs -export([ - initialize_authentication/2, register_provider/2, register_providers/1, deregister_provider/1, @@ -89,6 +89,7 @@ handle_call/3, handle_cast/2, handle_info/2, + handle_continue/2, terminate/2, code_change/3 ]). @@ -99,7 +100,8 @@ -export_type([ authenticator_id/0, position/0, - chain_name/0 + chain_name/0, + authn_type/0 ]). -ifdef(TEST). @@ -135,7 +137,7 @@ end). state := map() }. --type config() :: emqx_authentication_config:config(). +-type config() :: emqx_authn_config:config(). -type state() :: #{atom() => term()}. -type extra() :: #{ is_superuser := boolean(), @@ -146,85 +148,7 @@ end). atom() => term() }. -%% @doc check_config takes raw config from config file, -%% parse and validate it, and return parsed result. --callback check_config(config()) -> config(). - --callback create(AuthenticatorID, Config) -> - {ok, State} - | {error, term()} -when - AuthenticatorID :: authenticator_id(), Config :: config(), State :: state(). - --callback update(Config, State) -> - {ok, NewState} - | {error, term()} -when - Config :: config(), State :: state(), NewState :: state(). - --callback authenticate(Credential, State) -> - ignore - | {ok, Extra} - | {ok, Extra, AuthData} - | {continue, AuthCache} - | {continue, AuthData, AuthCache} - | {error, term()} -when - Credential :: map(), - State :: state(), - Extra :: extra(), - AuthData :: binary(), - AuthCache :: map(). - --callback destroy(State) -> - ok -when - State :: state(). - --callback import_users({Filename, FileData}, State) -> - ok - | {error, term()} -when - Filename :: binary(), FileData :: binary(), State :: state(). - --callback add_user(UserInfo, State) -> - {ok, User} - | {error, term()} -when - UserInfo :: user_info(), State :: state(), User :: user_info(). - --callback delete_user(UserID, State) -> - ok - | {error, term()} -when - UserID :: binary(), State :: state(). - --callback update_user(UserID, UserInfo, State) -> - {ok, User} - | {error, term()} -when - UserID :: binary(), UserInfo :: map(), State :: state(), User :: user_info(). - --callback lookup_user(UserID, UserInfo, State) -> - {ok, User} - | {error, term()} -when - UserID :: binary(), UserInfo :: map(), State :: state(), User :: user_info(). - --callback list_users(State) -> - {ok, Users} -when - State :: state(), Users :: [user_info()]. - --optional_callbacks([ - import_users/2, - add_user/2, - delete_user/2, - update_user/3, - lookup_user/3, - list_users/1, - check_config/1 -]). +-export_type([authenticator/0, config/0, state/0, extra/0, user_info/0]). %%------------------------------------------------------------------------------ %% Authenticate @@ -244,6 +168,9 @@ authenticate(#{listener := Listener, protocol := Protocol} = Credential, AuthRes ?TRACE_RESULT("authentication_result", AuthResult, no_chain) end. +authenticate_deny(_Credential, _AuthResult) -> + ?TRACE_RESULT("authentication_result", {ok, {error, not_authorized}}, not_initialized). + get_authenticators(Listener, Global) -> case ets:lookup(?CHAINS_TAB, Listener) of [#chain{name = Name, authenticators = Authenticators}] -> @@ -274,35 +201,14 @@ get_providers() -> %% and maybe a 'backend' key. %% This function works with both parsed (atom keys) and raw (binary keys) %% configurations. +-spec authenticator_id(config()) -> authenticator_id(). authenticator_id(Config) -> - emqx_authentication_config:authenticator_id(Config). - -%% @doc Call this API to initialize authenticators implemented in another APP. --spec initialize_authentication(chain_name(), [config()]) -> ok. -initialize_authentication(_, []) -> - ok; -initialize_authentication(ChainName, AuthenticatorsConfig) -> - CheckedConfig = to_list(AuthenticatorsConfig), - lists:foreach( - fun(AuthenticatorConfig) -> - case create_authenticator(ChainName, AuthenticatorConfig) of - {ok, _} -> - ok; - {error, Reason} -> - ?SLOG(error, #{ - msg => "failed_to_create_authenticator", - authenticator => authenticator_id(AuthenticatorConfig), - reason => Reason - }) - end - end, - CheckedConfig - ). + emqx_authn_config:authenticator_id(Config). -spec start_link() -> {ok, pid()} | ignore | {error, term()}. start_link() -> %% Create chains ETS table here so that it belongs to the supervisor - %% and survives `emqx_authentication` crashes. + %% and survives `emqx_authn_chains` crashes. ok = create_chain_table(), gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). @@ -316,7 +222,7 @@ stop() -> %% For example, ``[{{'password_based', redis}, emqx_authn_redis}]'' %% NOTE: Later registered provider may override earlier registered if they %% happen to clash the same `AuthNType'. --spec register_providers([{authn_type(), module()}]) -> ok. +-spec register_providers([{authn_type(), module()}]) -> ok | {error, term()}. register_providers(Providers) -> call({register_providers, Providers}). @@ -437,10 +343,12 @@ list_users(ChainName, AuthenticatorID, FuzzyParams) -> init(_Opts) -> process_flag(trap_exit, true), - Module = emqx_authentication_config, + Module = emqx_authn_config, ok = emqx_config_handler:add_handler([?CONF_ROOT], Module), ok = emqx_config_handler:add_handler([listeners, '?', '?', ?CONF_ROOT], Module), - {ok, #{hooked => false, providers => #{}}}. + ok = hook_deny(), + {ok, #{hooked => false, providers => #{}, init_done => false}, + {continue, initialize_authentication}}. handle_call(get_providers, _From, #{providers := Providers} = State) -> reply(Providers, State); @@ -458,7 +366,7 @@ handle_call( Reg0, Providers ), - reply(ok, State#{providers := Reg}); + reply(ok, State#{providers := Reg}, initialize_authentication); Clashes -> reply({error, {authentication_type_clash, Clashes}}, State) end; @@ -523,6 +431,12 @@ handle_call(Req, _From, State) -> ?SLOG(error, #{msg => "unexpected_call", call => Req}), {reply, ignored, State}. +handle_continue(initialize_authentication, #{init_done := true} = State) -> + {noreply, State}; +handle_continue(initialize_authentication, #{providers := Providers} = State) -> + InitDone = initialize_authentication(Providers), + {noreply, State#{init_done := InitDone}}. + handle_cast(Req, State) -> ?SLOG(error, #{msg => "unexpected_cast", cast => Req}), {noreply, State}. @@ -554,6 +468,71 @@ code_change(_OldVsn, State, _Extra) -> %% Private functions %%------------------------------------------------------------------------------ +initialize_authentication(Providers) -> + Chains = chain_configs(), + ProviderTypes = maps:keys(Providers), + HasProviders = lists:all( + fun({_, ChainConfigs}) -> + has_providers_for_configs(ChainConfigs, ProviderTypes) + end, + Chains + ), + do_initialize_authentication(Providers, Chains, HasProviders). + +do_initialize_authentication(_Providers, _Chains, _HasProviders = false) -> + false; +do_initialize_authentication(Providers, Chains, _HasProviders = true) -> + ok = lists:foreach( + fun({ChainName, ChainConfigs}) -> + initialize_chain_authentication(Providers, ChainName, ChainConfigs) + end, + Chains + ), + ok = unhook_deny(), + true. + +initialize_chain_authentication(_Providers, _ChainName, []) -> + ok; +initialize_chain_authentication(Providers, ChainName, AuthenticatorsConfig) -> + lists:foreach( + fun(AuthenticatorConfig) -> + CreateResult = with_new_chain(ChainName, fun(Chain) -> + handle_create_authenticator(Chain, AuthenticatorConfig, Providers) + end), + case CreateResult of + {ok, _} -> + ok; + {error, Reason} -> + ?SLOG(error, #{ + msg => "failed_to_create_authenticator", + authenticator => authenticator_id(AuthenticatorConfig), + reason => Reason + }) + end + end, + to_list(AuthenticatorsConfig) + ). + +has_providers_for_configs(AuthConfig, ProviderTypes) -> + Configs = to_list(AuthConfig), + lists:all( + fun(Config) -> + has_providers_for_config(Config, ProviderTypes) + end, + Configs + ). + +has_providers_for_config(_Config, []) -> + false; +has_providers_for_config(#{mechanism := Mechanism, backend := Backend}, [ + {Mechanism, Backend} | _ProviderTypes +]) -> + true; +has_providers_for_config(#{mechanism := Mechanism}, [Mechanism | _ProviderTypes]) -> + true; +has_providers_for_config(Config, [_ProviderType | ProviderTypes]) -> + has_providers_for_config(Config, ProviderTypes). + handle_update_authenticator(Chain, AuthenticatorID, Config) -> #chain{authenticators = Authenticators} = Chain, case lists:keyfind(AuthenticatorID, #authenticator.id, Authenticators) of @@ -700,6 +679,9 @@ authenticate_with_provider(#authenticator{id = ID, provider = Provider, state = reply(Reply, State) -> {reply, Reply, State}. +reply(Reply, State, Continue) -> + {reply, Reply, State, {continue, Continue}}. + save_chain(#chain{ name = Name, authenticators = [] @@ -773,6 +755,12 @@ maybe_unhook(#{hooked := true} = State) -> maybe_unhook(State) -> State. +hook_deny() -> + ok = emqx_hooks:put('client.authenticate', {?MODULE, authenticate_deny, []}, ?HP_AUTHN). + +unhook_deny() -> + ok = emqx_hooks:del('client.authenticate', {?MODULE, authenticate_deny, []}). + do_create_authenticator(AuthenticatorID, #{enable := Enable} = Config, Providers) -> Type = authn_type(Config), case maps:get(Type, Providers, undefined) of @@ -945,6 +933,27 @@ insert_user_group(_Chain, Config) -> metrics_id(ChainName, AuthenticatorId) -> iolist_to_binary([atom_to_binary(ChainName), <<"-">>, AuthenticatorId]). +chain_configs() -> + [global_chain_config() | listener_chain_configs()]. + +global_chain_config() -> + {?GLOBAL, emqx:get_config([?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM], [])}. + +listener_chain_configs() -> + lists:map( + fun({ListenerID, _}) -> + {ListenerID, emqx:get_config(auth_config_path(ListenerID), [])} + end, + emqx_listeners:list() + ). + +auth_config_path(ListenerID) -> + Names = [ + binary_to_existing_atom(N, utf8) + || N <- binary:split(atom_to_binary(ListenerID), <<":">>) + ], + [listeners] ++ Names ++ [?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM]. + to_list(undefined) -> []; to_list(M) when M =:= #{} -> []; to_list(M) when is_map(M) -> [M]; diff --git a/apps/emqx_authn/src/emqx_authentication_config.erl b/apps/emqx_auth/src/emqx_authn/emqx_authn_config.erl similarity index 94% rename from apps/emqx_authn/src/emqx_authentication_config.erl rename to apps/emqx_auth/src/emqx_authn/emqx_authn_config.erl index 95140a0e8..7d3797e7c 100644 --- a/apps/emqx_authn/src/emqx_authentication_config.erl +++ b/apps/emqx_auth/src/emqx_authn/emqx_authn_config.erl @@ -15,7 +15,7 @@ %%-------------------------------------------------------------------- %% @doc Authenticator configuration management module. --module(emqx_authentication_config). +-module(emqx_authn_config). -behaviour(emqx_config_handler). @@ -40,7 +40,7 @@ -export_type([config/0]). -include_lib("emqx/include/logger.hrl"). --include("emqx_authentication.hrl"). +-include("emqx_authn_chains.hrl"). -type parsed_config() :: #{ mechanism := atom(), @@ -50,9 +50,9 @@ -type raw_config() :: #{binary() => term()}. -type config() :: parsed_config() | raw_config(). --type authenticator_id() :: emqx_authentication:authenticator_id(). --type position() :: emqx_authentication:position(). --type chain_name() :: emqx_authentication:chain_name(). +-type authenticator_id() :: emqx_authn_chains:authenticator_id(). +-type position() :: emqx_authn_chains:position(). +-type chain_name() :: emqx_authn_chains:chain_name(). -type update_request() :: {create_authenticator, chain_name(), map()} | {delete_authenticator, chain_name(), authenticator_id()} @@ -164,7 +164,7 @@ do_post_config_update( _, {create_authenticator, ChainName, Config}, NewConfig, _OldConfig, _AppEnvs ) -> NConfig = get_authenticator_config(authenticator_id(Config), NewConfig), - emqx_authentication:create_authenticator(ChainName, NConfig); + emqx_authn_chains:create_authenticator(ChainName, NConfig); do_post_config_update( _, {delete_authenticator, ChainName, AuthenticatorID}, @@ -172,7 +172,7 @@ do_post_config_update( _OldConfig, _AppEnvs ) -> - emqx_authentication:delete_authenticator(ChainName, AuthenticatorID); + emqx_authn_chains:delete_authenticator(ChainName, AuthenticatorID); do_post_config_update( _, {update_authenticator, ChainName, AuthenticatorID, Config}, @@ -184,7 +184,7 @@ do_post_config_update( {error, not_found} -> {error, {not_found, {authenticator, AuthenticatorID}}}; NConfig -> - emqx_authentication:update_authenticator(ChainName, AuthenticatorID, NConfig) + emqx_authn_chains:update_authenticator(ChainName, AuthenticatorID, NConfig) end; do_post_config_update( _, @@ -193,7 +193,7 @@ do_post_config_update( _OldConfig, _AppEnvs ) -> - emqx_authentication:move_authenticator(ChainName, AuthenticatorID, Position); + emqx_authn_chains:move_authenticator(ChainName, AuthenticatorID, Position); do_post_config_update(_, _UpdateReq, OldConfig, OldConfig, _AppEnvs) -> ok; do_post_config_update(ConfPath, _UpdateReq, NewConfig0, OldConfig0, _AppEnvs) -> @@ -204,7 +204,7 @@ do_post_config_update(ConfPath, _UpdateReq, NewConfig0, OldConfig0, _AppEnvs) -> NewIds = lists:map(fun authenticator_id/1, NewConfig), ok = delete_authenticators(NewIds, ChainName, OldConfig), ok = create_or_update_authenticators(OldIds, ChainName, NewConfig), - ok = emqx_authentication:reorder_authenticator(ChainName, NewIds), + ok = emqx_authn_chains:reorder_authenticator(ChainName, NewIds), ok. %% @doc Handle listener config changes made at higher level. @@ -228,9 +228,9 @@ create_or_update_authenticators(OldIds, ChainName, NewConfig) -> Id = authenticator_id(Conf), case lists:member(Id, OldIds) of true -> - emqx_authentication:update_authenticator(ChainName, Id, Conf); + emqx_authn_chains:update_authenticator(ChainName, Id, Conf); false -> - emqx_authentication:create_authenticator(ChainName, Conf) + emqx_authn_chains:create_authenticator(ChainName, Conf) end end, NewConfig @@ -245,7 +245,7 @@ delete_authenticators(NewIds, ChainName, OldConfig) -> true -> ok; false -> - emqx_authentication:delete_authenticator(ChainName, Id) + emqx_authn_chains:delete_authenticator(ChainName, Id) end end, OldConfig diff --git a/apps/emqx_auth/src/emqx_authn/emqx_authn_enterprise.erl b/apps/emqx_auth/src/emqx_authn/emqx_authn_enterprise.erl new file mode 100644 index 000000000..bfafc4be5 --- /dev/null +++ b/apps/emqx_auth/src/emqx_authn/emqx_authn_enterprise.erl @@ -0,0 +1,36 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_authn_enterprise). + +-include("emqx_authn_schema.hrl"). + +-export([provider_schema_mods/0]). + +-if(?EMQX_RELEASE_EDITION == ee). + +% providers() -> +% [ +% {{password_based, ldap}, emqx_authn_ldap}, +% {{password_based, ldap_bind}, emqx_ldap_authn_bind}, +% {gcp_device, emqx_gcp_device_authn} +% ]. + +% resource_provider() -> +% [emqx_authn_ldap, emqx_ldap_authn_bind]. + +provider_schema_mods() -> + ?EE_PROVIDER_SCHEMA_MODS. + +-else. + +provider_schema_mods() -> + []. + +% providers() -> +% []. + +% resource_provider() -> +% []. +-endif. diff --git a/apps/emqx_authn/src/emqx_authn_password_hashing.erl b/apps/emqx_auth/src/emqx_authn/emqx_authn_password_hashing.erl similarity index 100% rename from apps/emqx_authn/src/emqx_authn_password_hashing.erl rename to apps/emqx_auth/src/emqx_authn/emqx_authn_password_hashing.erl diff --git a/apps/emqx_auth/src/emqx_authn/emqx_authn_provider.erl b/apps/emqx_auth/src/emqx_authn/emqx_authn_provider.erl new file mode 100644 index 000000000..3898d0bc4 --- /dev/null +++ b/apps/emqx_auth/src/emqx_authn/emqx_authn_provider.erl @@ -0,0 +1,98 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2021-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authn_provider). + +-type authenticator_id() :: emqx_authn_chains:authenticator_id(). +-type config() :: emqx_authn_chains:config(). +-type state() :: emqx_authn_chains:state(). +-type extra() :: emqx_authn_chains:extra(). +-type user_info() :: emqx_authn_chains:user_info(). + +-callback create(AuthenticatorID, Config) -> + {ok, State} + | {error, term()} +when + AuthenticatorID :: authenticator_id(), Config :: config(), State :: state(). + +-callback update(Config, State) -> + {ok, NewState} + | {error, term()} +when + Config :: config(), State :: state(), NewState :: state(). + +-callback authenticate(Credential, State) -> + ignore + | {ok, Extra} + | {ok, Extra, AuthData} + | {continue, AuthCache} + | {continue, AuthData, AuthCache} + | {error, term()} +when + Credential :: map(), + State :: state(), + Extra :: extra(), + AuthData :: binary(), + AuthCache :: map(). + +-callback destroy(State) -> + ok +when + State :: state(). + +-callback import_users({Filename, FileData}, State) -> + ok + | {error, term()} +when + Filename :: binary(), FileData :: binary(), State :: state(). + +-callback add_user(UserInfo, State) -> + {ok, User} + | {error, term()} +when + UserInfo :: user_info(), State :: state(), User :: user_info(). + +-callback delete_user(UserID, State) -> + ok + | {error, term()} +when + UserID :: binary(), State :: state(). + +-callback update_user(UserID, UserInfo, State) -> + {ok, User} + | {error, term()} +when + UserID :: binary(), UserInfo :: map(), State :: state(), User :: user_info(). + +-callback lookup_user(UserID, UserInfo, State) -> + {ok, User} + | {error, term()} +when + UserID :: binary(), UserInfo :: map(), State :: state(), User :: user_info(). + +-callback list_users(State) -> + {ok, Users} +when + State :: state(), Users :: [user_info()]. + +-optional_callbacks([ + import_users/2, + add_user/2, + delete_user/2, + update_user/3, + lookup_user/3, + list_users/1 +]). diff --git a/apps/emqx_authn/src/emqx_authn_schema.erl b/apps/emqx_auth/src/emqx_authn/emqx_authn_schema.erl similarity index 62% rename from apps/emqx_authn/src/emqx_authn_schema.erl rename to apps/emqx_auth/src/emqx_authn/emqx_authn_schema.erl index fb8e2ff23..49bda9472 100644 --- a/apps/emqx_authn/src/emqx_authn_schema.erl +++ b/apps/emqx_auth/src/emqx_authn/emqx_authn_schema.erl @@ -19,7 +19,8 @@ -elvis([{elvis_style, invalid_dynamic_call, disable}]). -include_lib("hocon/include/hoconsc.hrl"). -include("emqx_authn.hrl"). --include("emqx_authentication.hrl"). +-include("emqx_authn_schema.hrl"). +-include("emqx_authn_chains.hrl"). -behaviour(emqx_schema_hooks). -export([ @@ -29,7 +30,7 @@ -export([ common_fields/0, roots/0, - validations/0, + % validations/0, tags/0, fields/1, authenticator_type/0, @@ -38,6 +39,15 @@ backend/1 ]). +%%-------------------------------------------------------------------- +%% Authn Source Schema Behaviour +%%-------------------------------------------------------------------- + +-type schema_ref() :: ?R_REF(module(), hocon_schema:name()). +-callback refs() -> [schema_ref()]. +-callback select_union_member(emqx_config:raw_config()) -> schema_ref() | undefined | no_return(). +-callback fields(hocon_schema:name()) -> [hocon_schema:field()]. + roots() -> []. injected_fields() -> @@ -49,95 +59,53 @@ injected_fields() -> tags() -> [<<"Authentication">>]. -common_fields() -> - [{enable, fun enable/1}]. - -enable(type) -> boolean(); -enable(default) -> true; -enable(desc) -> ?DESC(?FUNCTION_NAME); -enable(_) -> undefined. - authenticator_type() -> - hoconsc:union(union_member_selector(emqx_authn:providers())). + hoconsc:union(union_member_selector(provider_schema_mods())). authenticator_type_without_scram() -> - Providers = lists:filtermap( - fun - ({{scram, _Backend}, _Mod}) -> - false; - (_) -> - true - end, - emqx_authn:providers() - ), - hoconsc:union(union_member_selector(Providers)). + hoconsc:union( + union_member_selector(provider_schema_mods() -- [emqx_authn_scram_mnesia_schema]) + ). -config_refs(Providers) -> - lists:append([Module:refs() || {_, Module} <- Providers]). - -union_member_selector(Providers) -> - Types = config_refs(Providers), +union_member_selector(Mods) -> + AllTypes = config_refs(Mods), fun - (all_union_members) -> Types; - ({value, Value}) -> select_union_member(Value, Providers) + (all_union_members) -> AllTypes; + ({value, Value}) -> select_union_member(Value, Mods) end. -select_union_member(#{<<"mechanism">> := _} = Value, Providers0) -> - BackendVal = maps:get(<<"backend">>, Value, undefined), - MechanismVal = maps:get(<<"mechanism">>, Value), - BackendFilterFn = fun - ({{_Mec, Backend}, _Mod}) -> - BackendVal =:= atom_to_binary(Backend); - (_) -> - BackendVal =:= undefined - end, - MechanismFilterFn = fun - ({{Mechanism, _Backend}, _Mod}) -> - MechanismVal =:= atom_to_binary(Mechanism); - ({Mechanism, _Mod}) -> - MechanismVal =:= atom_to_binary(Mechanism) - end, - case lists:filter(BackendFilterFn, Providers0) of - [] -> - throw(#{reason => "unknown_backend", backend => BackendVal}); - Providers1 -> - case lists:filter(MechanismFilterFn, Providers1) of - [] -> - throw(#{ - reason => "unsupported_mechanism", - mechanism => MechanismVal, - backend => BackendVal - }); - [{_, Module}] -> - try_select_union_member(Module, Value) - end +select_union_member(#{<<"mechanism">> := Mechanism, <<"backend">> := Backend}, []) -> + throw(#{ + reason => "unsupported_mechanism", + mechanism => Mechanism, + backend => Backend + }); +select_union_member(#{<<"mechanism">> := Mechanism}, []) -> + throw(#{ + reason => "unsupported_mechanism", + mechanism => Mechanism + }); +select_union_member(#{<<"mechanism">> := _} = Value, [Mod | Mods]) -> + case Mod:select_union_member(Value) of + undefined -> + select_union_member(Value, Mods); + Member -> + Member end; -select_union_member(Value, _Providers) when is_map(Value) -> +select_union_member(#{} = _Value, _Mods) -> throw(#{reason => "missing_mechanism_field"}); -select_union_member(Value, _Providers) -> +select_union_member(Value, _Mods) -> throw(#{reason => "not_a_struct", value => Value}). -try_select_union_member(Module, Value) -> - %% some modules have `union_member_selector/1' exported to help selecting - %% the sub-types, they are: - %% emqx_authn_http - %% emqx_authn_jwt - %% emqx_authn_mongodb - %% emqx_authn_redis - try - Module:union_member_selector({value, Value}) - catch - error:undef -> - %% otherwise expect only one member from this module - Module:refs() - end. +config_refs(Mods) -> + lists:append([Mod:refs() || Mod <- Mods]). root_type() -> hoconsc:array(authenticator_type()). global_auth_fields() -> [ - {?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM, + {?CONF_NS_ATOM, hoconsc:mk(root_type(), #{ desc => ?DESC(global_authentication), converter => fun ensure_array/2, @@ -148,7 +116,7 @@ global_auth_fields() -> mqtt_listener_auth_fields() -> [ - {?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM, + {?CONF_NS_ATOM, hoconsc:mk(root_type(), #{ desc => ?DESC(listener_authentication), converter => fun ensure_array/2, @@ -177,6 +145,14 @@ backend(Name) -> desc => ?DESC("backend") }). +common_fields() -> + [{enable, fun enable/1}]. + +enable(type) -> boolean(); +enable(default) -> true; +enable(desc) -> ?DESC(?FUNCTION_NAME); +enable(_) -> undefined. + fields("metrics_status_fields") -> [ {"resource_metrics", ?HOCON(?R_REF("resource_metrics"), #{desc => ?DESC("metrics")})}, @@ -230,6 +206,9 @@ common_field() -> {"rate_last5m", ?HOCON(float(), #{desc => ?DESC("rate_last5m")})} ]. +provider_schema_mods() -> + ?PROVIDER_SCHEMA_MODS ++ emqx_authn_enterprise:provider_schema_mods(). + status() -> hoconsc:enum([connected, disconnected, connecting]). @@ -244,27 +223,3 @@ array(Name) -> array(Name, DescId) -> {Name, ?HOCON(?R_REF(Name), #{desc => ?DESC(DescId)})}. - -validations() -> - [ - {check_http_ssl_opts, fun(Conf) -> - CheckFun = fun emqx_authn_http:check_ssl_opts/1, - validation(Conf, CheckFun) - end}, - {check_http_headers, fun(Conf) -> - CheckFun = fun emqx_authn_http:check_headers/1, - validation(Conf, CheckFun) - end} - ]. - -validation(Conf, CheckFun) when is_map(Conf) -> - validation(hocon_maps:get(?CONF_NS, Conf), CheckFun); -validation(undefined, _) -> - ok; -validation([], _) -> - ok; -validation([AuthN | Tail], CheckFun) -> - case CheckFun(#{?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY => AuthN}) of - ok -> validation(Tail, CheckFun); - Error -> Error - end. diff --git a/apps/emqx_authn/src/emqx_authn_sup.erl b/apps/emqx_auth/src/emqx_authn/emqx_authn_sup.erl similarity index 73% rename from apps/emqx_authn/src/emqx_authn_sup.erl rename to apps/emqx_auth/src/emqx_authn/emqx_authn_sup.erl index 211ebd518..a4adc5794 100644 --- a/apps/emqx_authn/src/emqx_authn_sup.erl +++ b/apps/emqx_auth/src/emqx_authn/emqx_authn_sup.erl @@ -27,15 +27,21 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> - AuthNSup = #{ - id => emqx_authentication_sup, - start => {emqx_authentication_sup, start_link, []}, - restart => permanent, - shutdown => infinity, - type => supervisor, - modules => [emqx_authentication_sup] + SupFlags = #{ + strategy => one_for_one, + intensity => 100, + period => 10 }, - ChildSpecs = [AuthNSup], + AuthN = #{ + id => emqx_authn_chains, + start => {emqx_authn_chains, start_link, []}, + restart => permanent, + shutdown => 1000, + type => worker, + modules => [emqx_authn_chains] + }, - {ok, {{one_for_one, 10, 10}, ChildSpecs}}. + ChildSpecs = [AuthN], + + {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/emqx_authn/src/emqx_authn_user_import_api.erl b/apps/emqx_auth/src/emqx_authn/emqx_authn_user_import_api.erl similarity index 94% rename from apps/emqx_authn/src/emqx_authn_user_import_api.erl rename to apps/emqx_auth/src/emqx_authn/emqx_authn_user_import_api.erl index f9d4208e6..bac313195 100644 --- a/apps/emqx_authn/src/emqx_authn_user_import_api.erl +++ b/apps/emqx_auth/src/emqx_authn/emqx_authn_user_import_api.erl @@ -92,7 +92,7 @@ authenticator_import_users( } ) -> [{FileName, FileData}] = maps:to_list(maps:without([type], File)), - case emqx_authentication:import_users(?GLOBAL, AuthenticatorID, {FileName, FileData}) of + case emqx_authn_chains:import_users(?GLOBAL, AuthenticatorID, {FileName, FileData}) of ok -> {204}; {error, Reason} -> emqx_authn_api:serialize_error(Reason) end; @@ -110,9 +110,7 @@ listener_authenticator_import_users( emqx_authn_api:with_chain( ListenerID, fun(ChainName) -> - case - emqx_authentication:import_users(ChainName, AuthenticatorID, {FileName, FileData}) - of + case emqx_authn_chains:import_users(ChainName, AuthenticatorID, {FileName, FileData}) of ok -> {204}; {error, Reason} -> emqx_authn_api:serialize_error(Reason) end diff --git a/apps/emqx_authn/src/emqx_authn_utils.erl b/apps/emqx_auth/src/emqx_authn/emqx_authn_utils.erl similarity index 76% rename from apps/emqx_authn/src/emqx_authn_utils.erl rename to apps/emqx_auth/src/emqx_authn/emqx_authn_utils.erl index 7fc20995a..a9d672922 100644 --- a/apps/emqx_authn/src/emqx_authn_utils.erl +++ b/apps/emqx_auth/src/emqx_authn/emqx_authn_utils.erl @@ -36,7 +36,12 @@ cleanup_resources/0, make_resource_id/1, without_password/1, - to_bool/1 + to_bool/1, + parse_url/1, + convert_headers/1, + convert_headers_no_content_type/1, + default_headers/0, + default_headers_no_content_type/0 ]). -define(AUTHN_PLACEHOLDERS, [ @@ -59,7 +64,7 @@ create_resource(ResourceId, Module, Config) -> Result = emqx_resource:create_local( ResourceId, - ?RESOURCE_GROUP, + ?AUTHN_RESOURCE_GROUP, Module, Config, ?DEFAULT_RESOURCE_OPTS @@ -163,7 +168,7 @@ bin(X) -> X. cleanup_resources() -> lists:foreach( fun emqx_resource:remove_local/1, - emqx_resource:list_group_instances(?RESOURCE_GROUP) + emqx_resource:list_group_instances(?AUTHN_RESOURCE_GROUP) ). make_resource_id(Name) -> @@ -207,6 +212,49 @@ to_bool(MaybeBinInt) when is_binary(MaybeBinInt) -> to_bool(_) -> false. +parse_url(Url) -> + case string:split(Url, "//", leading) of + [Scheme, UrlRem] -> + case string:split(UrlRem, "/", leading) of + [HostPort, Remaining] -> + BaseUrl = iolist_to_binary([Scheme, "//", HostPort]), + case string:split(Remaining, "?", leading) of + [Path, QueryString] -> + {BaseUrl, <<"/", Path/binary>>, QueryString}; + [Path] -> + {BaseUrl, <<"/", Path/binary>>, <<>>} + end; + [HostPort] -> + {iolist_to_binary([Scheme, "//", HostPort]), <<>>, <<>>} + end; + [Url] -> + throw({invalid_url, Url}) + end. + +convert_headers(Headers) -> + maps:merge(default_headers(), transform_header_name(Headers)). + +convert_headers_no_content_type(Headers) -> + maps:without( + [<<"content-type">>], + maps:merge(default_headers_no_content_type(), transform_header_name(Headers)) + ). + +default_headers() -> + maps:put( + <<"content-type">>, + <<"application/json">>, + default_headers_no_content_type() + ). + +default_headers_no_content_type() -> + #{ + <<"accept">> => <<"application/json">>, + <<"cache-control">> => <<"no-cache">>, + <<"connection">> => <<"keep-alive">>, + <<"keep-alive">> => <<"timeout=30, max=1000">> + }. + %%-------------------------------------------------------------------- %% Internal functions %%-------------------------------------------------------------------- @@ -242,3 +290,20 @@ mapping_credential(C = #{cn := CN, dn := DN}) -> C#{cert_common_name => CN, cert_subject => DN}; mapping_credential(C) -> C. + +transform_header_name(Headers) -> + maps:fold( + fun(K0, V, Acc) -> + K = list_to_binary(string:to_lower(to_list(K0))), + maps:put(K, V, Acc) + end, + #{}, + Headers + ). + +to_list(A) when is_atom(A) -> + atom_to_list(A); +to_list(B) when is_binary(B) -> + binary_to_list(B); +to_list(L) when is_list(L) -> + L. diff --git a/apps/emqx_authn/src/proto/emqx_authn_proto_v1.erl b/apps/emqx_auth/src/emqx_authn/proto/emqx_authn_proto_v1.erl similarity index 100% rename from apps/emqx_authn/src/proto/emqx_authn_proto_v1.erl rename to apps/emqx_auth/src/emqx_authn/proto/emqx_authn_proto_v1.erl diff --git a/apps/emqx_authz/src/emqx_authz.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz.erl similarity index 80% rename from apps/emqx_authz/src/emqx_authz.erl rename to apps/emqx_auth/src/emqx_authz/emqx_authz.erl index 1398ef8e9..246a1c23d 100644 --- a/apps/emqx_authz/src/emqx_authz.erl +++ b/apps/emqx_auth/src/emqx_authz/emqx_authz.erl @@ -27,9 +27,12 @@ -include_lib("snabbkaffe/include/snabbkaffe.hrl"). -export([ + register_source/2, + unregister_source/1, register_metrics/0, init/0, deinit/0, + merge_defaults/1, lookup/0, lookup/1, move/2, @@ -37,6 +40,7 @@ merge/1, merge_local/2, authorize/5, + authorize_deny/4, %% for telemetry information get_enabled_authzs/0 ]). @@ -48,24 +52,26 @@ -export([post_config_update/5, pre_config_update/3]). --export([acl_conf_file/0]). +-export([ + maybe_read_source_files/1, + maybe_read_source_files_safe/1 +]). + +% -export([acl_conf_file/0]). %% Data backup -export([ import_config/1, - maybe_read_acl_file/1, - maybe_write_acl_file/1 + maybe_read_files/1, + maybe_write_files/1 ]). --type source() :: map(). - --type match_result() :: {matched, allow} | {matched, deny} | nomatch. - -type default_result() :: allow | deny. -type authz_result_value() :: #{result := allow | deny, from => _}. -type authz_result() :: {stop, authz_result_value()} | {ok, authz_result_value()} | ignore. +-type source() :: emqx_authz_source:source(). -type sources() :: [source()]. -define(METRIC_SUPERUSER, 'authorization.superuser'). @@ -75,51 +81,70 @@ -define(METRICS, [?METRIC_SUPERUSER, ?METRIC_ALLOW, ?METRIC_DENY, ?METRIC_NOMATCH]). -%% Initialize authz backend. -%% Populate the passed configuration map with necessary data, -%% like `ResourceID`s --callback create(source()) -> source(). - -%% Update authz backend. -%% Change configuration, or simply enable/disable --callback update(source()) -> source(). - -%% Destroy authz backend. -%% Make cleanup of all allocated data. -%% An authz backend will not be used after `destroy`. --callback destroy(source()) -> ok. - -%% Get authz text description. --callback description() -> string(). - -%% Authorize client action. --callback authorize( - emqx_types:clientinfo(), - emqx_types:pubsub(), - emqx_types:topic(), - source() -) -> match_result(). - --optional_callbacks([ - update/1 -]). - -spec register_metrics() -> ok. register_metrics() -> lists:foreach(fun emqx_metrics:ensure/1, ?METRICS). init() -> ok = register_metrics(), - ok = init_metrics(client_info_source()), emqx_conf:add_handler(?CONF_KEY_PATH, ?MODULE), emqx_conf:add_handler(?ROOT_KEY, ?MODULE), + emqx_authz_source_registry:create(), + ok = emqx_hooks:put('client.authorize', {?MODULE, authorize_deny, []}, ?HP_AUTHZ), + ok = register_source(client_info, emqx_authz_client_info), + ok. + +register_source(Type, Module) -> + ok = emqx_authz_source_registry:register(Type, Module), + install_sources(not is_hook_installed() andalso are_all_providers_registered()). + +unregister_source(Type) -> + ok = emqx_authz_source_registry:unregister(Type). + +is_hook_installed() -> + lists:any( + fun(Callback) -> + case emqx_hooks:callback_action(Callback) of + {?MODULE, authorize, _} -> true; + _ -> false + end + end, + emqx_hooks:lookup('client.authorize') + ). + +are_all_providers_registered() -> + try + _ = lists:foreach( + fun(Type) -> + _ = emqx_authz_source_registry:get(Type) + end, + configured_types() + ), + true + catch + {unknown_authz_source_type, _Type} -> + false + end. + +configured_types() -> + lists:map( + fun(#{type := Type}) -> Type end, + emqx_conf:get(?CONF_KEY_PATH, []) + ). + +install_sources(true) -> + ok = init_metrics(client_info_source()), Sources = emqx_conf:get(?CONF_KEY_PATH, []), ok = check_dup_types(Sources), NSources = create_sources(Sources), - ok = emqx_hooks:put('client.authorize', {?MODULE, authorize, [NSources]}, ?HP_AUTHZ). + ok = emqx_hooks:put('client.authorize', {?MODULE, authorize, [NSources]}, ?HP_AUTHZ), + ok = emqx_hooks:del('client.authorize', {?MODULE, authorize_deny}); +install_sources(false) -> + ok. deinit() -> ok = emqx_hooks:del('client.authorize', {?MODULE, authorize}), + ok = emqx_hooks:del('client.authorize', {?MODULE, authorize_deny}), emqx_conf:remove_handler(?CONF_KEY_PATH), emqx_conf:remove_handler(?ROOT_KEY), emqx_authz_utils:cleanup_resources(). @@ -163,7 +188,14 @@ pre_config_update(Path, Cmd, Sources) -> {error, Reason} -> {error, Reason}; NSources -> {ok, NSources} catch - _:Reason -> {error, Reason} + Error:Reason:Stack -> + ?SLOG(info, #{ + msg => "error_in_pre_config_update", + exception => Error, + reason => Reason, + stacktrace => Stack + }), + {error, Reason} end. do_pre_config_update(?CONF_KEY_PATH, Cmd, Sources) -> @@ -191,17 +223,17 @@ do_pre_config_replace(NewConf, OldConf) -> do_pre_config_update({?CMD_MOVE, _, _} = Cmd, Sources) -> do_move(Cmd, Sources); do_pre_config_update({?CMD_PREPEND, Source}, Sources) -> - NSource = maybe_write_files(Source), + NSource = maybe_write_source_files(Source), NSources = [NSource] ++ Sources, ok = check_dup_types(NSources), NSources; do_pre_config_update({?CMD_APPEND, Source}, Sources) -> - NSource = maybe_write_files(Source), + NSource = maybe_write_source_files(Source), NSources = Sources ++ [NSource], ok = check_dup_types(NSources), NSources; do_pre_config_update({{?CMD_REPLACE, Type}, Source}, Sources) -> - NSource = maybe_write_files(Source), + NSource = maybe_write_source_files(Source), {_Old, Front, Rear} = take(Type, Sources), NSources = Front ++ [NSource | Rear], ok = check_dup_types(NSources), @@ -212,7 +244,7 @@ do_pre_config_update({{?CMD_DELETE, Type}, _Source}, Sources) -> NSources; do_pre_config_update({?CMD_REPLACE, Sources}, _OldSources) -> %% overwrite the entire config! - NSources = lists:map(fun maybe_write_files/1, Sources), + NSources = lists:map(fun maybe_write_source_files/1, Sources), ok = check_dup_types(NSources), NSources; do_pre_config_update({Op, Source}, Sources) -> @@ -356,6 +388,32 @@ init_metrics(Source) -> %% AuthZ callbacks %%------------------------------------------------------------------------------ +-spec authorize_deny( + emqx_types:clientinfo(), + emqx_types:pubsub(), + emqx_types:topic(), + default_result() +) -> + {stop, #{result => deny, from => ?MODULE}}. +authorize_deny( + #{ + username := Username, + peerhost := IpAddress + } = _Client, + _PubSub, + Topic, + _DefaultResult +) -> + emqx_metrics:inc(?METRIC_DENY), + ?SLOG(warning, #{ + msg => "authorization_not_initialized", + username => Username, + ipaddr => IpAddress, + topic => Topic, + source => ?MODULE + }), + {stop, #{result => deny, from => ?MODULE}}. + %% @doc Check AuthZ -spec authorize( emqx_types:clientinfo(), @@ -502,29 +560,23 @@ changed_paths(OldSources, NewSources) -> Changed = maps:get(changed, emqx_utils:diff_lists(NewSources, OldSources, fun type/1)), [?CONF_KEY_PATH ++ [type(OldSource)] || {OldSource, _} <- Changed]. -maybe_read_acl_file(RawConf) -> - maybe_convert_acl_file(RawConf, fun read_acl_file/1). +maybe_read_files(RawConf) -> + maybe_convert_sources(RawConf, fun maybe_read_source_files/1). -maybe_write_acl_file(RawConf) -> - maybe_convert_acl_file(RawConf, fun write_acl_file/1). +maybe_write_files(RawConf) -> + maybe_convert_sources(RawConf, fun maybe_write_source_files/1). -maybe_convert_acl_file( +maybe_convert_sources( #{?CONF_NS_BINARY := #{<<"sources">> := Sources} = AuthRawConf} = RawConf, Fun ) -> - Sources1 = lists:map( - fun - (#{<<"type">> := <<"file">>} = FileSource) -> Fun(FileSource); - (Source) -> Source - end, - Sources - ), + Sources1 = lists:map(Fun, Sources), RawConf#{?CONF_NS_BINARY => AuthRawConf#{<<"sources">> => Sources1}}; -maybe_convert_acl_file(RawConf, _Fun) -> +maybe_convert_sources(RawConf, _Fun) -> RawConf. -read_acl_file(#{<<"path">> := Path} = Source) -> - {ok, Rules} = emqx_authz_file:read_file(Path), - maps:remove(<<"path">>, Source#{<<"rules">> => Rules}). +% read_acl_file(#{<<"path">> := Path} = Source) -> +% {ok, Rules} = emqx_authz_file:read_file(Path), +% maps:remove(<<"path">>, Source#{<<"rules">> => Rules}). %%------------------------------------------------------------------------------ %% Extended Features @@ -566,59 +618,79 @@ take(Type, Sources) -> end. find_action_in_hooks() -> - Callbacks = emqx_hooks:lookup('client.authorize'), - [Action] = [Action || {callback, {?MODULE, authorize, _} = Action, _, _} <- Callbacks], - Action. - -authz_module(built_in_database) -> - emqx_authz_mnesia; -authz_module(Type) -> - case emqx_authz_enterprise:is_enterprise_module(Type) of - {ok, Module} -> - Module; - _ -> - list_to_existing_atom("emqx_authz_" ++ atom_to_list(Type)) + Actions = lists:filtermap( + fun(Callback) -> + case emqx_hooks:callback_action(Callback) of + {?MODULE, authorize, _} = Action -> {true, Action}; + _ -> false + end + end, + emqx_hooks:lookup('client.authorize') + ), + case Actions of + [] -> + ?SLOG(error, #{ + msg => "authz_not_initialized", + configured_types => configured_types(), + registered_types => emqx_authz_source_registry:get() + }), + error(authz_not_initialized); + [Action] -> + Action end. -type(#{type := Type}) -> type(Type); -type(#{<<"type">> := Type}) -> type(Type); -type(file) -> file; -type(<<"file">>) -> file; -type(http) -> http; -type(<<"http">>) -> http; -type(mongodb) -> mongodb; -type(<<"mongodb">>) -> mongodb; -type(mysql) -> mysql; -type(<<"mysql">>) -> mysql; -type(redis) -> redis; -type(<<"redis">>) -> redis; -type(postgresql) -> postgresql; -type(<<"postgresql">>) -> postgresql; -type(built_in_database) -> built_in_database; -type(<<"built_in_database">>) -> built_in_database; -type(client_info) -> client_info; -type(<<"client_info">>) -> client_info; -type(MaybeEnterprise) -> emqx_authz_enterprise:type(MaybeEnterprise). +authz_module(Type) -> + emqx_authz_source_registry:module(Type). -maybe_write_files(#{<<"type">> := <<"file">>} = Source) -> - write_acl_file(Source); -maybe_write_files(NewSource) -> - maybe_write_certs(NewSource). +type(#{type := Type}) -> + type(Type); +type(#{<<"type">> := Type}) -> + type(Type); +type(Type) when is_atom(Type) orelse is_binary(Type) -> + emqx_authz_source_registry:get(Type). -write_acl_file(#{<<"rules">> := Rules} = Source0) -> - AclPath = ?MODULE:acl_conf_file(), - %% Always check if the rules are valid before writing to the file - %% If the rules are invalid, the old file will be kept - ok = check_acl_file_rules(AclPath, Rules), - ok = write_file(AclPath, Rules), - Source1 = maps:remove(<<"rules">>, Source0), - maps:put(<<"path">>, AclPath, Source1); -write_acl_file(Source) -> - Source. +merge_defaults(Source) -> + Type = type(Source), + Mod = authz_module(Type), + try + Mod:merge_defaults(Source) + catch + error:undef -> + Source + end. -%% @doc where the acl.conf file is stored. -acl_conf_file() -> - filename:join([emqx:data_dir(), "authz", "acl.conf"]). +maybe_write_source_files(Source) -> + Module = authz_module(type(Source)), + case erlang:function_exported(Module, write_files, 1) of + true -> + Module:write_files(Source); + false -> + maybe_write_certs(Source) + end. + +maybe_read_source_files(Source) -> + Module = authz_module(type(Source)), + case erlang:function_exported(Module, read_files, 1) of + true -> + Module:read_files(Source); + false -> + Source + end. + +maybe_read_source_files_safe(Source0) -> + try maybe_read_source_files(Source0) of + Source1 -> + {ok, Source1} + catch + Error:Reason:Stacktrace -> + ?SLOG(error, #{ + msg => "error_in_maybe_read_source_files", + exception => Error, + reason => Reason, + stacktrace => Stacktrace + }), + {error, Reason} + end. maybe_write_certs(#{<<"type">> := Type, <<"ssl">> := SSL = #{}} = Source) -> case emqx_tls_lib:ensure_ssl_files(ssl_file_path(Type), SSL) of @@ -631,16 +703,6 @@ maybe_write_certs(#{<<"type">> := Type, <<"ssl">> := SSL = #{}} = Source) -> maybe_write_certs(#{} = Source) -> Source. -write_file(Filename, Bytes) -> - ok = filelib:ensure_dir(Filename), - case file:write_file(Filename, Bytes) of - ok -> - ok; - {error, Reason} -> - ?SLOG(error, #{filename => Filename, msg => "write_file_error", reason => Reason}), - throw(Reason) - end. - ssl_file_path(Type) -> filename:join(["authz", Type]). @@ -652,18 +714,6 @@ get_source_by_type(Type, Sources) -> update_authz_chain(Actions) -> emqx_hooks:put('client.authorize', {?MODULE, authorize, [Actions]}, ?HP_AUTHZ). -check_acl_file_rules(Path, Rules) -> - TmpPath = Path ++ ".tmp", - try - ok = write_file(TmpPath, Rules), - {ok, _} = emqx_authz_file:validate(TmpPath), - ok - catch - throw:Reason -> throw(Reason) - after - _ = file:delete(TmpPath) - end. - merge_sources(OriginConf, NewConf) -> {OriginSource, NewSources} = lists:foldl( diff --git a/apps/emqx_authz/src/emqx_authz_api_cache.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz_api_cache.erl similarity index 100% rename from apps/emqx_authz/src/emqx_authz_api_cache.erl rename to apps/emqx_auth/src/emqx_authz/emqx_authz_api_cache.erl diff --git a/apps/emqx_authz/src/emqx_authz_api_settings.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz_api_settings.erl similarity index 98% rename from apps/emqx_authz/src/emqx_authz_api_settings.erl rename to apps/emqx_auth/src/emqx_authz/emqx_authz_api_settings.erl index db915a795..13c330d35 100644 --- a/apps/emqx_authz/src/emqx_authz_api_settings.erl +++ b/apps/emqx_auth/src/emqx_authz/emqx_authz_api_settings.erl @@ -20,8 +20,6 @@ -include_lib("hocon/include/hoconsc.hrl"). --import(hoconsc, [mk/1, ref/2]). - -export([ api_spec/0, paths/0, diff --git a/apps/emqx_authz/src/emqx_authz_api_sources.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz_api_sources.erl similarity index 86% rename from apps/emqx_authz/src/emqx_authz_api_sources.erl rename to apps/emqx_auth/src/emqx_authz/emqx_authz_api_sources.erl index d332f009f..247f3a9ac 100644 --- a/apps/emqx_authz/src/emqx_authz_api_sources.erl +++ b/apps/emqx_auth/src/emqx_authz/emqx_authz_api_sources.erl @@ -22,13 +22,11 @@ -include_lib("emqx/include/logger.hrl"). -include_lib("hocon/include/hoconsc.hrl"). --import(hoconsc, [mk/1, mk/2, ref/2, array/1, enum/1]). +-import(hoconsc, [mk/1, mk/2, ref/2, enum/1]). -define(BAD_REQUEST, 'BAD_REQUEST'). -define(NOT_FOUND, 'NOT_FOUND'). --define(API_SCHEMA_MODULE, emqx_authz_api_schema). - -export([ get_raw_sources/0, get_raw_source/1, @@ -65,7 +63,19 @@ paths() -> ]. fields(sources) -> - [{sources, mk(array(hoconsc:union(authz_sources_type_refs())), #{desc => ?DESC(sources)})}]. + emqx_authz_schema:api_authz_fields(); +fields(position) -> + [ + {position, + mk( + string(), + #{ + desc => ?DESC(position), + required => true, + in => body + } + )} + ]. %%-------------------------------------------------------------------- %% Schema for each URI @@ -87,7 +97,7 @@ schema("/authorization/sources") -> description => ?DESC(authorization_sources_post), tags => ?TAGS, 'requestBody' => mk( - hoconsc:union(authz_sources_type_refs()), + emqx_authz_schema:api_source_type(), #{desc => ?DESC(source_config)} ), responses => @@ -111,7 +121,7 @@ schema("/authorization/sources/:type") -> responses => #{ 200 => mk( - hoconsc:union(authz_sources_type_refs()), + emqx_authz_schema:api_source_type(), #{desc => ?DESC(source)} ), 404 => emqx_dashboard_swagger:error_codes([?NOT_FOUND], <<"Not Found">>) @@ -122,7 +132,7 @@ schema("/authorization/sources/:type") -> description => ?DESC(authorization_sources_type_put), tags => ?TAGS, parameters => parameters_field(), - 'requestBody' => mk(hoconsc:union(authz_sources_type_refs())), + 'requestBody' => mk(emqx_authz_schema:api_source_type()), responses => #{ 204 => <<"Authorization source updated successfully">>, @@ -172,7 +182,7 @@ schema("/authorization/sources/:type/move") -> parameters => parameters_field(), 'requestBody' => emqx_dashboard_swagger:schema_with_examples( - ref(?API_SCHEMA_MODULE, position), + ref(?MODULE, position), position_example() ), responses => @@ -196,35 +206,14 @@ sources(Method, #{bindings := #{type := Type} = Bindings} = Req) when sources(Method, Req#{bindings => Bindings#{type => atom_to_binary(Type, utf8)}}); sources(get, _) -> Sources = lists:foldl( - fun - ( - #{ - <<"type">> := <<"file">>, - <<"enable">> := Enable, - <<"path">> := Path - }, - AccIn - ) -> - case emqx_authz_file:read_file(Path) of - {ok, Rules} -> - lists:append(AccIn, [ - #{ - type => file, - enable => Enable, - rules => Rules - } - ]); - {error, _} -> - lists:append(AccIn, [ - #{ - type => file, - enable => Enable, - rules => <<"">> - } - ]) - end; - (Source, AccIn) -> - lists:append(AccIn, [Source]) + fun(Source0, AccIn) -> + try emqx_authz:maybe_read_source_files(Source0) of + Source1 -> + lists:append(AccIn, [Source1]) + catch + _Error:_Reason -> + lists:append(AccIn, [Source0]) + end end, [], get_raw_sources() @@ -240,23 +229,17 @@ source(Method, #{bindings := #{type := Type} = Bindings} = Req) when source(get, #{bindings := #{type := Type}}) -> with_source( Type, - fun - (#{<<"type">> := <<"file">>, <<"enable">> := Enable, <<"path">> := Path}) -> - case emqx_authz_file:read_file(Path) of - {ok, Rules} -> - {200, #{ - type => file, - enable => Enable, - rules => Rules - }}; - {error, Reason} -> - {500, #{ - code => <<"INTERNAL_ERROR">>, - message => bin(Reason) - }} - end; - (Source) -> - {200, Source} + fun(Source0) -> + try emqx_authz:maybe_read_source_files(Source0) of + Source1 -> + {200, Source1} + catch + _Error:Reason -> + {500, #{ + code => <<"INTERNAL_ERROR">>, + message => bin(Reason) + }} + end end ); source(put, #{bindings := #{type := Type}, body := #{<<"type">> := Type} = Body}) -> @@ -474,29 +457,10 @@ get_raw_sources() -> Schema = emqx_hocon:make_schema(emqx_authz_schema:authz_fields()), Conf = #{<<"sources">> => RawSources}, #{<<"sources">> := Sources} = hocon_tconf:make_serializable(Schema, Conf, #{}), - merge_default_headers(Sources). + merge_defaults(Sources). -merge_default_headers(Sources) -> - lists:map( - fun(Source) -> - case maps:find(<<"headers">>, Source) of - {ok, Headers} -> - NewHeaders = - case Source of - #{<<"method">> := <<"get">>} -> - (emqx_authz_schema:headers_no_content_type(converter))(Headers); - #{<<"method">> := <<"post">>} -> - (emqx_authz_schema:headers(converter))(Headers); - _ -> - Headers - end, - Source#{<<"headers">> => NewHeaders}; - error -> - Source - end - end, - Sources - ). +merge_defaults(Sources) -> + lists:map(fun emqx_authz:merge_defaults/1, Sources). get_raw_source(Type) -> lists:filter( @@ -546,7 +510,7 @@ parameters_field() -> [ {type, mk( - enum(?API_SCHEMA_MODULE:authz_sources_types(simple)), + enum(emqx_authz_schema:source_types()), #{in => path, desc => ?DESC(source_type)} )} ]. @@ -590,12 +554,6 @@ position_example() -> } }. -authz_sources_type_refs() -> - [ - ref(?API_SCHEMA_MODULE, Type) - || Type <- emqx_authz_api_schema:authz_sources_types(detailed) - ]. - bin(Term) -> erlang:iolist_to_binary(io_lib:format("~p", [Term])). status_metrics_example() -> diff --git a/apps/emqx_authz/src/emqx_authz_app.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz_app.erl similarity index 100% rename from apps/emqx_authz/src/emqx_authz_app.erl rename to apps/emqx_auth/src/emqx_authz/emqx_authz_app.erl diff --git a/apps/emqx_authz/src/emqx_authz_client_info.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz_client_info.erl similarity index 99% rename from apps/emqx_authz/src/emqx_authz_client_info.erl rename to apps/emqx_auth/src/emqx_authz/emqx_authz_client_info.erl index 3454d56fe..ae8360943 100644 --- a/apps/emqx_authz/src/emqx_authz_client_info.erl +++ b/apps/emqx_auth/src/emqx_authz/emqx_authz_client_info.erl @@ -18,7 +18,7 @@ -include_lib("emqx/include/logger.hrl"). --behaviour(emqx_authz). +-behaviour(emqx_authz_source). -ifdef(TEST). -compile(export_all). diff --git a/apps/emqx_auth/src/emqx_authz/emqx_authz_enterprise.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz_enterprise.erl new file mode 100644 index 000000000..e601d9d9b --- /dev/null +++ b/apps/emqx_auth/src/emqx_authz/emqx_authz_enterprise.erl @@ -0,0 +1,24 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +-module(emqx_authz_enterprise). + +-include("emqx_authz_schema.hrl"). + +-export([ + source_schema_mods/0 +]). + +-if(?EMQX_RELEASE_EDITION == ee). + +source_schema_mods() -> + ?EE_SOURCE_SCHEMA_MODS. + +-else. + +-dialyzer({nowarn_function, [source_schema_mods/0]}). + +source_schema_mods() -> + []. + +-endif. diff --git a/apps/emqx_authz/src/emqx_authz_rule.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz_rule.erl similarity index 100% rename from apps/emqx_authz/src/emqx_authz_rule.erl rename to apps/emqx_auth/src/emqx_authz/emqx_authz_rule.erl diff --git a/apps/emqx_authz/src/emqx_authz_rule_raw.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz_rule_raw.erl similarity index 100% rename from apps/emqx_authz/src/emqx_authz_rule_raw.erl rename to apps/emqx_auth/src/emqx_authz/emqx_authz_rule_raw.erl diff --git a/apps/emqx_auth/src/emqx_authz/emqx_authz_schema.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz_schema.erl new file mode 100644 index 000000000..e851f6661 --- /dev/null +++ b/apps/emqx_auth/src/emqx_authz/emqx_authz_schema.erl @@ -0,0 +1,231 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authz_schema). + +-include("emqx_authz.hrl"). +-include("emqx_authz_schema.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-export([ + roots/0, + fields/1, + desc/1 +]). + +-export([ + authz_fields/0, + api_authz_fields/0, + api_source_type/0, + source_types/0 +]). + +-export([ + injected_fields/0 +]). + +-export([ + default_authz/0, + authz_common_fields/1 +]). + +%%-------------------------------------------------------------------- +%% Authz Source Schema Behaviour +%%-------------------------------------------------------------------- + +-type schema_ref() :: ?R_REF(module(), hocon_schema:name()). + +-callback type() -> emqx_authz_source:source_type(). +-callback source_refs() -> [schema_ref()]. +-callback select_union_member(emqx_config:raw_config()) -> schema_ref() | undefined | no_return(). +-callback fields(hocon_schema:name()) -> [hocon_schema:field()]. +-callback api_source_refs() -> [schema_ref()]. + +-optional_callbacks([ + api_source_refs/0 +]). + +%%-------------------------------------------------------------------- +%% Hocon Schema +%%-------------------------------------------------------------------- + +roots() -> []. + +fields(?CONF_NS) -> + emqx_schema:authz_fields() ++ authz_fields(); +fields("metrics_status_fields") -> + [ + {"resource_metrics", ?HOCON(?R_REF("resource_metrics"), #{desc => ?DESC("metrics")})}, + {"node_resource_metrics", array("node_resource_metrics", "node_metrics")}, + {"metrics", ?HOCON(?R_REF("metrics"), #{desc => ?DESC("metrics")})}, + {"node_metrics", array("node_metrics")}, + {"status", ?HOCON(cluster_status(), #{desc => ?DESC("status")})}, + {"node_status", array("node_status")}, + {"node_error", array("node_error")} + ]; +fields("metrics") -> + [ + {"total", ?HOCON(integer(), #{desc => ?DESC("metrics_total")})}, + {"allow", ?HOCON(integer(), #{desc => ?DESC("allow")})}, + {"deny", ?HOCON(integer(), #{desc => ?DESC("deny")})}, + {"nomatch", ?HOCON(float(), #{desc => ?DESC("nomatch")})} + ] ++ common_rate_field(); +fields("node_metrics") -> + [ + node_name(), + {"metrics", ?HOCON(?R_REF("metrics"), #{desc => ?DESC("metrics")})} + ]; +fields("resource_metrics") -> + common_field(); +fields("node_resource_metrics") -> + [ + node_name(), + {"metrics", ?HOCON(?R_REF("resource_metrics"), #{desc => ?DESC("metrics")})} + ]; +fields("node_status") -> + [ + node_name(), + {"status", ?HOCON(status(), #{desc => ?DESC("node_status")})} + ]; +fields("node_error") -> + [ + node_name(), + {"error", ?HOCON(string(), #{desc => ?DESC("node_error")})} + ]. + +desc(?CONF_NS) -> + ?DESC(?CONF_NS); +desc(_) -> + undefined. + +%%-------------------------------------------------------------------- +%% emqx_schema_hooks behaviour +%%-------------------------------------------------------------------- + +injected_fields() -> + #{ + 'roots.high' => [ + {?CONF_NS, ?HOCON(?R_REF("authorization"), #{desc => ?DESC(?CONF_NS)})} + ] + }. + +authz_fields() -> + AuthzSchemaMods = source_schema_mods(), + AllTypes = lists:concat([Mod:source_refs() || Mod <- AuthzSchemaMods]), + UnionMemberSelector = + fun + (all_union_members) -> AllTypes; + %% must return list + ({value, Value}) -> [select_union_member(Value, AuthzSchemaMods)] + end, + [ + {sources, + ?HOCON( + ?ARRAY(?UNION(UnionMemberSelector)), + #{ + default => [default_authz()], + desc => ?DESC(sources), + %% doc_lift is force a root level reference instead of nesting sub-structs + extra => #{doc_lift => true}, + %% it is recommended to configure authz sources from dashboard + %% hence the importance level for config is low + importance => ?IMPORTANCE_LOW + } + )} + ]. + +api_authz_fields() -> + [{sources, ?HOCON(?ARRAY(api_source_type()), #{desc => ?DESC(sources)})}]. + +api_source_type() -> + ?UNION(api_authz_refs()). + +api_authz_refs() -> + lists:concat([api_source_refs(Mod) || Mod <- source_schema_mods()]). + +authz_common_fields(Type) -> + [ + {type, ?HOCON(Type, #{required => true, desc => ?DESC(type)})}, + {enable, ?HOCON(boolean(), #{default => true, desc => ?DESC(enable)})} + ]. + +source_types() -> + [Mod:type() || Mod <- source_schema_mods()]. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +api_source_refs(Mod) -> + try + Mod:api_source_refs() + catch + error:undef -> + Mod:source_refs() + end. + +source_schema_mods() -> + ?SOURCE_SCHEMA_MODS ++ emqx_authz_enterprise:source_schema_mods(). + +common_rate_field() -> + [ + {"rate", ?HOCON(float(), #{desc => ?DESC("rate")})}, + {"rate_max", ?HOCON(float(), #{desc => ?DESC("rate_max")})}, + {"rate_last5m", ?HOCON(float(), #{desc => ?DESC("rate_last5m")})} + ]. + +array(Ref) -> array(Ref, Ref). + +array(Ref, DescId) -> + ?HOCON(?ARRAY(?R_REF(Ref)), #{desc => ?DESC(DescId)}). + +select_union_member(#{<<"type">> := Type}, []) -> + throw(#{ + reason => "unknown_authz_type", + got => Type + }); +select_union_member(#{<<"type">> := _} = Value, [Mod | Mods]) -> + case Mod:select_union_member(Value) of + undefined -> + select_union_member(Value, Mods); + Member -> + Member + end; +select_union_member(_Value, _Mods) -> + throw("missing_type_field"). + +default_authz() -> + #{ + <<"type">> => <<"file">>, + <<"enable">> => true, + <<"path">> => <<"${EMQX_ETC_DIR}/acl.conf">> + }. + +common_field() -> + [ + {"matched", ?HOCON(integer(), #{desc => ?DESC("matched")})}, + {"success", ?HOCON(integer(), #{desc => ?DESC("success")})}, + {"failed", ?HOCON(integer(), #{desc => ?DESC("failed")})} + ] ++ common_rate_field(). + +status() -> + hoconsc:enum([connected, disconnected, connecting]). + +cluster_status() -> + hoconsc:enum([connected, disconnected, connecting, inconsistent]). + +node_name() -> + {"node", ?HOCON(binary(), #{desc => ?DESC("node"), example => "emqx@127.0.0.1"})}. diff --git a/apps/emqx_auth/src/emqx_authz/emqx_authz_source.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz_source.erl new file mode 100644 index 000000000..03834cc6f --- /dev/null +++ b/apps/emqx_auth/src/emqx_authz/emqx_authz_source.erl @@ -0,0 +1,69 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authz_source). + +-type source_type() :: atom(). +-type source() :: #{type => source_type(), _ => _}. +-type raw_source() :: map(). +-type match_result() :: {matched, allow} | {matched, deny} | nomatch. + +-export_type([ + source_type/0, + source/0, + match_result/0 +]). + +%% Initialize authz backend. +%% Populate the passed configuration map with necessary data, +%% like `ResourceID`s +-callback create(source()) -> source(). + +%% Update authz backend. +%% Change configuration, or simply enable/disable +-callback update(source()) -> source(). + +%% Destroy authz backend. +%% Make cleanup of all allocated data. +%% An authz backend will not be used after `destroy`. +-callback destroy(source()) -> ok. + +%% Get authz text description. +-callback description() -> string(). + +%% Authorize client action. +-callback authorize( + emqx_types:clientinfo(), + emqx_types:pubsub(), + emqx_types:topic(), + source() +) -> match_result(). + +%% Convert filepath values to the content of the files. +-callback write_files(raw_source()) -> raw_source() | no_return(). + +%% Convert filepath values to the content of the files. +-callback read_files(raw_source()) -> raw_source() | no_return(). + +%% Merge default values to the source, for example, for exposing via API +-callback merge_defaults(raw_source()) -> raw_source(). + +-optional_callbacks([ + update/1, + write_files/1, + read_files/1, + merge_defaults/1 +]). diff --git a/apps/emqx_auth/src/emqx_authz/emqx_authz_source_registry.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz_source_registry.erl new file mode 100644 index 000000000..e9666a596 --- /dev/null +++ b/apps/emqx_auth/src/emqx_authz/emqx_authz_source_registry.erl @@ -0,0 +1,78 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authz_source_registry). + +-export([ + create/0, + register/2, + unregister/1, + get/0, + get/1, + module/1 +]). + +-define(TAB, ?MODULE). + +-type fuzzy_type() :: emqx_authz_source:source_type() | binary(). + +-spec create() -> ok. +create() -> + _ = ets:new(?TAB, [named_table, public, set]), + ok. + +-spec register(emqx_authz_source:source_type(), module()) -> ok | {error, term()}. +register(Type, Module) when is_atom(Type) andalso is_atom(Module) -> + case ets:insert_new(?TAB, {Type, Type, Module}) of + true -> + _ = ets:insert(?TAB, {atom_to_binary(Type), Type, Module}), + ok; + false -> + {error, {already_registered, Type}} + end. + +-spec unregister(emqx_authz_source:source_type()) -> ok. +unregister(Type) when is_atom(Type) -> + _ = ets:delete(?TAB, Type), + _ = ets:delete(?TAB, atom_to_binary(Type)), + ok. + +-spec get(fuzzy_type()) -> + emqx_authz_source:source_type() | no_return(). +get(FuzzyType) when is_atom(FuzzyType) orelse is_binary(FuzzyType) -> + case ets:lookup(?TAB, FuzzyType) of + [] -> + throw({unknown_authz_source_type, FuzzyType}); + [{FuzzyType, Type, _Module}] -> + Type + end. + +-spec get() -> [emqx_authz_source:source_type()]. +get() -> + Types = lists:map( + fun({_, Type, _}) -> Type end, + ets:tab2list(?TAB) + ), + lists:usort(Types). + +-spec module(fuzzy_type()) -> module() | no_return(). +module(FuzzyType) when is_atom(FuzzyType) orelse is_binary(FuzzyType) -> + case ets:lookup(?TAB, FuzzyType) of + [] -> + throw({unknown_authz_source_type, FuzzyType}); + [{FuzzyType, _Type, Module}] -> + Module + end. diff --git a/apps/emqx_authz/src/emqx_authz_sup.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz_sup.erl similarity index 100% rename from apps/emqx_authz/src/emqx_authz_sup.erl rename to apps/emqx_auth/src/emqx_authz/emqx_authz_sup.erl diff --git a/apps/emqx_authz/src/emqx_authz_utils.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz_utils.erl similarity index 97% rename from apps/emqx_authz/src/emqx_authz_utils.erl rename to apps/emqx_auth/src/emqx_authz/emqx_authz_utils.erl index d903ae027..3a0d4f1a1 100644 --- a/apps/emqx_authz/src/emqx_authz_utils.erl +++ b/apps/emqx_auth/src/emqx_authz/emqx_authz_utils.erl @@ -24,6 +24,7 @@ create_resource/2, create_resource/3, update_resource/2, + remove_resource/1, update_config/2, parse_deep/2, parse_str/2, @@ -59,7 +60,7 @@ create_resource(Module, Config) -> create_resource(ResourceId, Module, Config) -> Result = emqx_resource:create_local( ResourceId, - ?RESOURCE_GROUP, + ?AUTHZ_RESOURCE_GROUP, Module, Config, ?DEFAULT_RESOURCE_OPTS @@ -81,6 +82,9 @@ update_resource(Module, #{annotations := #{id := ResourceId}} = Config) -> end, start_resource_if_enabled(Result, ResourceId, Config). +remove_resource(ResourceId) -> + emqx_resource:remove_local(ResourceId). + start_resource_if_enabled({ok, _} = Result, ResourceId, #{enable := true}) -> _ = emqx_resource:start(ResourceId), Result; @@ -90,7 +94,7 @@ start_resource_if_enabled(Result, _ResourceId, _Config) -> cleanup_resources() -> lists:foreach( fun emqx_resource:remove_local/1, - emqx_resource:list_group_instances(?RESOURCE_GROUP) + emqx_resource:list_group_instances(?AUTHZ_RESOURCE_GROUP) ). make_resource_id(Name) -> diff --git a/apps/emqx_authz/src/proto/emqx_authz_proto_v1.erl b/apps/emqx_auth/src/emqx_authz/proto/emqx_authz_proto_v1.erl similarity index 100% rename from apps/emqx_authz/src/proto/emqx_authz_proto_v1.erl rename to apps/emqx_auth/src/emqx_authz/proto/emqx_authz_proto_v1.erl diff --git a/apps/emqx_authn/test/data/certs/ca.crt b/apps/emqx_auth/test/data/certs/ca.crt similarity index 100% rename from apps/emqx_authn/test/data/certs/ca.crt rename to apps/emqx_auth/test/data/certs/ca.crt diff --git a/apps/emqx_authn/test/data/certs/client.crt b/apps/emqx_auth/test/data/certs/client.crt similarity index 100% rename from apps/emqx_authn/test/data/certs/client.crt rename to apps/emqx_auth/test/data/certs/client.crt diff --git a/apps/emqx_authn/test/data/certs/client.key b/apps/emqx_auth/test/data/certs/client.key similarity index 100% rename from apps/emqx_authn/test/data/certs/client.key rename to apps/emqx_auth/test/data/certs/client.key diff --git a/apps/emqx_authn/test/data/certs/server.crt b/apps/emqx_auth/test/data/certs/server.crt similarity index 100% rename from apps/emqx_authn/test/data/certs/server.crt rename to apps/emqx_auth/test/data/certs/server.crt diff --git a/apps/emqx_authn/test/data/certs/server.key b/apps/emqx_auth/test/data/certs/server.key similarity index 100% rename from apps/emqx_authn/test/data/certs/server.key rename to apps/emqx_auth/test/data/certs/server.key diff --git a/apps/emqx_authn/test/data/private_key.pem b/apps/emqx_auth/test/data/private_key.pem similarity index 100% rename from apps/emqx_authn/test/data/private_key.pem rename to apps/emqx_auth/test/data/private_key.pem diff --git a/apps/emqx_authn/test/data/public_key.pem b/apps/emqx_auth/test/data/public_key.pem similarity index 100% rename from apps/emqx_authn/test/data/public_key.pem rename to apps/emqx_auth/test/data/public_key.pem diff --git a/apps/emqx_authn/test/data/user-credentials-malformed-0.json b/apps/emqx_auth/test/data/user-credentials-malformed-0.json similarity index 100% rename from apps/emqx_authn/test/data/user-credentials-malformed-0.json rename to apps/emqx_auth/test/data/user-credentials-malformed-0.json diff --git a/apps/emqx_authn/test/data/user-credentials-malformed-1.json b/apps/emqx_auth/test/data/user-credentials-malformed-1.json similarity index 100% rename from apps/emqx_authn/test/data/user-credentials-malformed-1.json rename to apps/emqx_auth/test/data/user-credentials-malformed-1.json diff --git a/apps/emqx_authn/test/data/user-credentials-malformed.csv b/apps/emqx_auth/test/data/user-credentials-malformed.csv similarity index 100% rename from apps/emqx_authn/test/data/user-credentials-malformed.csv rename to apps/emqx_auth/test/data/user-credentials-malformed.csv diff --git a/apps/emqx_authn/test/data/user-credentials.csv b/apps/emqx_auth/test/data/user-credentials.csv similarity index 100% rename from apps/emqx_authn/test/data/user-credentials.csv rename to apps/emqx_auth/test/data/user-credentials.csv diff --git a/apps/emqx_authn/test/data/user-credentials.json b/apps/emqx_auth/test/data/user-credentials.json similarity index 100% rename from apps/emqx_authn/test/data/user-credentials.json rename to apps/emqx_auth/test/data/user-credentials.json diff --git a/apps/emqx_authn/test/emqx_authn_SUITE.erl b/apps/emqx_auth/test/emqx_authn/emqx_authn_SUITE.erl similarity index 79% rename from apps/emqx_authn/test/emqx_authn_SUITE.erl rename to apps/emqx_auth/test/emqx_authn/emqx_authn_SUITE.erl index b3c786875..f27c08494 100644 --- a/apps/emqx_authn/test/emqx_authn_SUITE.erl +++ b/apps/emqx_auth/test/emqx_authn/emqx_authn_SUITE.erl @@ -53,9 +53,36 @@ end_per_testcase(Case, Config) -> %% Testcases %%================================================================================= +t_fill_defaults({init, Config}) -> + Config; +t_fill_defaults({'end', _Config}) -> + ok; +t_fill_defaults(Config) when is_list(Config) -> + Conf0 = #{ + <<"mechanism">> => <<"password_based">>, + <<"backend">> => <<"mysql">>, + <<"query">> => <<"SELECT 1">>, + <<"server">> => <<"mysql:3306">>, + <<"database">> => <<"mqtt">> + }, + + %% Missing defaults are filled + ?assertMatch( + #{<<"query_timeout">> := _}, + emqx_authn:fill_defaults(Conf0) + ), + + Conf1 = Conf0#{<<"mechanism">> => <<"unknown-xx">>}, + %% fill_defaults (check_config formerly) is actually never called on unvalidated config + %% so it will not meet validation errors + %% However, we still test it here + ?assertThrow( + #{reason := "unknown_mechanism"}, emqx_authn:fill_defaults(Conf1) + ). + t_will_message_connection_denied({init, Config}) -> - emqx_common_test_helpers:start_apps([emqx_conf, emqx_authn]), - mria:clear_table(emqx_authn_mnesia), + emqx_common_test_helpers:start_apps([emqx_conf, emqx_auth, emqx_auth_file]), + emqx_authn_test_lib:register_fake_providers([{password_based, built_in_database}]), AuthnConfig = #{ <<"mechanism">> => <<"password_based">>, <<"backend">> => <<"built_in_database">>, @@ -68,7 +95,7 @@ t_will_message_connection_denied({init, Config}) -> ), User = #{user_id => <<"subscriber">>, password => <<"p">>}, AuthenticatorID = <<"password_based:built_in_database">>, - {ok, _} = emqx_authentication:add_user( + {ok, _} = emqx_authn_chains:add_user( Chain, AuthenticatorID, User @@ -79,10 +106,11 @@ t_will_message_connection_denied({'end', _Config}) -> [authentication], {delete_authenticator, 'mqtt:global', <<"password_based:built_in_database">>} ), - emqx_common_test_helpers:stop_apps([emqx_authn, emqx_conf]), - mria:clear_table(emqx_authn_mnesia), + emqx_common_test_helpers:stop_apps([emqx_auth_file, emqx_auth, emqx_conf]), ok; t_will_message_connection_denied(Config) when is_list(Config) -> + process_flag(trap_exit, true), + {ok, Subscriber} = emqtt:start_link([ {clientid, <<"subscriber">>}, {password, <<"p">>} @@ -90,8 +118,6 @@ t_will_message_connection_denied(Config) when is_list(Config) -> {ok, _} = emqtt:connect(Subscriber), {ok, _, [?RC_SUCCESS]} = emqtt:subscribe(Subscriber, <<"lwt">>), - process_flag(trap_exit, true), - {ok, Publisher} = emqtt:start_link([ {clientid, <<"publisher">>}, {will_topic, <<"lwt">>}, @@ -120,7 +146,10 @@ t_will_message_connection_denied(Config) when is_list(Config) -> %% With auth enabled, send CONNECT without password field, %% expect CONNACK with reason_code=5 and socket close t_password_undefined({init, Config}) -> - emqx_common_test_helpers:start_apps([emqx_conf, emqx_authn]), + emqx_common_test_helpers:start_apps([emqx_conf, emqx_auth]), + emqx_authn_test_lib:register_fake_providers([ + {password_based, built_in_database} + ]), AuthnConfig = #{ <<"mechanism">> => <<"password_based">>, <<"backend">> => <<"built_in_database">>, @@ -137,7 +166,7 @@ t_password_undefined({'end', _Config}) -> [authentication], {delete_authenticator, 'mqtt:global', <<"password_based:built_in_database">>} ), - emqx_common_test_helpers:stop_apps([emqx_authn, emqx_conf]), + emqx_common_test_helpers:stop_apps([emqx_auth, emqx_conf]), ok; t_password_undefined(Config) when is_list(Config) -> Payload = <<16, 19, 0, 4, 77, 81, 84, 84, 4, 130, 0, 60, 0, 2, 97, 49, 0, 3, 97, 97, 97>>, @@ -168,45 +197,18 @@ t_password_undefined(Config) when is_list(Config) -> end, ok. -t_union_selector_errors({init, Config}) -> - Config; -t_union_selector_errors({'end', _Config}) -> - ok; -t_union_selector_errors(Config) when is_list(Config) -> - Conf0 = #{ - <<"mechanism">> => <<"password_based">>, - <<"backend">> => <<"mysql">> - }, - Conf1 = Conf0#{<<"mechanism">> => <<"unknown-atom-xx">>}, - ?assertThrow(#{error := unknown_mechanism}, emqx_authn:check_config(Conf1)), - Conf2 = Conf0#{<<"backend">> => <<"unknown-atom-xx">>}, - ?assertThrow(#{error := unknown_backend}, emqx_authn:check_config(Conf2)), - Conf3 = Conf0#{<<"backend">> => <<"unknown">>, <<"mechanism">> => <<"unknown">>}, - ?assertThrow( - #{ - error := unknown_authn_provider, - backend := unknown, - mechanism := unknown - }, - emqx_authn:check_config(Conf3) - ), - Res = catch (emqx_authn:check_config(#{<<"mechanism">> => <<"unknown">>})), - ?assertEqual( - #{ - error => unknown_authn_provider, - mechanism => unknown - }, - Res - ), - ok. - t_update_conf({init, Config}) -> - emqx_common_test_helpers:start_apps([emqx_conf, emqx_authn]), + emqx_common_test_helpers:start_apps([emqx_conf, emqx_auth]), + emqx_authn_test_lib:register_fake_providers([ + {password_based, built_in_database}, + {password_based, http}, + jwt + ]), {ok, _} = emqx:update_config([authentication], []), Config; t_update_conf({'end', _Config}) -> {ok, _} = emqx:update_config([authentication], []), - emqx_common_test_helpers:stop_apps([emqx_authn, emqx_conf]), + emqx_common_test_helpers:stop_apps([emqx_auth, emqx_conf]), ok; t_update_conf(Config) when is_list(Config) -> Authn1 = #{ @@ -242,11 +244,11 @@ t_update_conf(Config) when is_list(Config) -> #{ enable := true, id := <<"password_based:built_in_database">>, - provider := emqx_authn_mnesia + provider := emqx_authn_fake_provider } ] }}, - emqx_authentication:lookup_chain(Chain) + emqx_authn_chains:lookup_chain(Chain) ), {ok, _} = emqx:update_config([authentication], [Authn1, Authn2, Authn3]), @@ -256,21 +258,21 @@ t_update_conf(Config) when is_list(Config) -> #{ enable := true, id := <<"password_based:built_in_database">>, - provider := emqx_authn_mnesia + provider := emqx_authn_fake_provider }, #{ enable := true, id := <<"password_based:http">>, - provider := emqx_authn_http + provider := emqx_authn_fake_provider }, #{ enable := true, id := <<"jwt">>, - provider := emqx_authn_jwt + provider := emqx_authn_fake_provider } ] }}, - emqx_authentication:lookup_chain(Chain) + emqx_authn_chains:lookup_chain(Chain) ), {ok, _} = emqx:update_config([authentication], [Authn2, Authn1]), ?assertMatch( @@ -279,16 +281,16 @@ t_update_conf(Config) when is_list(Config) -> #{ enable := true, id := <<"password_based:http">>, - provider := emqx_authn_http + provider := emqx_authn_fake_provider }, #{ enable := true, id := <<"password_based:built_in_database">>, - provider := emqx_authn_mnesia + provider := emqx_authn_fake_provider } ] }}, - emqx_authentication:lookup_chain(Chain) + emqx_authn_chains:lookup_chain(Chain) ), {ok, _} = emqx:update_config([authentication], [Authn3, Authn2, Authn1]), @@ -298,26 +300,26 @@ t_update_conf(Config) when is_list(Config) -> #{ enable := true, id := <<"jwt">>, - provider := emqx_authn_jwt + provider := emqx_authn_fake_provider }, #{ enable := true, id := <<"password_based:http">>, - provider := emqx_authn_http + provider := emqx_authn_fake_provider }, #{ enable := true, id := <<"password_based:built_in_database">>, - provider := emqx_authn_mnesia + provider := emqx_authn_fake_provider } ] }}, - emqx_authentication:lookup_chain(Chain) + emqx_authn_chains:lookup_chain(Chain) ), {ok, _} = emqx:update_config([authentication], []), ?assertMatch( {error, {not_found, {chain, Chain}}}, - emqx_authentication:lookup_chain(Chain) + emqx_authn_chains:lookup_chain(Chain) ), ok. diff --git a/apps/emqx_authn/test/emqx_authn_api_SUITE.erl b/apps/emqx_auth/test/emqx_authn/emqx_authn_api_SUITE.erl similarity index 64% rename from apps/emqx_authn/test/emqx_authn_api_SUITE.erl rename to apps/emqx_auth/test/emqx_authn/emqx_authn_api_SUITE.erl index 4056f7f84..635b157d9 100644 --- a/apps/emqx_authn/test/emqx_authn_api_SUITE.erl +++ b/apps/emqx_auth/test/emqx_authn/emqx_authn_api_SUITE.erl @@ -18,7 +18,6 @@ -compile(nowarn_export_all). -compile(export_all). --import(emqx_dashboard_api_test_helpers, [multipart_formdata_request/3]). -import(emqx_mgmt_api_test_util, [request/3, uri/1]). -include("emqx_authn.hrl"). @@ -53,8 +52,6 @@ init_per_testcase(_Case, Config) -> [listeners, tcp, default, ?CONF_NS_ATOM], ?TCP_DEFAULT ), - - {atomic, ok} = mria:clear_table(emqx_authn_mnesia), Config. end_per_testcase(t_authenticator_fail, Config) -> @@ -68,7 +65,7 @@ init_per_suite(Config) -> [ emqx, emqx_conf, - emqx_authn, + emqx_auth, emqx_management, {emqx_dashboard, "dashboard.listeners.http { enable = true, bind = 18083 }"} ], @@ -77,6 +74,12 @@ init_per_suite(Config) -> } ), _ = emqx_common_test_http:create_default_app(), + ok = emqx_authn_test_lib:register_fake_providers([ + {password_based, built_in_database}, + {password_based, redis}, + {password_based, http}, + jwt + ]), ?AUTHN:delete_chain(?GLOBAL), {ok, Chains} = ?AUTHN:list_chains(), ?assertEqual(length(Chains), 0), @@ -116,36 +119,18 @@ t_authenticator_fail(_) -> ) ). -t_authenticator_users(_) -> - test_authenticator_users([]). - -t_authenticator_user(_) -> - test_authenticator_user([]). - t_authenticator_position(_) -> test_authenticator_position([]). -t_authenticator_import_users(_) -> - test_authenticator_import_users([]). - %t_listener_authenticators(_) -> % test_authenticators(["listeners", ?TCP_DEFAULT]). %t_listener_authenticator(_) -> % test_authenticator(["listeners", ?TCP_DEFAULT]). -%t_listener_authenticator_users(_) -> -% test_authenticator_users(["listeners", ?TCP_DEFAULT]). - -%t_listener_authenticator_user(_) -> -% test_authenticator_user(["listeners", ?TCP_DEFAULT]). - %t_listener_authenticator_position(_) -> % test_authenticator_position(["listeners", ?TCP_DEFAULT]). -%t_listener_authenticator_import_users(_) -> -% test_authenticator_import_users(["listeners", ?TCP_DEFAULT]). - t_aggregate_metrics(_) -> Metrics = #{ 'emqx@node1.emqx.io' => #{ @@ -329,218 +314,6 @@ test_authenticator(PathPrefix) -> ?assertAuthenticatorsMatch([], PathPrefix ++ [?CONF_NS]). -test_authenticator_users(PathPrefix) -> - UsersUri = uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database", "users"]), - - {ok, 200, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS]), - emqx_authn_test_lib:built_in_database_example() - ), - - {ok, Client} = emqtt:start_link( - [ - {username, <<"u_event">>}, - {clientid, <<"c_event">>}, - {proto_ver, v5}, - {properties, #{'Session-Expiry-Interval' => 60}} - ] - ), - - process_flag(trap_exit, true), - ?assertMatch({error, _}, emqtt:connect(Client)), - timer:sleep(300), - - UsersUri0 = uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database", "status"]), - {ok, 200, PageData0} = request(get, UsersUri0), - case PathPrefix of - [] -> - #{ - <<"metrics">> := #{ - <<"total">> := 1, - <<"success">> := 0, - <<"failed">> := 1 - } - } = emqx_utils_json:decode(PageData0, [return_maps]); - ["listeners", 'tcp:default'] -> - #{ - <<"metrics">> := #{ - <<"total">> := 1, - <<"success">> := 0, - <<"nomatch">> := 1 - } - } = emqx_utils_json:decode(PageData0, [return_maps]) - end, - - InvalidUsers = [ - #{clientid => <<"u1">>, password => <<"p1">>}, - #{user_id => <<"u2">>}, - #{user_id => <<"u3">>, password => <<"p3">>, foobar => <<"foobar">>} - ], - - lists:foreach( - fun(User) -> {ok, 400, _} = request(post, UsersUri, User) end, - InvalidUsers - ), - - ValidUsers = [ - #{user_id => <<"u1">>, password => <<"p1">>}, - #{user_id => <<"u2">>, password => <<"p2">>, is_superuser => true}, - #{user_id => <<"u3">>, password => <<"p3">>} - ], - - lists:foreach( - fun(User) -> - {ok, 201, UserData} = request(post, UsersUri, User), - CreatedUser = emqx_utils_json:decode(UserData, [return_maps]), - ?assertMatch(#{<<"user_id">> := _}, CreatedUser) - end, - ValidUsers - ), - - {ok, Client1} = emqtt:start_link( - [ - {username, <<"u1">>}, - {password, <<"p1">>}, - {clientid, <<"c_event">>}, - {proto_ver, v5}, - {properties, #{'Session-Expiry-Interval' => 60}} - ] - ), - {ok, _} = emqtt:connect(Client1), - timer:sleep(300), - UsersUri01 = uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database", "status"]), - {ok, 200, PageData01} = request(get, UsersUri01), - case PathPrefix of - [] -> - #{ - <<"metrics">> := #{ - <<"total">> := 2, - <<"success">> := 1, - <<"failed">> := 1 - } - } = emqx_utils_json:decode(PageData01, [return_maps]); - ["listeners", 'tcp:default'] -> - #{ - <<"metrics">> := #{ - <<"total">> := 2, - <<"success">> := 1, - <<"nomatch">> := 1 - } - } = emqx_utils_json:decode(PageData01, [return_maps]) - end, - - {ok, 200, Page1Data} = request(get, UsersUri ++ "?page=1&limit=2"), - - #{ - <<"data">> := Page1Users, - <<"meta">> := - #{ - <<"page">> := 1, - <<"limit">> := 2, - <<"count">> := 3 - } - } = - emqx_utils_json:decode(Page1Data, [return_maps]), - - {ok, 200, Page2Data} = request(get, UsersUri ++ "?page=2&limit=2"), - - #{ - <<"data">> := Page2Users, - <<"meta">> := - #{ - <<"page">> := 2, - <<"limit">> := 2, - <<"count">> := 3 - } - } = emqx_utils_json:decode(Page2Data, [return_maps]), - - ?assertEqual(2, length(Page1Users)), - ?assertEqual(1, length(Page2Users)), - - ?assertEqual( - [<<"u1">>, <<"u2">>, <<"u3">>], - lists:usort([UserId || #{<<"user_id">> := UserId} <- Page1Users ++ Page2Users]) - ), - - {ok, 200, Super1Data} = request(get, UsersUri ++ "?page=1&limit=3&is_superuser=true"), - - #{ - <<"data">> := Super1Users, - <<"meta">> := - #{ - <<"page">> := 1, - <<"limit">> := 3, - <<"count">> := 1 - } - } = emqx_utils_json:decode(Super1Data, [return_maps]), - - ?assertEqual( - [<<"u2">>], - lists:usort([UserId || #{<<"user_id">> := UserId} <- Super1Users]) - ), - - {ok, 200, Super2Data} = request(get, UsersUri ++ "?page=1&limit=3&is_superuser=false"), - - #{ - <<"data">> := Super2Users, - <<"meta">> := - #{ - <<"page">> := 1, - <<"limit">> := 3, - <<"count">> := 2 - } - } = emqx_utils_json:decode(Super2Data, [return_maps]), - - ?assertEqual( - [<<"u1">>, <<"u3">>], - lists:usort([UserId || #{<<"user_id">> := UserId} <- Super2Users]) - ), - - ok. - -test_authenticator_user(PathPrefix) -> - UsersUri = uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database", "users"]), - - {ok, 200, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS]), - emqx_authn_test_lib:built_in_database_example() - ), - - User = #{user_id => <<"u1">>, password => <<"p1">>}, - {ok, 201, _} = request(post, UsersUri, User), - - {ok, 404, _} = request(get, UsersUri ++ "/u123"), - - {ok, 409, _} = request(post, UsersUri, User), - - {ok, 200, UserData} = request(get, UsersUri ++ "/u1"), - - FetchedUser = emqx_utils_json:decode(UserData, [return_maps]), - ?assertMatch(#{<<"user_id">> := <<"u1">>}, FetchedUser), - ?assertNotMatch(#{<<"password">> := _}, FetchedUser), - - ValidUserUpdates = [ - #{password => <<"p1">>}, - #{password => <<"p1">>, is_superuser => true} - ], - - lists:foreach( - fun(UserUpdate) -> {ok, 200, _} = request(put, UsersUri ++ "/u1", UserUpdate) end, - ValidUserUpdates - ), - - InvalidUserUpdates = [#{user_id => <<"u1">>, password => <<"p1">>}], - - lists:foreach( - fun(UserUpdate) -> {ok, 400, _} = request(put, UsersUri ++ "/u1", UserUpdate) end, - InvalidUserUpdates - ), - - {ok, 404, _} = request(delete, UsersUri ++ "/u123"), - {ok, 204, _} = request(delete, UsersUri ++ "/u1"). - test_authenticator_position(PathPrefix) -> AuthenticatorConfs = [ emqx_authn_test_lib:http_example(), @@ -660,37 +433,6 @@ test_authenticator_position(PathPrefix) -> PathPrefix ++ [?CONF_NS] ). -test_authenticator_import_users(PathPrefix) -> - ImportUri = uri( - PathPrefix ++ - [?CONF_NS, "password_based:built_in_database", "import_users"] - ), - - {ok, 200, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS]), - emqx_authn_test_lib:built_in_database_example() - ), - - {ok, 400, _} = multipart_formdata_request(ImportUri, [], []), - {ok, 400, _} = multipart_formdata_request(ImportUri, [], [ - {filenam, "user-credentials.json", <<>>} - ]), - - Dir = code:lib_dir(emqx_authn, test), - JSONFileName = filename:join([Dir, <<"data/user-credentials.json">>]), - CSVFileName = filename:join([Dir, <<"data/user-credentials.csv">>]), - - {ok, JSONData} = file:read_file(JSONFileName), - {ok, 204, _} = multipart_formdata_request(ImportUri, [], [ - {filename, "user-credentials.json", JSONData} - ]), - - {ok, CSVData} = file:read_file(CSVFileName), - {ok, 204, _} = multipart_formdata_request(ImportUri, [], [ - {filename, "user-credentials.csv", CSVData} - ]). - %% listener authn api is not supported since 5.1.0 %% Don't support listener switch to global chain. ignore_switch_to_global_chain(_) -> diff --git a/apps/emqx_authn/test/emqx_authentication_SUITE.erl b/apps/emqx_auth/test/emqx_authn/emqx_authn_chains_SUITE.erl similarity index 95% rename from apps/emqx_authn/test/emqx_authentication_SUITE.erl rename to apps/emqx_auth/test/emqx_authn/emqx_authn_chains_SUITE.erl index a15f22c41..747a1d15a 100644 --- a/apps/emqx_authn/test/emqx_authentication_SUITE.erl +++ b/apps/emqx_auth/test/emqx_authn/emqx_authn_chains_SUITE.erl @@ -14,10 +14,10 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_authentication_SUITE). +-module(emqx_authn_chains_SUITE). -behaviour(hocon_schema). --behaviour(emqx_authentication). +-behaviour(emqx_authn_provider). -compile(export_all). -compile(nowarn_export_all). @@ -26,9 +26,9 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). --include("emqx_authentication.hrl"). +-include("emqx_authn_chains.hrl"). --define(AUTHN, emqx_authentication). +-define(AUTHN, emqx_authn_chains). -define(config(KEY), (fun() -> {KEY, _V_} = lists:keyfind(KEY, 1, Config), @@ -98,7 +98,7 @@ init_per_suite(Config) -> [ emqx, emqx_conf, - emqx_authn + emqx_auth ], #{work_dir => ?config(priv_dir)} ), @@ -295,9 +295,9 @@ t_update_config({init, Config}) -> | Config ]; t_update_config(Config) when is_list(Config) -> - emqx_config_handler:add_handler([?CONF_ROOT], emqx_authentication_config), + emqx_config_handler:add_handler([?CONF_ROOT], emqx_authn_config), ok = emqx_config_handler:add_handler( - [listeners, '?', '?', ?CONF_ROOT], emqx_authentication_config + [listeners, '?', '?', ?CONF_ROOT], emqx_authn_config ), ok = register_provider(?config("auth1"), ?MODULE), ok = register_provider(?config("auth2"), ?MODULE), @@ -469,8 +469,8 @@ t_restart(Config) when is_list(Config) -> ?assertEqual({ok, [test_chain]}, ?AUTHN:list_chain_names()), - ok = supervisor:terminate_child(emqx_authentication_sup, ?AUTHN), - {ok, _} = supervisor:restart_child(emqx_authentication_sup, ?AUTHN), + ok = supervisor:terminate_child(emqx_authn_sup, ?AUTHN), + {ok, _} = supervisor:restart_child(emqx_authn_sup, ?AUTHN), ?assertEqual({ok, [test_chain]}, ?AUTHN:list_chain_names()); t_restart({'end', _Config}) -> @@ -493,7 +493,7 @@ t_combine_authn_and_callback(Config) when is_list(Config) -> password => <<"any">> }, - %% no emqx_authentication authenticators, anonymous is allowed + %% no emqx_authn_chains authenticators, anonymous is allowed ?assertAuthSuccessForUser(bad), AuthNType = ?config(authn_type), @@ -506,7 +506,7 @@ t_combine_authn_and_callback(Config) when is_list(Config) -> }, {ok, _} = ?AUTHN:create_authenticator(ListenerID, AuthenticatorConfig), - %% emqx_authentication alone + %% emqx_authn_chains alone ?assertAuthSuccessForUser(good), ?assertAuthFailureForUser(ignore), ?assertAuthFailureForUser(bad), @@ -520,12 +520,12 @@ t_combine_authn_and_callback(Config) when is_list(Config) -> ?assertAuthFailureForUser(bad), %% higher-priority hook can permit access with {ok,...}, - %% then emqx_authentication overrides the result + %% then emqx_authn_chains overrides the result ?assertAuthFailureForUser(hook_user_good), ?assertAuthFailureForUser(hook_user_bad), %% higher-priority hook can permit and return {stop,...}, - %% then emqx_authentication cannot override the result + %% then emqx_authn_chains cannot override the result ?assertAuthSuccessForUser(hook_user_finally_good), ?assertAuthFailureForUser(hook_user_finally_bad), @@ -539,13 +539,13 @@ t_combine_authn_and_callback(Config) when is_list(Config) -> ?assertAuthFailureForUser(bad), ?assertAuthFailureForUser(ignore), - %% lower-priority hook can overrride emqx_authentication result + %% lower-priority hook can overrride emqx_authn_chains result %% for ignored users ?assertAuthSuccessForUser(emqx_authn_ignore_for_hook_good), ?assertAuthFailureForUser(emqx_authn_ignore_for_hook_bad), %% lower-priority hook cannot overrride - %% successful/unsuccessful emqx_authentication result + %% successful/unsuccessful emqx_authn_chains result ?assertAuthFailureForUser(hook_user_finally_good), ?assertAuthFailureForUser(hook_user_finally_bad), ?assertAuthFailureForUser(hook_user_good), diff --git a/apps/emqx_authn/test/emqx_authn_enable_flag_SUITE.erl b/apps/emqx_auth/test/emqx_authn/emqx_authn_enable_flag_SUITE.erl similarity index 95% rename from apps/emqx_authn/test/emqx_authn_enable_flag_SUITE.erl rename to apps/emqx_auth/test/emqx_authn/emqx_authn_enable_flag_SUITE.erl index 63cdb3f5f..76c28ccd9 100644 --- a/apps/emqx_authn/test/emqx_authn_enable_flag_SUITE.erl +++ b/apps/emqx_auth/test/emqx_authn/emqx_authn_enable_flag_SUITE.erl @@ -30,9 +30,10 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_auth], #{ work_dir => ?config(priv_dir, Config) }), + ok = emqx_authn_test_lib:register_fake_providers([{password_based, built_in_database}]), [{apps, Apps} | Config]. end_per_suite(Config) -> diff --git a/apps/emqx_auth/test/emqx_authn/emqx_authn_fake_provider.erl b/apps/emqx_auth/test/emqx_authn/emqx_authn_fake_provider.erl new file mode 100644 index 000000000..ef9609b8c --- /dev/null +++ b/apps/emqx_auth/test/emqx_authn/emqx_authn_fake_provider.erl @@ -0,0 +1,72 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2021-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authn_fake_provider). + +-behaviour(emqx_authn_provider). + +-export([ + create/2, + update/2, + authenticate/2, + destroy/1, + + add_user/2 +]). + +%%------------------------------------------------------------------------------ +%% APIs +%%------------------------------------------------------------------------------ + +create(_AuthenticatorID, Config) -> + create(Config). + +create(#{} = Config) -> + UserTab = ets:new(?MODULE, [set, public]), + {ok, #{users => UserTab, config => Config}}. + +update(Config, _State) -> + create(Config). + +authenticate(Credentials, #{users := UserTab} = _State) -> + IsValid = lists:any( + fun(User) -> are_credentials_matching(Credentials, User) end, ets:tab2list(UserTab) + ), + case IsValid of + true -> + {ok, #{is_superuser => true}}; + false -> + {error, bad_username_or_password} + end. + +destroy(#{users := UserTab}) -> + true = ets:delete(UserTab), + ok. + +add_user(#{user_id := UserId, password := Password} = User, #{users := UserTab} = _State) -> + true = ets:insert(UserTab, {UserId, Password}), + {ok, User}. + +%%------------------------------------------------------------------------------ +%% Internal functions +%%------------------------------------------------------------------------------ + +are_credentials_matching(#{username := Username, password := Password}, {Username, Password}) -> + true; +are_credentials_matching(#{clientid := ClientId, password := Password}, {ClientId, Password}) -> + true; +are_credentials_matching(_Credentials, _User) -> + false. diff --git a/apps/emqx_auth/test/emqx_authn/emqx_authn_init_SUITE.erl b/apps/emqx_auth/test/emqx_authn/emqx_authn_init_SUITE.erl new file mode 100644 index 000000000..48542ebfc --- /dev/null +++ b/apps/emqx_auth/test/emqx_authn/emqx_authn_init_SUITE.erl @@ -0,0 +1,85 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authn_init_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). + +-define(CLIENTINFO, #{ + zone => default, + listener => {tcp, default}, + protocol => mqtt, + peerhost => {127, 0, 0, 1}, + clientid => <<"clientid">>, + username => <<"username">>, + password => <<"passwd">>, + is_superuser => false, + mountpoint => undefined +}). + +all() -> + emqx_common_test_helpers:all(?MODULE). + +init_per_testcase(_Case, Config) -> + Apps = emqx_cth_suite:start( + [ + emqx, + {emqx_conf, + "authentication = [{mechanism = password_based, backend = built_in_database}]"} + ], + #{ + work_dir => ?config(priv_dir, Config) + } + ), + [{apps, Apps} | Config]. + +end_per_testcase(_Case, Config) -> + _ = application:stop(emqx_auth), + ok = emqx_cth_suite:stop(?config(apps, Config)), + ok. + +t_initialize(_Config) -> + ?assertMatch( + {ok, _}, + emqx_access_control:authenticate(?CLIENTINFO) + ), + ok = application:start(emqx_auth), + ?assertMatch( + {error, not_authorized}, + emqx_access_control:authenticate(?CLIENTINFO) + ), + + ok = emqx_authn_test_lib:register_fake_providers([{password_based, built_in_database}]), + + ?assertMatch( + {error, not_authorized}, + emqx_access_control:authenticate(?CLIENTINFO) + ), + + _ = emqx_authn_chains:add_user( + 'mqtt:global', + <<"password_based:built_in_database">>, + #{user_id => <<"username">>, password => <<"passwd">>} + ), + + ?assertMatch( + {ok, _}, + emqx_access_control:authenticate(?CLIENTINFO) + ). diff --git a/apps/emqx_authn/test/emqx_authn_listeners_SUITE.erl b/apps/emqx_auth/test/emqx_authn/emqx_authn_listeners_SUITE.erl similarity index 90% rename from apps/emqx_authn/test/emqx_authn_listeners_SUITE.erl rename to apps/emqx_auth/test/emqx_authn/emqx_authn_listeners_SUITE.erl index 9708bf1bb..dbf5531de 100644 --- a/apps/emqx_authn/test/emqx_authn_listeners_SUITE.erl +++ b/apps/emqx_auth/test/emqx_authn/emqx_authn_listeners_SUITE.erl @@ -28,9 +28,13 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_auth], #{ work_dir => ?config(priv_dir, Config) }), + ok = emqx_authn_test_lib:register_fake_providers([ + {password_based, built_in_database}, + {password_based, redis} + ]), [{apps, Apps} | Config]. end_per_suite(Config) -> @@ -70,14 +74,14 @@ t_create_update_delete(Config) -> #{ id := <<"password_based:built_in_database">>, state := #{ - user_id_type := clientid + config := #{user_id_type := clientid} } } ], name := 'tcp:listener0' } ]}, - emqx_authentication:list_chains() + emqx_authn_chains:list_chains() ), %% Drop old, create new @@ -99,14 +103,14 @@ t_create_update_delete(Config) -> #{ id := <<"password_based:built_in_database">>, state := #{ - user_id_type := clientid + config := #{user_id_type := clientid} } } ], name := 'tcp:listener1' } ]}, - emqx_authentication:list_chains() + emqx_authn_chains:list_chains() ), %% Update @@ -128,14 +132,14 @@ t_create_update_delete(Config) -> #{ id := <<"password_based:built_in_database">>, state := #{ - user_id_type := username + config := #{user_id_type := username} } } ], name := 'tcp:listener1' } ]}, - emqx_authentication:list_chains() + emqx_authn_chains:list_chains() ), %% Update by listener path @@ -153,14 +157,14 @@ t_create_update_delete(Config) -> #{ id := <<"password_based:built_in_database">>, state := #{ - user_id_type := clientid + config := #{user_id_type := clientid} } } ], name := 'tcp:listener1' } ]}, - emqx_authentication:list_chains() + emqx_authn_chains:list_chains() ), %% Delete @@ -170,7 +174,7 @@ t_create_update_delete(Config) -> ), ?assertMatch( {ok, []}, - emqx_authentication:list_chains() + emqx_authn_chains:list_chains() ). t_convert_certs(Config) -> @@ -236,7 +240,7 @@ listener_mqtt_tcp_conf(Config) -> }. some_pem() -> - Dir = code:lib_dir(emqx_authn, test), + Dir = code:lib_dir(emqx_auth, test), Path = filename:join([Dir, "data", "private_key.pem"]), {ok, Pem} = file:read_file(Path), Pem. diff --git a/apps/emqx_authn/test/emqx_authn_password_hashing_SUITE.erl b/apps/emqx_auth/test/emqx_authn/emqx_authn_password_hashing_SUITE.erl similarity index 100% rename from apps/emqx_authn/test/emqx_authn_password_hashing_SUITE.erl rename to apps/emqx_auth/test/emqx_authn/emqx_authn_password_hashing_SUITE.erl diff --git a/apps/emqx_authn/test/emqx_authn_schema_SUITE.erl b/apps/emqx_auth/test/emqx_authn/emqx_authn_schema_SUITE.erl similarity index 93% rename from apps/emqx_authn/test/emqx_authn_schema_SUITE.erl rename to apps/emqx_auth/test/emqx_authn/emqx_authn_schema_SUITE.erl index 8266ade10..6d6ea420f 100644 --- a/apps/emqx_authn/test/emqx_authn_schema_SUITE.erl +++ b/apps/emqx_auth/test/emqx_authn/emqx_authn_schema_SUITE.erl @@ -12,7 +12,7 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_auth], #{ work_dir => ?config(priv_dir, Config) }), [{apps, Apps} | Config]. @@ -54,7 +54,7 @@ t_check_schema(_Config) -> ?assertThrow( #{ path := "authentication.1.password_hash_algorithm.name", - matched_type := "authn:builtin_db/authn-hash:simple", + matched_type := "builtin_db/authn-hash:simple", reason := unable_to_convert_to_enum_symbol }, Check(ConfigNotOk) @@ -73,7 +73,7 @@ t_check_schema(_Config) -> #{ path := "authentication.1.password_hash_algorithm", reason := "algorithm_name_missing", - matched_type := "authn:builtin_db" + matched_type := "builtin_db" }, Check(ConfigMissingAlgoName) ). @@ -107,7 +107,8 @@ t_union_member_selector(_) -> BadBackend = Base#{<<"mechanism">> => <<"password_based">>, <<"backend">> => <<"bar">>}, ?assertThrow( #{ - reason := "unknown_backend", + reason := "unsupported_mechanism", + mechanism := <<"password_based">>, backend := <<"bar">> }, check(BadBackend) @@ -124,9 +125,8 @@ t_union_member_selector(_) -> BadCombination = Base#{<<"mechanism">> => <<"scram">>, <<"backend">> => <<"http">>}, ?assertThrow( #{ - reason := "unsupported_mechanism", - mechanism := <<"scram">>, - backend := <<"http">> + reason := "unknown_mechanism", + expected := "password_based" }, check(BadCombination) ), diff --git a/apps/emqx_authn/test/emqx_authn_schema_tests.erl b/apps/emqx_auth/test/emqx_authn/emqx_authn_schema_tests.erl similarity index 100% rename from apps/emqx_authn/test/emqx_authn_schema_tests.erl rename to apps/emqx_auth/test/emqx_authn/emqx_authn_schema_tests.erl diff --git a/apps/emqx_authn/test/emqx_authn_test_lib.erl b/apps/emqx_auth/test/emqx_authn/emqx_authn_test_lib.erl similarity index 82% rename from apps/emqx_authn/test/emqx_authn_test_lib.erl rename to apps/emqx_auth/test/emqx_authn/emqx_authn_test_lib.erl index 1c0eeb359..5ecd38cec 100644 --- a/apps/emqx_authn/test/emqx_authn_test_lib.erl +++ b/apps/emqx_auth/test/emqx_authn/emqx_authn_test_lib.erl @@ -35,7 +35,7 @@ jwt_example() -> authenticator_example(jwt). delete_authenticators(Path, Chain) -> - case emqx_authentication:list_authenticators(Chain) of + case emqx_authn_chains:list_authenticators(Chain) of {error, _} -> ok; {ok, Authenticators} -> @@ -60,9 +60,20 @@ delete_config(ID) -> ). client_ssl_cert_opts() -> - Dir = code:lib_dir(emqx_authn, test), + Dir = code:lib_dir(emqx_auth, test), #{ <<"keyfile">> => filename:join([Dir, <<"data/certs">>, <<"client.key">>]), <<"certfile">> => filename:join([Dir, <<"data/certs">>, <<"client.crt">>]), <<"cacertfile">> => filename:join([Dir, <<"data/certs">>, <<"ca.crt">>]) }. + +register_fake_providers(ProviderTypes) -> + Providers = [ + {ProviderType, emqx_authn_fake_provider} + || ProviderType <- ProviderTypes + ], + emqx_authn_chains:register_providers(Providers). + +deregister_providers() -> + ProviderTypes = maps:keys(emqx_authn_chains:get_providers()), + emqx_authn_chains:deregister_providers(ProviderTypes). diff --git a/apps/emqx_authz/test/emqx_authz_SUITE.erl b/apps/emqx_auth/test/emqx_authz/emqx_authz_SUITE.erl similarity index 95% rename from apps/emqx_authz/test/emqx_authz_SUITE.erl rename to apps/emqx_auth/test/emqx_authz/emqx_authz_SUITE.erl index 12558813c..34ab5d87e 100644 --- a/apps/emqx_authz/test/emqx_authz_SUITE.erl +++ b/apps/emqx_auth/test/emqx_authz/emqx_authz_SUITE.erl @@ -39,20 +39,33 @@ init_per_suite(Config) -> meck:expect(emqx_resource, create_local, fun(_, _, _, _) -> {ok, meck_data} end), meck:expect(emqx_resource, remove_local, fun(_) -> ok end), meck:expect( - emqx_authz, + emqx_authz_file, acl_conf_file, fun() -> - emqx_common_test_helpers:deps_path(emqx_authz, "etc/acl.conf") + emqx_common_test_helpers:deps_path(emqx_auth_file, "etc/acl.conf") end ), - - ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1 + Apps = emqx_cth_suite:start( + [ + emqx, + {emqx_conf, + "authorization { cache { enable = false }, no_match = deny, sources = [] }"}, + emqx_auth, + emqx_auth_file, + emqx_auth_http, + emqx_auth_mnesia, + emqx_auth_redis, + emqx_auth_postgresql, + emqx_auth_mysql, + emqx_auth_mongodb + ], + #{ + work_dir => filename:join(?config(priv_dir, Config), ?MODULE) + } ), - Config. + [{suite_apps, Apps} | Config]. -end_per_suite(_Config) -> +end_per_suite(Config) -> {ok, _} = emqx:update_config( [authorization], #{ @@ -61,7 +74,7 @@ end_per_suite(_Config) -> <<"sources">> => [] } ), - emqx_common_test_helpers:stop_apps([emqx_authz, emqx_conf]), + emqx_cth_suite:stop(?config(suite_apps, Config)), meck:unload(emqx_resource), ok. @@ -73,7 +86,7 @@ init_per_testcase(TestCase, Config) when {ok, _} = emqx_authz:update(?CMD_REPLACE, []), {ok, _} = emqx:update_config([authorization, deny_action], disconnect), Config; -init_per_testcase(_, Config) -> +init_per_testcase(_TestCase, Config) -> {ok, _} = emqx_authz:update(?CMD_REPLACE, []), Config. @@ -90,14 +103,6 @@ end_per_testcase(_TestCase, _Config) -> emqx_common_test_helpers:call_janitor(), ok. -set_special_configs(emqx_authz) -> - {ok, _} = emqx:update_config([authorization, cache, enable], false), - {ok, _} = emqx:update_config([authorization, no_match], deny), - {ok, _} = emqx:update_config([authorization, sources], []), - ok; -set_special_configs(_App) -> - ok. - -define(SOURCE1, #{ <<"type">> => <<"http">>, <<"enable">> => true, @@ -205,7 +210,7 @@ t_bad_file_source(_) -> BadActionErr = {invalid_authorization_action, pubsub}, lists:foreach( fun({Source, Error}) -> - File = emqx_authz:acl_conf_file(), + File = emqx_authz_file:acl_conf_file(), {ok, Bin1} = file:read_file(File), ?assertEqual(?UPDATE_ERROR(Error), emqx_authz:update(?CMD_REPLACE, [Source])), ?assertEqual(?UPDATE_ERROR(Error), emqx_authz:update(?CMD_PREPEND, Source)), diff --git a/apps/emqx_authz/test/emqx_authz_api_cache_SUITE.erl b/apps/emqx_auth/test/emqx_authz/emqx_authz_api_cache_SUITE.erl similarity index 95% rename from apps/emqx_authz/test/emqx_authz_api_cache_SUITE.erl rename to apps/emqx_auth/test/emqx_authz/emqx_authz_api_cache_SUITE.erl index ab673b225..d81458c76 100644 --- a/apps/emqx_authz/test/emqx_authz_api_cache_SUITE.erl +++ b/apps/emqx_auth/test/emqx_authz/emqx_authz_api_cache_SUITE.erl @@ -33,7 +33,7 @@ groups() -> init_per_suite(Config) -> ok = emqx_mgmt_api_test_util:init_suite( - [emqx_conf, emqx_authz], + [emqx_conf, emqx_auth], fun set_special_configs/1 ), Config. @@ -47,12 +47,12 @@ end_per_suite(_Config) -> <<"sources">> => [] } ), - emqx_mgmt_api_test_util:end_suite([emqx_authz, emqx_conf]), + emqx_mgmt_api_test_util:end_suite([emqx_auth, emqx_conf]), ok. set_special_configs(emqx_dashboard) -> emqx_dashboard_api_test_helpers:set_default_config(); -set_special_configs(emqx_authz) -> +set_special_configs(emqx_auth) -> {ok, _} = emqx:update_config([authorization, cache, enable], true), {ok, _} = emqx:update_config([authorization, no_match], deny), {ok, _} = emqx:update_config([authorization, sources], []), diff --git a/apps/emqx_authz/test/emqx_authz_api_settings_SUITE.erl b/apps/emqx_auth/test/emqx_authz/emqx_authz_api_settings_SUITE.erl similarity index 95% rename from apps/emqx_authz/test/emqx_authz_api_settings_SUITE.erl rename to apps/emqx_auth/test/emqx_authz/emqx_authz_api_settings_SUITE.erl index c29fe0f5b..ccbe0298b 100644 --- a/apps/emqx_authz/test/emqx_authz_api_settings_SUITE.erl +++ b/apps/emqx_auth/test/emqx_authz/emqx_authz_api_settings_SUITE.erl @@ -31,7 +31,7 @@ groups() -> init_per_suite(Config) -> ok = emqx_mgmt_api_test_util:init_suite( - [emqx_conf, emqx_authz, emqx_dashboard], + [emqx_conf, emqx_auth, emqx_dashboard], fun set_special_configs/1 ), Config. @@ -46,12 +46,12 @@ end_per_suite(_Config) -> } ), ok = stop_apps([emqx_resource]), - emqx_mgmt_api_test_util:end_suite([emqx_authz, emqx_conf]), + emqx_mgmt_api_test_util:end_suite([emqx_auth, emqx_conf]), ok. set_special_configs(emqx_dashboard) -> emqx_dashboard_api_test_helpers:set_default_config(); -set_special_configs(emqx_authz) -> +set_special_configs(emqx_auth) -> {ok, _} = emqx:update_config([authorization, cache, enable], false), {ok, _} = emqx:update_config([authorization, no_match], deny), {ok, _} = emqx:update_config([authorization, sources], []), diff --git a/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl b/apps/emqx_auth/test/emqx_authz/emqx_authz_api_sources_SUITE.erl similarity index 94% rename from apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl rename to apps/emqx_auth/test/emqx_authz/emqx_authz_api_sources_SUITE.erl index 7a7dbb7e9..2c8b5a863 100644 --- a/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl +++ b/apps/emqx_auth/test/emqx_authz/emqx_authz_api_sources_SUITE.erl @@ -21,6 +21,7 @@ -import(emqx_mgmt_api_test_util, [request/3, uri/1]). -include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl"). -define(MONGO_SINGLE_HOST, "mongo"). @@ -101,27 +102,42 @@ groups() -> []. init_per_suite(Config) -> - ok = stop_apps([emqx_resource]), meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]), meck:expect(emqx_resource, create_local, fun(_, _, _, _) -> {ok, meck_data} end), meck:expect(emqx_resource, health_check, fun(St) -> {ok, St} end), meck:expect(emqx_resource, remove_local, fun(_) -> ok end), meck:expect( - emqx_authz, + emqx_authz_file, acl_conf_file, fun() -> - emqx_common_test_helpers:deps_path(emqx_authz, "etc/acl.conf") + emqx_common_test_helpers:deps_path(emqx_auth_file, "etc/acl.conf") end ), - ok = emqx_mgmt_api_test_util:init_suite( - [emqx_conf, emqx_authz], - fun set_special_configs/1 + Apps = emqx_cth_suite:start( + [ + emqx, + {emqx_conf, + "authorization { cache { enable = false }, no_match = deny, sources = [] }"}, + emqx_auth, + emqx_auth_file, + emqx_auth_http, + emqx_auth_mnesia, + emqx_auth_redis, + emqx_auth_postgresql, + emqx_auth_mysql, + emqx_auth_mongodb, + emqx_management, + {emqx_dashboard, "dashboard.listeners.http { enable = true, bind = 18083 }"} + ], + #{ + work_dir => filename:join(?config(priv_dir, Config), ?MODULE) + } ), - ok = start_apps([emqx_resource]), - Config. + _ = emqx_common_test_http:create_default_app(), + [{suite_apps, Apps} | Config]. -end_per_suite(_Config) -> +end_per_suite(Config) -> {ok, _} = emqx:update_config( [authorization], #{ @@ -130,23 +146,11 @@ end_per_suite(_Config) -> <<"sources">> => [] } ), - %% resource and connector should be stop first, - %% or authz_[mysql|pgsql|redis..]_SUITE would be failed - ok = stop_apps([emqx_resource]), - emqx_mgmt_api_test_util:end_suite([emqx_authz, emqx_conf]), + _ = emqx_common_test_http:delete_default_app(), + emqx_cth_suite:stop(?config(suite_apps, Config)), meck:unload(emqx_resource), ok. -set_special_configs(emqx_dashboard) -> - emqx_dashboard_api_test_helpers:set_default_config(); -set_special_configs(emqx_authz) -> - {ok, _} = emqx:update_config([authorization, cache, enable], false), - {ok, _} = emqx:update_config([authorization, no_match], deny), - {ok, _} = emqx:update_config([authorization, sources], []), - ok; -set_special_configs(_App) -> - ok. - init_per_testcase(t_api, Config) -> meck:new(emqx_utils, [non_strict, passthrough, no_history, no_link]), meck:expect(emqx_utils, gen_id, fun() -> "fake" end), @@ -206,7 +210,7 @@ t_api(_) -> ], Sources ), - ?assert(filelib:is_file(emqx_authz:acl_conf_file())), + ?assert(filelib:is_file(emqx_authz_file:acl_conf_file())), {ok, 204, _} = request( put, diff --git a/apps/emqx_authz/test/emqx_authz_rich_actions_SUITE.erl b/apps/emqx_auth/test/emqx_authz/emqx_authz_rich_actions_SUITE.erl similarity index 96% rename from apps/emqx_authz/test/emqx_authz_rich_actions_SUITE.erl rename to apps/emqx_auth/test/emqx_authz/emqx_authz_rich_actions_SUITE.erl index fc597f15b..1cd448e34 100644 --- a/apps/emqx_authz/test/emqx_authz_rich_actions_SUITE.erl +++ b/apps/emqx_auth/test/emqx_authz/emqx_authz_rich_actions_SUITE.erl @@ -34,10 +34,12 @@ groups() -> init_per_testcase(TestCase, Config) -> Apps = emqx_cth_suite:start( [ + emqx, {emqx_conf, "authorization.no_match = deny, authorization.cache.enable = false"}, - emqx_authz + emqx_auth, + emqx_auth_file ], - #{work_dir => emqx_cth_suite:work_dir(TestCase, Config)} + #{work_dir => filename:join(?config(priv_dir, Config), TestCase)} ), [{tc_apps, Apps} | Config]. diff --git a/apps/emqx_authz/test/emqx_authz_rule_SUITE.erl b/apps/emqx_auth/test/emqx_authz/emqx_authz_rule_SUITE.erl similarity index 99% rename from apps/emqx_authz/test/emqx_authz_rule_SUITE.erl rename to apps/emqx_auth/test/emqx_authz/emqx_authz_rule_SUITE.erl index c73fe96ea..b34e4fb00 100644 --- a/apps/emqx_authz/test/emqx_authz_rule_SUITE.erl +++ b/apps/emqx_auth/test/emqx_authz/emqx_authz_rule_SUITE.erl @@ -35,7 +35,7 @@ all() -> init_per_suite(Config) -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], + [emqx_conf, emqx_auth], fun set_special_configs/1 ), Config. @@ -49,7 +49,7 @@ end_per_suite(_Config) -> <<"sources">> => [] } ), - emqx_common_test_helpers:stop_apps([emqx_authz, emqx_conf]), + emqx_common_test_helpers:stop_apps([emqx_auth, emqx_conf]), ok. init_per_testcase(_TestCase, Config) -> @@ -58,7 +58,7 @@ end_per_testcase(_TestCase, _Config) -> _ = emqx_authz:set_feature_available(rich_actions, true), ok. -set_special_configs(emqx_authz) -> +set_special_configs(emqx_auth) -> {ok, _} = emqx:update_config([authorization, cache, enable], false), {ok, _} = emqx:update_config([authorization, no_match], deny), {ok, _} = emqx:update_config([authorization, sources], []), diff --git a/apps/emqx_authz/test/emqx_authz_rule_raw_SUITE.erl b/apps/emqx_auth/test/emqx_authz/emqx_authz_rule_raw_SUITE.erl similarity index 100% rename from apps/emqx_authz/test/emqx_authz_rule_raw_SUITE.erl rename to apps/emqx_auth/test/emqx_authz/emqx_authz_rule_raw_SUITE.erl diff --git a/apps/emqx_authz/test/emqx_authz_schema_tests.erl b/apps/emqx_auth/test/emqx_authz/emqx_authz_schema_tests.erl similarity index 100% rename from apps/emqx_authz/test/emqx_authz_schema_tests.erl rename to apps/emqx_auth/test/emqx_authz/emqx_authz_schema_tests.erl diff --git a/apps/emqx_authz/test/emqx_authz_test_lib.erl b/apps/emqx_auth/test/emqx_authz/emqx_authz_test_lib.erl similarity index 100% rename from apps/emqx_authz/test/emqx_authz_test_lib.erl rename to apps/emqx_auth/test/emqx_authz/emqx_authz_test_lib.erl diff --git a/apps/emqx_authz/etc/acl.conf b/apps/emqx_auth_file/etc/acl.conf similarity index 100% rename from apps/emqx_authz/etc/acl.conf rename to apps/emqx_auth_file/etc/acl.conf diff --git a/apps/emqx_auth_file/include/emqx_auth_file.hrl b/apps/emqx_auth_file/include/emqx_auth_file.hrl new file mode 100644 index 000000000..64fd1b71b --- /dev/null +++ b/apps/emqx_auth_file/include/emqx_auth_file.hrl @@ -0,0 +1,23 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-ifndef(EMQX_AUTH_FILE_HRL). +-define(EMQX_AUTH_FILE_HRL, true). + +-define(AUTHZ_TYPE, file). +-define(AUTHZ_TYPE_BIN, <<"file">>). + +-endif. diff --git a/apps/emqx_auth_file/rebar.config b/apps/emqx_auth_file/rebar.config new file mode 100644 index 000000000..8da485335 --- /dev/null +++ b/apps/emqx_auth_file/rebar.config @@ -0,0 +1,7 @@ +%% -*- mode: erlang -*- +{deps, [ + {emqx, {path, "../emqx"}}, + {emqx_utils, {path, "../emqx_utils"}}, + {emqx_auth, {path, "../emqx_auth"}} + +]}. diff --git a/apps/emqx_auth_file/src/emqx_auth_file.app.src b/apps/emqx_auth_file/src/emqx_auth_file.app.src new file mode 100644 index 000000000..f9118b68a --- /dev/null +++ b/apps/emqx_auth_file/src/emqx_auth_file.app.src @@ -0,0 +1,18 @@ +%% -*- mode: erlang -*- +{application, emqx_auth_file, [ + {description, "EMQX File-based Authentication and Authorization"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, {emqx_auth_file_app, []}}, + {applications, [ + kernel, + stdlib, + emqx, + emqx_auth + ]}, + {env, []}, + {modules, []}, + + {licenses, ["Apache 2.0"]}, + {links, []} +]}. diff --git a/apps/emqx_auth_file/src/emqx_auth_file_app.erl b/apps/emqx_auth_file/src/emqx_auth_file_app.erl new file mode 100644 index 000000000..b69b55148 --- /dev/null +++ b/apps/emqx_auth_file/src/emqx_auth_file_app.erl @@ -0,0 +1,32 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_file_app). + +-include("emqx_auth_file.hrl"). + +-behaviour(application). + +-export([start/2, stop/1]). + +start(_StartType, _StartArgs) -> + ok = emqx_authz:register_source(?AUTHZ_TYPE, emqx_authz_file), + {ok, Sup} = emqx_auth_file_sup:start_link(), + {ok, Sup}. + +stop(_State) -> + ok = emqx_authz:unregister_source(?AUTHZ_TYPE), + ok. diff --git a/apps/emqx_auth_file/src/emqx_auth_file_sup.erl b/apps/emqx_auth_file/src/emqx_auth_file_sup.erl new file mode 100644 index 000000000..ead18c6fb --- /dev/null +++ b/apps/emqx_auth_file/src/emqx_auth_file_sup.erl @@ -0,0 +1,37 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_file_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(SERVER, ?MODULE). + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +init([]) -> + SupFlags = #{ + strategy => one_for_all, + intensity => 0, + period => 1 + }, + ChildSpecs = [], + {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/emqx_authz/src/emqx_authz_file.erl b/apps/emqx_auth_file/src/emqx_authz_file.erl similarity index 54% rename from apps/emqx_authz/src/emqx_authz_file.erl rename to apps/emqx_auth_file/src/emqx_authz_file.erl index 72bcb60aa..441fc682b 100644 --- a/apps/emqx_authz/src/emqx_authz_file.erl +++ b/apps/emqx_auth_file/src/emqx_authz_file.erl @@ -18,7 +18,7 @@ -include_lib("emqx/include/logger.hrl"). --behaviour(emqx_authz). +-behaviour(emqx_authz_source). -ifdef(TEST). -compile(export_all). @@ -31,14 +31,56 @@ create/1, update/1, destroy/1, - authorize/4, - validate/1, - read_file/1 + authorize/4 ]). +-export([ + validate/1, + write_files/1, + read_files/1 +]). + +%% For testing +-export([ + acl_conf_file/0 +]). + +%%------------------------------------------------------------------------------ +%% Authz Source Callbacks +%%------------------------------------------------------------------------------ + description() -> "AuthZ with static rules". +create(#{path := Path} = Source) -> + {ok, Rules} = validate(Path), + Source#{annotations => #{rules => Rules}}. + +update(#{path := _Path} = Source) -> + create(Source). + +destroy(_Source) -> ok. + +authorize(Client, PubSub, Topic, #{annotations := #{rules := Rules}}) -> + emqx_authz_rule:matches(Client, PubSub, Topic, Rules). + +read_files(#{<<"path">> := Path} = Source) -> + {ok, Rules} = read_file(Path), + maps:remove(<<"path">>, Source#{<<"rules">> => Rules}). + +write_files(#{<<"rules">> := Rules} = Source0) -> + AclPath = ?MODULE:acl_conf_file(), + %% Always check if the rules are valid before writing to the file + %% If the rules are invalid, the old file will be kept + ok = check_acl_file_rules(AclPath, Rules), + ok = write_file(AclPath, Rules), + Source1 = maps:remove(<<"rules">>, Source0), + maps:put(<<"path">>, AclPath, Source1). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + validate(Path0) -> Path = filename(Path0), Rules = @@ -53,25 +95,39 @@ validate(Path0) -> }), throw(failed_to_read_acl_file); {error, Reason} -> - ?SLOG(alert, #{msg => "bad_acl_file_content", path => Path, reason => Reason}), + ?SLOG(alert, #{msg => bad_acl_file_content, path => Path, reason => Reason}), throw({bad_acl_file_content, Reason}) end, {ok, Rules}. -create(#{path := Path} = Source) -> - {ok, Rules} = validate(Path), - Source#{annotations => #{rules => Rules}}. - -update(#{path := _Path} = Source) -> - create(Source). - -destroy(_Source) -> ok. - -authorize(Client, PubSub, Topic, #{annotations := #{rules := Rules}}) -> - emqx_authz_rule:matches(Client, PubSub, Topic, Rules). - read_file(Path) -> file:read_file(filename(Path)). +write_file(Filename, Bytes) -> + ok = filelib:ensure_dir(Filename), + case file:write_file(Filename, Bytes) of + ok -> + ok; + {error, Reason} -> + ?SLOG(error, #{filename => Filename, msg => "write_file_error", reason => Reason}), + throw(Reason) + end. + filename(PathMaybeTemplate) -> emqx_schema:naive_env_interpolation(PathMaybeTemplate). + +%% @doc where the acl.conf file is stored. +acl_conf_file() -> + filename:join([emqx:data_dir(), "authz", "acl.conf"]). + +check_acl_file_rules(Path, Rules) -> + TmpPath = Path ++ ".tmp", + try + ok = write_file(TmpPath, Rules), + {ok, _} = validate(TmpPath), + ok + catch + throw:Reason -> throw(Reason) + after + _ = file:delete(TmpPath) + end. diff --git a/apps/emqx_auth_file/src/emqx_authz_file_schema.erl b/apps/emqx_auth_file/src/emqx_authz_file_schema.erl new file mode 100644 index 000000000..cea697d66 --- /dev/null +++ b/apps/emqx_auth_file/src/emqx_authz_file_schema.erl @@ -0,0 +1,80 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authz_file_schema). + +-include("emqx_auth_file.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authz_schema). + +-export([ + type/0, + fields/1, + desc/1, + source_refs/0, + api_source_refs/0, + select_union_member/1 +]). + +type() -> ?AUTHZ_TYPE. + +fields(file) -> + emqx_authz_schema:authz_common_fields(?AUTHZ_TYPE) ++ + [ + {path, + ?HOCON( + string(), + #{ + required => true, + validator => fun(Path) -> element(1, emqx_authz_file:validate(Path)) end, + desc => ?DESC(path) + } + )} + ]; +fields(api_file) -> + emqx_authz_schema:authz_common_fields(?AUTHZ_TYPE) ++ + [ + {rules, + ?HOCON( + binary(), + #{ + required => true, + example => + << + "{allow,{username,{re,\"^dashboard$\"}},subscribe,[\"$SYS/#\"]}.\n", + "{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}." + >>, + desc => ?DESC(rules) + } + )} + ]. + +desc(file) -> + ?DESC(file); +desc(api_file) -> + ?DESC(file). + +source_refs() -> + [?R_REF(file)]. + +api_source_refs() -> + [?R_REF(api_file)]. + +select_union_member(#{<<"type">> := ?AUTHZ_TYPE_BIN}) -> + ?R_REF(file); +select_union_member(_Value) -> + undefined. diff --git a/apps/emqx_authz/test/emqx_authz_file_SUITE.erl b/apps/emqx_auth_file/test/emqx_authz_file_SUITE.erl similarity index 96% rename from apps/emqx_authz/test/emqx_authz_file_SUITE.erl rename to apps/emqx_auth_file/test/emqx_authz_file_SUITE.erl index d31935363..834879e14 100644 --- a/apps/emqx_authz/test/emqx_authz_file_SUITE.erl +++ b/apps/emqx_auth_file/test/emqx_authz_file_SUITE.erl @@ -18,7 +18,7 @@ -compile(nowarn_export_all). -compile(export_all). --include("emqx_authz.hrl"). +-include_lib("emqx_auth/include/emqx_authz.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). @@ -42,9 +42,11 @@ init_per_testcase(TestCase, Config) -> Apps = emqx_cth_suite:start( [ {emqx_conf, "authorization.no_match = deny, authorization.cache.enable = false"}, - emqx_authz + emqx, + emqx_auth, + emqx_auth_file ], - #{work_dir => emqx_cth_suite:work_dir(TestCase, Config)} + #{work_dir => filename:join(?config(priv_dir, Config), TestCase)} ), [{tc_apps, Apps} | Config]. diff --git a/apps/emqx_auth_http/include/emqx_auth_http.hrl b/apps/emqx_auth_http/include/emqx_auth_http.hrl new file mode 100644 index 000000000..a9561c3cd --- /dev/null +++ b/apps/emqx_auth_http/include/emqx_auth_http.hrl @@ -0,0 +1,29 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-ifndef(EMQX_AUTH_HTTP_HRL). +-define(EMQX_AUTH_HTTP_HRL, true). + +-define(AUTHZ_TYPE, http). +-define(AUTHZ_TYPE_BIN, <<"http">>). + +-define(AUTHN_MECHANISM, password_based). +-define(AUTHN_MECHANISM_BIN, <<"password_based">>). +-define(AUTHN_BACKEND, http). +-define(AUTHN_BACKEND_BIN, <<"http">>). +-define(AUTHN_TYPE, {?AUTHN_MECHANISM, ?AUTHN_BACKEND}). + +-endif. diff --git a/apps/emqx_auth_http/rebar.config b/apps/emqx_auth_http/rebar.config new file mode 100644 index 000000000..db2424602 --- /dev/null +++ b/apps/emqx_auth_http/rebar.config @@ -0,0 +1,6 @@ +%% -*- mode: erlang -*- +{deps, [ + {emqx, {path, "../emqx"}}, + {emqx_utils, {path, "../emqx_utils"}}, + {emqx_auth, {path, "../emqx_auth"}} +]}. diff --git a/apps/emqx_auth_http/src/emqx_auth_http.app.src b/apps/emqx_auth_http/src/emqx_auth_http.app.src new file mode 100644 index 000000000..b5de90ad9 --- /dev/null +++ b/apps/emqx_auth_http/src/emqx_auth_http.app.src @@ -0,0 +1,19 @@ +%% -*- mode: erlang -*- +{application, emqx_auth_http, [ + {description, "EMQX External HTTP API Authentication and Authorization"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, {emqx_auth_http_app, []}}, + {applications, [ + kernel, + stdlib, + emqx_auth, + emqx_connector, + emqx_resource + ]}, + {env, []}, + {modules, []}, + + {licenses, ["Apache 2.0"]}, + {links, []} +]}. diff --git a/apps/emqx_auth_http/src/emqx_auth_http_app.erl b/apps/emqx_auth_http/src/emqx_auth_http_app.erl new file mode 100644 index 000000000..31d77385a --- /dev/null +++ b/apps/emqx_auth_http/src/emqx_auth_http_app.erl @@ -0,0 +1,34 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_http_app). + +-include("emqx_auth_http.hrl"). + +-behaviour(application). + +-export([start/2, stop/1]). + +start(_StartType, _StartArgs) -> + ok = emqx_authz:register_source(?AUTHZ_TYPE, emqx_authz_http), + ok = emqx_authn:register_provider(?AUTHN_TYPE, emqx_authn_http), + {ok, Sup} = emqx_auth_http_sup:start_link(), + {ok, Sup}. + +stop(_State) -> + ok = emqx_authn:deregister_provider(?AUTHN_TYPE), + ok = emqx_authz:unregister_source(?AUTHZ_TYPE), + ok. diff --git a/apps/emqx_auth_http/src/emqx_auth_http_sup.erl b/apps/emqx_auth_http/src/emqx_auth_http_sup.erl new file mode 100644 index 000000000..90c865f83 --- /dev/null +++ b/apps/emqx_auth_http/src/emqx_auth_http_sup.erl @@ -0,0 +1,37 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_http_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(SERVER, ?MODULE). + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +init([]) -> + SupFlags = #{ + strategy => one_for_all, + intensity => 0, + period => 1 + }, + ChildSpecs = [], + {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl b/apps/emqx_auth_http/src/emqx_authn_http.erl similarity index 51% rename from apps/emqx_authn/src/simple_authn/emqx_authn_http.erl rename to apps/emqx_auth_http/src/emqx_authn_http.erl index 722d11e81..1ba7994cf 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl +++ b/apps/emqx_auth_http/src/emqx_authn_http.erl @@ -16,188 +16,47 @@ -module(emqx_authn_http). --include("emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("emqx/include/logger.hrl"). --include_lib("hocon/include/hoconsc.hrl"). --include_lib("emqx_connector/include/emqx_connector.hrl"). --behaviour(hocon_schema). --behaviour(emqx_authentication). +-behaviour(emqx_authn_provider). -export([ - namespace/0, - tags/0, - roots/0, - fields/1, - desc/1, - validations/0 -]). - --export([ - headers_no_content_type/1, - headers/1 -]). - --export([check_headers/1, check_ssl_opts/1]). - --export([ - refs/0, - union_member_selector/1, create/2, update/2, authenticate/2, destroy/1 ]). -%%------------------------------------------------------------------------------ -%% Hocon Schema -%%------------------------------------------------------------------------------ - -namespace() -> "authn". - -tags() -> - [<<"Authentication">>]. - -%% used for config check when the schema module is resolved -roots() -> - [ - {?CONF_NS, - hoconsc:mk( - hoconsc:union(fun ?MODULE:union_member_selector/1), - #{} - )} - ]. - -fields(http_get) -> - [ - {method, #{type => get, required => true, desc => ?DESC(method)}}, - {headers, fun headers_no_content_type/1} - ] ++ common_fields(); -fields(http_post) -> - [ - {method, #{type => post, required => true, desc => ?DESC(method)}}, - {headers, fun headers/1} - ] ++ common_fields(). - -desc(http_get) -> - ?DESC(get); -desc(http_post) -> - ?DESC(post); -desc(_) -> - undefined. - -common_fields() -> - [ - {mechanism, emqx_authn_schema:mechanism(password_based)}, - {backend, emqx_authn_schema:backend(http)}, - {url, fun url/1}, - {body, - hoconsc:mk(map([{fuzzy, term(), binary()}]), #{ - required => false, desc => ?DESC(body) - })}, - {request_timeout, fun request_timeout/1} - ] ++ emqx_authn_schema:common_fields() ++ - maps:to_list( - maps:without( - [ - pool_type - ], - maps:from_list(emqx_bridge_http_connector:fields(config)) - ) - ). - -validations() -> - [ - {check_ssl_opts, fun ?MODULE:check_ssl_opts/1}, - {check_headers, fun ?MODULE:check_headers/1} - ]. - -url(type) -> binary(); -url(desc) -> ?DESC(?FUNCTION_NAME); -url(validator) -> [?NOT_EMPTY("the value of the field 'url' cannot be empty")]; -url(required) -> true; -url(_) -> undefined. - -headers(type) -> - map(); -headers(desc) -> - ?DESC(?FUNCTION_NAME); -headers(converter) -> - fun(Headers) -> - maps:merge(default_headers(), transform_header_name(Headers)) - end; -headers(default) -> - default_headers(); -headers(_) -> - undefined. - -headers_no_content_type(type) -> - map(); -headers_no_content_type(desc) -> - ?DESC(?FUNCTION_NAME); -headers_no_content_type(converter) -> - fun(Headers) -> - maps:without( - [<<"content-type">>], - maps:merge(default_headers_no_content_type(), transform_header_name(Headers)) - ) - end; -headers_no_content_type(default) -> - default_headers_no_content_type(); -headers_no_content_type(_) -> - undefined. - -request_timeout(type) -> emqx_schema:duration_ms(); -request_timeout(desc) -> ?DESC(?FUNCTION_NAME); -request_timeout(default) -> <<"5s">>; -request_timeout(_) -> undefined. - %%------------------------------------------------------------------------------ %% APIs %%------------------------------------------------------------------------------ -refs() -> - [ - hoconsc:ref(?MODULE, http_get), - hoconsc:ref(?MODULE, http_post) - ]. - -union_member_selector(all_union_members) -> - refs(); -union_member_selector({value, Value}) -> - refs(Value). - -refs(#{<<"method">> := <<"get">>}) -> - [hoconsc:ref(?MODULE, http_get)]; -refs(#{<<"method">> := <<"post">>}) -> - [hoconsc:ref(?MODULE, http_post)]; -refs(_) -> - throw(#{ - field_name => method, - expected => "get | post" - }). - create(_AuthenticatorID, Config) -> create(Config). create(Config0) -> - ResourceId = emqx_authn_utils:make_resource_id(?MODULE), - {Config, State} = parse_config(Config0), - {ok, _Data} = emqx_authn_utils:create_resource( - ResourceId, - emqx_bridge_http_connector, - Config - ), - {ok, State#{resource_id => ResourceId}}. + with_validated_config(Config0, fun(Config, State) -> + ResourceId = emqx_authn_utils:make_resource_id(?MODULE), + % {Config, State} = parse_config(Config0), + {ok, _Data} = emqx_authn_utils:create_resource( + ResourceId, + emqx_bridge_http_connector, + Config + ), + {ok, State#{resource_id => ResourceId}} + end). update(Config0, #{resource_id := ResourceId} = _State) -> - {Config, NState} = parse_config(Config0), - case emqx_authn_utils:update_resource(emqx_bridge_http_connector, Config, ResourceId) of - {error, Reason} -> - error({load_config_error, Reason}); - {ok, _} -> - {ok, NState#{resource_id => ResourceId}} - end. + with_validated_config(Config0, fun(Config, NState) -> + % {Config, NState} = parse_config(Config0), + case emqx_authn_utils:update_resource(emqx_bridge_http_connector, Config, ResourceId) of + {error, Reason} -> + error({load_config_error, Reason}); + {ok, _} -> + {ok, NState#{resource_id => ResourceId}} + end + end). authenticate(#{auth_method := _}, _) -> ignore; @@ -237,92 +96,35 @@ destroy(#{resource_id := ResourceId}) -> %% Internal functions %%-------------------------------------------------------------------- -default_headers() -> - maps:put( - <<"content-type">>, - <<"application/json">>, - default_headers_no_content_type() - ). +with_validated_config(Config, Fun) -> + Pipeline = [ + fun check_ssl_opts/1, + fun check_headers/1, + fun parse_config/1 + ], + case emqx_utils:pipeline(Pipeline, Config, undefined) of + {ok, NConfig, ProviderState} -> + Fun(NConfig, ProviderState); + {error, Reason, _} -> + {error, Reason} + end. -default_headers_no_content_type() -> - #{ - <<"accept">> => <<"application/json">>, - <<"cache-control">> => <<"no-cache">>, - <<"connection">> => <<"keep-alive">>, - <<"keep-alive">> => <<"timeout=30, max=1000">> - }. +check_ssl_opts(#{url := <<"https://", _/binary>>, ssl := #{enable := false}}) -> + {error, + {invalid_ssl_opts, + <<"it's required to enable the TLS option to establish a https connection">>}}; +check_ssl_opts(_) -> + ok. -transform_header_name(Headers) -> - maps:fold( - fun(K0, V, Acc) -> - K = list_to_binary(string:to_lower(to_list(K0))), - maps:put(K, V, Acc) - end, - #{}, - Headers - ). - -check_ssl_opts(Conf) -> - case is_backend_http(Conf) of - true -> - Url = get_conf_val("url", Conf), - {BaseUrl, _Path, _Query} = parse_url(Url), - case BaseUrl of - <<"https://", _/binary>> -> - case get_conf_val("ssl.enable", Conf) of - true -> - ok; - false -> - <<"it's required to enable the TLS option to establish a https connection">> - end; - <<"http://", _/binary>> -> - ok - end; +check_headers(#{headers := Headers, method := get}) -> + case maps:is_key(<<"content-type">>, Headers) of false -> - ok - end. - -check_headers(Conf) -> - case is_backend_http(Conf) of + ok; true -> - Headers = get_conf_val("headers", Conf), - case to_bin(get_conf_val("method", Conf)) of - <<"post">> -> - ok; - <<"get">> -> - case maps:is_key(<<"content-type">>, Headers) of - false -> ok; - true -> <<"HTTP GET requests cannot include content-type header.">> - end - end; - false -> - ok - end. - -is_backend_http(Conf) -> - case get_conf_val("backend", Conf) of - http -> true; - _ -> false - end. - -parse_url(Url) -> - case string:split(Url, "//", leading) of - [Scheme, UrlRem] -> - case string:split(UrlRem, "/", leading) of - [HostPort, Remaining] -> - BaseUrl = iolist_to_binary([Scheme, "//", HostPort]), - case string:split(Remaining, "?", leading) of - [Path, QueryString] -> - {BaseUrl, <<"/", Path/binary>>, QueryString}; - [Path] -> - {BaseUrl, <<"/", Path/binary>>, <<>>} - end; - [HostPort] -> - {iolist_to_binary([Scheme, "//", HostPort]), <<>>, <<>>} - end; - [Url] -> - throw({invalid_url, Url}) - end. + {error, {invalid_headers, <<"HTTP GET requests cannot include content-type header.">>}} + end; +check_headers(_) -> + ok. parse_config( #{ @@ -332,7 +134,7 @@ parse_config( request_timeout := RequestTimeout } = Config ) -> - {BaseUrl0, Path, Query} = parse_url(RawUrl), + {BaseUrl0, Path, Query} = emqx_authn_utils:parse_url(RawUrl), {ok, BaseUrl} = emqx_http_lib:uri_parse(BaseUrl0), State = #{ method => Method, @@ -346,7 +148,7 @@ parse_config( request_timeout => RequestTimeout, url => RawUrl }, - {Config#{base_url => BaseUrl, pool_type => random}, State}. + {ok, Config#{base_url => BaseUrl, pool_type => random}, State}. generate_request(Credential, #{ method := Method, @@ -469,16 +271,11 @@ to_list(B) when is_binary(B) -> to_list(L) when is_list(L) -> L. -to_bin(A) when is_atom(A) -> - atom_to_binary(A); to_bin(B) when is_binary(B) -> B; to_bin(L) when is_list(L) -> list_to_binary(L). -get_conf_val(Name, Conf) -> - hocon_maps:get(?CONF_NS ++ "." ++ Name, Conf). - ensure_header_name_type(Headers) -> Fun = fun (Key, _Val, Acc) when is_binary(Key) -> diff --git a/apps/emqx_auth_http/src/emqx_authn_http_schema.erl b/apps/emqx_auth_http/src/emqx_authn_http_schema.erl new file mode 100644 index 000000000..205eb87d8 --- /dev/null +++ b/apps/emqx_auth_http/src/emqx_authn_http_schema.erl @@ -0,0 +1,132 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authn_http_schema). + +-include("emqx_auth_http.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authn_schema). + +-export([ + fields/1, + desc/1, + refs/0, + select_union_member/1 +]). + +-define(NOT_EMPTY(MSG), emqx_resource_validator:not_empty(MSG)). + +refs() -> + [?R_REF(http_get), ?R_REF(http_post)]. + +select_union_member( + #{<<"mechanism">> := ?AUTHN_MECHANISM_BIN, <<"backend">> := ?AUTHN_BACKEND_BIN} = Value +) -> + Method = maps:get(<<"method">>, Value, undefined), + case Method of + <<"get">> -> + [?R_REF(http_get)]; + <<"post">> -> + [?R_REF(http_post)]; + Else -> + throw(#{ + reason => "unknown_http_method", + expected => "get | post", + field_name => method, + got => Else + }) + end; +select_union_member(#{<<"backend">> := ?AUTHN_BACKEND_BIN}) -> + throw(#{ + reason => "unknown_mechanism", + expected => "password_based", + got => undefined + }); +select_union_member(_Value) -> + undefined. + +fields(http_get) -> + [ + {method, #{type => get, required => true, desc => ?DESC(method)}}, + {headers, fun headers_no_content_type/1} + ] ++ common_fields(); +fields(http_post) -> + [ + {method, #{type => post, required => true, desc => ?DESC(method)}}, + {headers, fun headers/1} + ] ++ common_fields(). + +desc(http_get) -> + ?DESC(get); +desc(http_post) -> + ?DESC(post); +desc(_) -> + undefined. + +common_fields() -> + [ + {mechanism, emqx_authn_schema:mechanism(?AUTHN_MECHANISM)}, + {backend, emqx_authn_schema:backend(?AUTHN_BACKEND)}, + {url, fun url/1}, + {body, + hoconsc:mk(map([{fuzzy, term(), binary()}]), #{ + required => false, desc => ?DESC(body) + })}, + {request_timeout, fun request_timeout/1} + ] ++ emqx_authn_schema:common_fields() ++ + maps:to_list( + maps:without( + [ + pool_type + ], + maps:from_list(emqx_bridge_http_connector:fields(config)) + ) + ). + +url(type) -> binary(); +url(desc) -> ?DESC(?FUNCTION_NAME); +url(validator) -> [?NOT_EMPTY("the value of the field 'url' cannot be empty")]; +url(required) -> true; +url(_) -> undefined. + +headers(type) -> + map(); +headers(desc) -> + ?DESC(?FUNCTION_NAME); +headers(converter) -> + fun emqx_authn_utils:convert_headers/1; +headers(default) -> + emqx_authn_utils:default_headers(); +headers(_) -> + undefined. + +headers_no_content_type(type) -> + map(); +headers_no_content_type(desc) -> + ?DESC(?FUNCTION_NAME); +headers_no_content_type(converter) -> + fun emqx_authn_utils:convert_headers_no_content_type/1; +headers_no_content_type(default) -> + emqx_authn_utils:default_headers_no_content_type(); +headers_no_content_type(_) -> + undefined. + +request_timeout(type) -> emqx_schema:duration_ms(); +request_timeout(desc) -> ?DESC(?FUNCTION_NAME); +request_timeout(default) -> <<"5s">>; +request_timeout(_) -> undefined. diff --git a/apps/emqx_authz/src/emqx_authz_http.erl b/apps/emqx_auth_http/src/emqx_authz_http.erl similarity index 91% rename from apps/emqx_authz/src/emqx_authz_http.erl rename to apps/emqx_auth_http/src/emqx_authz_http.erl index 5e0895900..ed7051bb6 100644 --- a/apps/emqx_authz/src/emqx_authz_http.erl +++ b/apps/emqx_auth_http/src/emqx_authz_http.erl @@ -16,13 +16,11 @@ -module(emqx_authz_http). --include("emqx_authz.hrl"). --include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl"). --behaviour(emqx_authz). +-behaviour(emqx_authz_source). %% AuthZ Callbacks -export([ @@ -31,6 +29,7 @@ update/1, destroy/1, authorize/4, + merge_defaults/1, parse_url/1 ]). @@ -73,7 +72,7 @@ update(Config) -> end. destroy(#{annotations := #{id := Id}}) -> - ok = emqx_resource:remove_local(Id). + emqx_authz_utils:remove_resource(Id). authorize( Client, @@ -95,7 +94,7 @@ authorize( case emqx_authz_utils:parse_http_resp_body(ContentType, Body) of error -> ?SLOG(error, #{ - msg => "authz_http_response_incorrect", + msg => authz_http_response_incorrect, content_type => ContentType, body => Body }), @@ -119,11 +118,25 @@ authorize( ignore end. +merge_defaults(#{<<"headers">> := Headers} = Source) -> + NewHeaders = + case Source of + #{<<"method">> := <<"get">>} -> + (emqx_authz_http_schema:headers_no_content_type(converter))(Headers); + #{<<"method">> := <<"post">>} -> + (emqx_authz_http_schema:headers(converter))(Headers); + _ -> + Headers + end, + Source#{<<"headers">> => NewHeaders}; +merge_defaults(Source) -> + Source. + log_nomtach_msg(Status, Headers, Body) -> ?SLOG( debug, #{ - msg => "unexpected_authz_http_response", + msg => unexpected_authz_http_response, status => Status, content_type => emqx_authz_utils:content_type(Headers), body => Body diff --git a/apps/emqx_auth_http/src/emqx_authz_http_schema.erl b/apps/emqx_auth_http/src/emqx_authz_http_schema.erl new file mode 100644 index 000000000..18ec23757 --- /dev/null +++ b/apps/emqx_auth_http/src/emqx_authz_http_schema.erl @@ -0,0 +1,179 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authz_http_schema). + +-include("emqx_auth_http.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authz_schema). + +-export([ + type/0, + fields/1, + desc/1, + source_refs/0, + select_union_member/1 +]). + +-export([ + headers_no_content_type/1, + headers/1 +]). + +-define(NOT_EMPTY(MSG), emqx_resource_validator:not_empty(MSG)). + +-import(emqx_schema, [mk_duration/2]). + +type() -> ?AUTHZ_TYPE. + +source_refs() -> + [?R_REF(http_get), ?R_REF(http_post)]. + +fields(http_get) -> + emqx_authz_schema:authz_common_fields(?AUTHZ_TYPE) ++ + http_common_fields() ++ + [ + {method, method(get)}, + {headers, fun headers_no_content_type/1} + ]; +fields(http_post) -> + emqx_authz_schema:authz_common_fields(?AUTHZ_TYPE) ++ + http_common_fields() ++ + [ + {method, method(post)}, + {headers, fun headers/1} + ]. + +desc(http_get) -> + ?DESC(http_get); +desc(http_post) -> + ?DESC(http_post); +desc(_) -> + undefined. + +select_union_member(#{<<"type">> := ?AUTHZ_TYPE_BIN} = Value) -> + Method = maps:get(<<"method">>, Value, undefined), + case Method of + <<"get">> -> + ?R_REF(http_get); + <<"post">> -> + ?R_REF(http_post); + Else -> + throw(#{ + reason => "unknown_http_method", + expected => "get | post", + got => Else + }) + end; +select_union_member(_Value) -> + undefined. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +method(Method) -> + ?HOCON(Method, #{required => true, desc => ?DESC(method)}). + +http_common_fields() -> + [ + {url, fun url/1}, + {request_timeout, + mk_duration("Request timeout", #{ + required => false, default => <<"30s">>, desc => ?DESC(request_timeout) + })}, + {body, ?HOCON(map(), #{required => false, desc => ?DESC(body)})} + ] ++ + lists:keydelete( + pool_type, + 1, + emqx_bridge_http_connector:fields(config) + ). + +headers(type) -> + list({binary(), binary()}); +headers(desc) -> + ?DESC(?FUNCTION_NAME); +headers(converter) -> + fun(Headers) -> + maps:to_list(maps:merge(default_headers(), transform_header_name(Headers))) + end; +headers(default) -> + default_headers(); +headers(_) -> + undefined. + +headers_no_content_type(type) -> + list({binary(), binary()}); +headers_no_content_type(desc) -> + ?DESC(?FUNCTION_NAME); +headers_no_content_type(converter) -> + fun(Headers) -> + maps:to_list( + maps:without( + [<<"content-type">>], + maps:merge(default_headers_no_content_type(), transform_header_name(Headers)) + ) + ) + end; +headers_no_content_type(default) -> + default_headers_no_content_type(); +headers_no_content_type(validator) -> + fun(Headers) -> + case lists:keyfind(<<"content-type">>, 1, Headers) of + false -> ok; + _ -> {error, do_not_include_content_type} + end + end; +headers_no_content_type(_) -> + undefined. + +url(type) -> binary(); +url(desc) -> ?DESC(?FUNCTION_NAME); +url(validator) -> [?NOT_EMPTY("the value of the field 'url' cannot be empty")]; +url(required) -> true; +url(_) -> undefined. + +default_headers() -> + maps:put( + <<"content-type">>, + <<"application/json">>, + default_headers_no_content_type() + ). + +default_headers_no_content_type() -> + #{ + <<"accept">> => <<"application/json">>, + <<"cache-control">> => <<"no-cache">>, + <<"connection">> => <<"keep-alive">>, + <<"keep-alive">> => <<"timeout=30, max=1000">> + }. + +transform_header_name(Headers) -> + maps:fold( + fun(K0, V, Acc) -> + K = list_to_binary(string:to_lower(to_list(K0))), + maps:put(K, V, Acc) + end, + #{}, + Headers + ). + +to_list(A) when is_atom(A) -> + atom_to_list(A); +to_list(B) when is_binary(B) -> + binary_to_list(B). diff --git a/apps/emqx_authn/test/emqx_authn_http_SUITE.erl b/apps/emqx_auth_http/test/emqx_authn_http_SUITE.erl similarity index 98% rename from apps/emqx_authn/test/emqx_authn_http_SUITE.erl rename to apps/emqx_auth_http/test/emqx_authn_http_SUITE.erl index dcd41a28d..577b3b638 100644 --- a/apps/emqx_authn/test/emqx_authn_http_SUITE.erl +++ b/apps/emqx_auth_http/test/emqx_authn_http_SUITE.erl @@ -19,7 +19,7 @@ -compile(nowarn_export_all). -compile(export_all). --include("emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl"). @@ -65,7 +65,7 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - Apps = emqx_cth_suite:start([cowboy, emqx, emqx_conf, emqx_authn], #{ + Apps = emqx_cth_suite:start([cowboy, emqx, emqx_conf, emqx_auth, emqx_auth_http], #{ work_dir => ?config(priv_dir, Config) }), [{apps, Apps} | Config]. @@ -102,7 +102,7 @@ t_create(_Config) -> {create_authenticator, ?GLOBAL, AuthConfig} ), - {ok, [#{provider := emqx_authn_http}]} = emqx_authentication:list_authenticators(?GLOBAL). + {ok, [#{provider := emqx_authn_http}]} = emqx_authn_chains:list_authenticators(?GLOBAL). t_create_invalid(_Config) -> AuthConfig = raw_http_auth_config(), @@ -128,7 +128,7 @@ t_create_invalid(_Config) -> end, ?assertEqual( {error, {not_found, {chain, ?GLOBAL}}}, - emqx_authentication:list_authenticators(?GLOBAL) + emqx_authn_chains:list_authenticators(?GLOBAL) ) end, InvalidConfigs @@ -274,7 +274,7 @@ t_destroy(_Config) -> ), {ok, [#{provider := emqx_authn_http, state := State}]} = - emqx_authentication:list_authenticators(?GLOBAL), + emqx_authn_chains:list_authenticators(?GLOBAL), Credentials = maps:with([username, password], ?CREDENTIALS), diff --git a/apps/emqx_authn/test/emqx_authn_http_test_server.erl b/apps/emqx_auth_http/test/emqx_authn_http_test_server.erl similarity index 100% rename from apps/emqx_authn/test/emqx_authn_http_test_server.erl rename to apps/emqx_auth_http/test/emqx_authn_http_test_server.erl diff --git a/apps/emqx_authn/test/emqx_authn_https_SUITE.erl b/apps/emqx_auth_http/test/emqx_authn_https_SUITE.erl similarity index 98% rename from apps/emqx_authn/test/emqx_authn_https_SUITE.erl rename to apps/emqx_auth_http/test/emqx_authn_https_SUITE.erl index 6fb8de294..a22a4e3b1 100644 --- a/apps/emqx_authn/test/emqx_authn_https_SUITE.erl +++ b/apps/emqx_auth_http/test/emqx_authn_https_SUITE.erl @@ -19,7 +19,7 @@ -compile(nowarn_export_all). -compile(export_all). --include("emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl"). @@ -39,7 +39,7 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - Apps = emqx_cth_suite:start([cowboy, emqx, emqx_conf, emqx_authn], #{ + Apps = emqx_cth_suite:start([cowboy, emqx, emqx_conf, emqx_auth, emqx_auth_http], #{ work_dir => ?config(priv_dir, Config) }), [{apps, Apps} | Config]. @@ -160,7 +160,7 @@ stop_apps(Apps) -> lists:foreach(fun application:stop/1, Apps). cert_path(FileName) -> - Dir = code:lib_dir(emqx_authn, test), + Dir = code:lib_dir(emqx_auth, test), filename:join([Dir, <<"data/certs">>, FileName]). cowboy_handler(Req0, State) -> diff --git a/apps/emqx_authz/test/emqx_authz_http_SUITE.erl b/apps/emqx_auth_http/test/emqx_authz_http_SUITE.erl similarity index 97% rename from apps/emqx_authz/test/emqx_authz_http_SUITE.erl rename to apps/emqx_auth_http/test/emqx_authz_http_SUITE.erl index 6cf4b5bc0..e56e25f5f 100644 --- a/apps/emqx_authz/test/emqx_authz_http_SUITE.erl +++ b/apps/emqx_auth_http/test/emqx_authz_http_SUITE.erl @@ -19,7 +19,7 @@ -compile(nowarn_export_all). -compile(export_all). --include("emqx_authz.hrl"). +-include_lib("emqx_auth/include/emqx_authz.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl"). @@ -41,23 +41,20 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - ok = stop_apps([emqx_resource, cowboy]), - ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1 + Apps = emqx_cth_suite:start( + [ + emqx, + {emqx_conf, "authorization.no_match = deny, authorization.cache.enable = false"}, + emqx_auth, + emqx_auth_http + ], + #{work_dir => ?config(priv_dir, Config)} ), - ok = start_apps([emqx_resource, cowboy]), - Config. + [{suite_apps, Apps} | Config]. end_per_suite(_Config) -> ok = emqx_authz_test_lib:restore_authorizers(), - ok = stop_apps([emqx_resource, cowboy]), - ok = emqx_common_test_helpers:stop_apps([emqx_conf, emqx_authz]). - -set_special_configs(emqx_authz) -> - ok = emqx_authz_test_lib:reset_authorizers(); -set_special_configs(_) -> - ok. + emqx_cth_suite:stop(?config(suite_apps, _Config)). init_per_testcase(_Case, Config) -> ok = emqx_authz_test_lib:reset_authorizers(), diff --git a/apps/emqx_authz/test/emqx_authz_http_test_server.erl b/apps/emqx_auth_http/test/emqx_authz_http_test_server.erl similarity index 100% rename from apps/emqx_authz/test/emqx_authz_http_test_server.erl rename to apps/emqx_auth_http/test/emqx_authz_http_test_server.erl diff --git a/apps/emqx_auth_jwt/include/emqx_auth_jwt.hrl b/apps/emqx_auth_jwt/include/emqx_auth_jwt.hrl new file mode 100644 index 000000000..0378ebcd6 --- /dev/null +++ b/apps/emqx_auth_jwt/include/emqx_auth_jwt.hrl @@ -0,0 +1,27 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-ifndef(EMQX_AUTH_JWT_HRL). +-define(EMQX_AUTH_JWT_HRL, true). + +-define(AUTHZ_TYPE, http). +-define(AUTHZ_TYPE_BIN, <<"http">>). + +-define(AUTHN_MECHANISM, jwt). +-define(AUTHN_MECHANISM_BIN, <<"jwt">>). +-define(AUTHN_TYPE, ?AUTHN_MECHANISM). + +-endif. diff --git a/apps/emqx_auth_jwt/rebar.config b/apps/emqx_auth_jwt/rebar.config new file mode 100644 index 000000000..db2424602 --- /dev/null +++ b/apps/emqx_auth_jwt/rebar.config @@ -0,0 +1,6 @@ +%% -*- mode: erlang -*- +{deps, [ + {emqx, {path, "../emqx"}}, + {emqx_utils, {path, "../emqx_utils"}}, + {emqx_auth, {path, "../emqx_auth"}} +]}. diff --git a/apps/emqx_auth_jwt/src/emqx_auth_jwt.app.src b/apps/emqx_auth_jwt/src/emqx_auth_jwt.app.src new file mode 100644 index 000000000..4679e43bb --- /dev/null +++ b/apps/emqx_auth_jwt/src/emqx_auth_jwt.app.src @@ -0,0 +1,21 @@ +%% -*- mode: erlang -*- +{application, emqx_auth_jwt, [ + {description, "EMQX JWT Authentication and Authorization"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, {emqx_auth_jwt_app, []}}, + {applications, [ + kernel, + stdlib, + jose, + emqx, + emqx_auth, + emqx_connector, + emqx_resource + ]}, + {env, []}, + {modules, []}, + + {licenses, ["Apache 2.0"]}, + {links, []} +]}. diff --git a/apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl b/apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl new file mode 100644 index 000000000..9ca7bd7cc --- /dev/null +++ b/apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl @@ -0,0 +1,32 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_jwt_app). + +-include("emqx_auth_jwt.hrl"). + +-behaviour(application). + +-export([start/2, stop/1]). + +start(_StartType, _StartArgs) -> + {ok, Sup} = emqx_auth_jwt_sup:start_link(), + ok = emqx_authn:register_provider(?AUTHN_TYPE, emqx_authn_jwt), + {ok, Sup}. + +stop(_State) -> + ok = emqx_authn:deregister_provider(?AUTHN_TYPE), + ok. diff --git a/apps/emqx_auth_jwt/src/emqx_auth_jwt_sup.erl b/apps/emqx_auth_jwt/src/emqx_auth_jwt_sup.erl new file mode 100644 index 000000000..0ad3574bb --- /dev/null +++ b/apps/emqx_auth_jwt/src/emqx_auth_jwt_sup.erl @@ -0,0 +1,37 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_jwt_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(SERVER, ?MODULE). + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +init([]) -> + SupFlags = #{ + strategy => one_for_all, + intensity => 0, + period => 1 + }, + ChildSpecs = [], + {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_jwks_client.erl b/apps/emqx_auth_jwt/src/emqx_authn_jwks_client.erl similarity index 100% rename from apps/emqx_authn/src/simple_authn/emqx_authn_jwks_client.erl rename to apps/emqx_auth_jwt/src/emqx_authn_jwks_client.erl diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_jwks_connector.erl b/apps/emqx_auth_jwt/src/emqx_authn_jwks_connector.erl similarity index 100% rename from apps/emqx_authn/src/simple_authn/emqx_authn_jwks_connector.erl rename to apps/emqx_auth_jwt/src/emqx_authn_jwks_connector.erl diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl b/apps/emqx_auth_jwt/src/emqx_authn_jwt.erl similarity index 64% rename from apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl rename to apps/emqx_auth_jwt/src/emqx_authn_jwt.erl index 5fef89078..5e83a0d13 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl +++ b/apps/emqx_auth_jwt/src/emqx_authn_jwt.erl @@ -16,204 +16,24 @@ -module(emqx_authn_jwt). --include("emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("emqx/include/logger.hrl"). --include_lib("hocon/include/hoconsc.hrl"). - --behaviour(hocon_schema). -export([ - namespace/0, - tags/0, - roots/0, - fields/1, - desc/1 -]). - --export([ - refs/0, - union_member_selector/1, create/2, update/2, authenticate/2, destroy/1 ]). -%%------------------------------------------------------------------------------ -%% Hocon Schema -%%------------------------------------------------------------------------------ - -namespace() -> "authn". - -tags() -> - [<<"Authentication">>]. - -%% used for config check when the schema module is resolved -roots() -> - [ - {?CONF_NS, - hoconsc:mk( - hoconsc:union(fun ?MODULE:union_member_selector/1), - #{} - )} - ]. - -fields(jwt_hmac) -> - [ - %% for hmac, it's the 'algorithm' field which selects this type - %% use_jwks field can be ignored (kept for backward compatibility) - {use_jwks, - sc( - hoconsc:enum([false]), - #{ - required => false, - desc => ?DESC(use_jwks), - importance => ?IMPORTANCE_HIDDEN - } - )}, - {algorithm, - sc(hoconsc:enum(['hmac-based']), #{required => true, desc => ?DESC(algorithm)})}, - {secret, fun secret/1}, - {secret_base64_encoded, fun secret_base64_encoded/1} - ] ++ common_fields(); -fields(jwt_public_key) -> - [ - %% for public-key, it's the 'algorithm' field which selects this type - %% use_jwks field can be ignored (kept for backward compatibility) - {use_jwks, - sc( - hoconsc:enum([false]), - #{ - required => false, - desc => ?DESC(use_jwks), - importance => ?IMPORTANCE_HIDDEN - } - )}, - {algorithm, - sc(hoconsc:enum(['public-key']), #{required => true, desc => ?DESC(algorithm)})}, - {public_key, fun public_key/1} - ] ++ common_fields(); -fields(jwt_jwks) -> - [ - {use_jwks, sc(hoconsc:enum([true]), #{required => true, desc => ?DESC(use_jwks)})}, - {endpoint, fun endpoint/1}, - {pool_size, fun emqx_connector_schema_lib:pool_size/1}, - {refresh_interval, fun refresh_interval/1}, - {ssl, #{ - type => hoconsc:ref(emqx_schema, "ssl_client_opts"), - default => #{<<"enable">> => false}, - desc => ?DESC("ssl") - }} - ] ++ common_fields(). - -desc(jwt_hmac) -> - ?DESC(jwt_hmac); -desc(jwt_public_key) -> - ?DESC(jwt_public_key); -desc(jwt_jwks) -> - ?DESC(jwt_jwks); -desc(undefined) -> - undefined. - -common_fields() -> - [ - {mechanism, emqx_authn_schema:mechanism('jwt')}, - {acl_claim_name, #{ - type => binary(), - default => <<"acl">>, - desc => ?DESC(acl_claim_name) - }}, - {verify_claims, fun verify_claims/1}, - {from, fun from/1} - ] ++ emqx_authn_schema:common_fields(). - -secret(type) -> binary(); -secret(desc) -> ?DESC(?FUNCTION_NAME); -secret(required) -> true; -secret(_) -> undefined. - -secret_base64_encoded(type) -> boolean(); -secret_base64_encoded(desc) -> ?DESC(?FUNCTION_NAME); -secret_base64_encoded(default) -> false; -secret_base64_encoded(_) -> undefined. - -public_key(type) -> string(); -public_key(desc) -> ?DESC(?FUNCTION_NAME); -public_key(required) -> ture; -public_key(_) -> undefined. - -endpoint(type) -> string(); -endpoint(desc) -> ?DESC(?FUNCTION_NAME); -endpoint(required) -> true; -endpoint(_) -> undefined. - -refresh_interval(type) -> integer(); -refresh_interval(desc) -> ?DESC(?FUNCTION_NAME); -refresh_interval(default) -> 300; -refresh_interval(validator) -> [fun(I) -> I > 0 end]; -refresh_interval(_) -> undefined. - -verify_claims(type) -> - list(); -verify_claims(desc) -> - ?DESC(?FUNCTION_NAME); -verify_claims(default) -> - []; -verify_claims(validator) -> - [fun do_check_verify_claims/1]; -verify_claims(converter) -> - fun - (VerifyClaims) when is_map(VerifyClaims) -> - [{to_binary(K), V} || {K, V} <- maps:to_list(VerifyClaims)]; - (VerifyClaims) -> - VerifyClaims - end; -verify_claims(required) -> - false; -verify_claims(_) -> - undefined. - -from(type) -> hoconsc:enum([username, password]); -from(desc) -> ?DESC(?FUNCTION_NAME); -from(default) -> password; -from(_) -> undefined. +-export([ + handle_placeholder/1 +]). %%------------------------------------------------------------------------------ %% APIs %%------------------------------------------------------------------------------ -refs() -> - [ - hoconsc:ref(?MODULE, jwt_hmac), - hoconsc:ref(?MODULE, jwt_public_key), - hoconsc:ref(?MODULE, jwt_jwks) - ]. - -union_member_selector(all_union_members) -> - refs(); -union_member_selector({value, V}) -> - UseJWKS = maps:get(<<"use_jwks">>, V, undefined), - select_ref(boolean(UseJWKS), V). - -%% this field is technically a boolean type, -%% but union member selection is done before type casting (by typrefl), -%% so we have to allow strings too -boolean(<<"true">>) -> true; -boolean(<<"false">>) -> false; -boolean(Other) -> Other. - -select_ref(true, _) -> - [hoconsc:ref(?MODULE, 'jwt_jwks')]; -select_ref(false, #{<<"public_key">> := _}) -> - [hoconsc:ref(?MODULE, jwt_public_key)]; -select_ref(false, _) -> - [hoconsc:ref(?MODULE, jwt_hmac)]; -select_ref(_, _) -> - throw(#{ - field_name => use_jwks, - expected => "true | false" - }). - create(_AuthenticatorID, Config) -> create(Config). @@ -344,7 +164,7 @@ create2( ResourceId = emqx_authn_utils:make_resource_id(?MODULE), {ok, _Data} = emqx_resource:create_local( ResourceId, - ?RESOURCE_GROUP, + ?AUTHN_RESOURCE_GROUP, emqx_authn_jwks_connector, connector_opts(Config) ), @@ -509,35 +329,6 @@ do_verify_claims(Claims, [{Name, Value} | More]) -> {error, {claims, {Name, Value0}}} end. -do_check_verify_claims([]) -> - true; -do_check_verify_claims([{Name, Expected} | More]) -> - check_claim_name(Name) andalso - check_claim_expected(Expected) andalso - do_check_verify_claims(More). - -check_claim_name(exp) -> - false; -check_claim_name(iat) -> - false; -check_claim_name(nbf) -> - false; -check_claim_name(Name) when - Name == <<>>; - Name == "" --> - false; -check_claim_name(_) -> - true. - -check_claim_expected(Expected) -> - try handle_placeholder(Expected) of - _ -> true - catch - _:_ -> - false - end. - handle_verify_claims(VerifyClaims) -> handle_verify_claims(VerifyClaims, []). @@ -562,22 +353,13 @@ validate_placeholder(<<"clientid">>) -> validate_placeholder(<<"username">>) -> username. -to_binary(A) when is_atom(A) -> - atom_to_binary(A); -to_binary(B) when is_binary(B) -> - B. - -sc(Type, Meta) -> hoconsc:mk(Type, Meta). - binary_to_number(Bin) -> - try - {ok, erlang:binary_to_integer(Bin)} - catch - _:_ -> - try - {ok, erlang:binary_to_float(Bin)} - catch - _:_ -> - false + case string:to_integer(Bin) of + {Val, <<>>} -> + {ok, Val}; + _ -> + case string:to_float(Bin) of + {Val, <<>>} -> {ok, Val}; + _ -> false end end. diff --git a/apps/emqx_auth_jwt/src/emqx_authn_jwt_schema.erl b/apps/emqx_auth_jwt/src/emqx_authn_jwt_schema.erl new file mode 100644 index 000000000..fc7de7cd8 --- /dev/null +++ b/apps/emqx_auth_jwt/src/emqx_authn_jwt_schema.erl @@ -0,0 +1,217 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authn_jwt_schema). + +-include("emqx_auth_jwt.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authn_schema). + +-export([ + fields/1, + desc/1, + refs/0, + select_union_member/1 +]). + +refs() -> + [ + ?R_REF(jwt_hmac), + ?R_REF(jwt_public_key), + ?R_REF(jwt_jwks) + ]. + +select_union_member(#{<<"mechanism">> := ?AUTHN_MECHANISM_BIN} = Value) -> + UseJWKS = maps:get(<<"use_jwks">>, Value, undefined), + select_ref(boolean(UseJWKS), Value); +select_union_member(_Value) -> + undefined. + +select_ref(true, _) -> + [?R_REF(jwt_jwks)]; +select_ref(false, #{<<"public_key">> := _}) -> + [?R_REF(jwt_public_key)]; +select_ref(false, _) -> + [?R_REF(jwt_hmac)]; +select_ref(_, _) -> + throw(#{ + field_name => use_jwks, + expected => "true | false" + }). + +fields(jwt_hmac) -> + [ + %% for hmac, it's the 'algorithm' field which selects this type + %% use_jwks field can be ignored (kept for backward compatibility) + {use_jwks, + sc( + hoconsc:enum([false]), + #{ + required => false, + desc => ?DESC(use_jwks), + importance => ?IMPORTANCE_HIDDEN + } + )}, + {algorithm, + sc(hoconsc:enum(['hmac-based']), #{required => true, desc => ?DESC(algorithm)})}, + {secret, fun secret/1}, + {secret_base64_encoded, fun secret_base64_encoded/1} + ] ++ common_fields(); +fields(jwt_public_key) -> + [ + %% for public-key, it's the 'algorithm' field which selects this type + %% use_jwks field can be ignored (kept for backward compatibility) + {use_jwks, + sc( + hoconsc:enum([false]), + #{ + required => false, + desc => ?DESC(use_jwks), + importance => ?IMPORTANCE_HIDDEN + } + )}, + {algorithm, + sc(hoconsc:enum(['public-key']), #{required => true, desc => ?DESC(algorithm)})}, + {public_key, fun public_key/1} + ] ++ common_fields(); +fields(jwt_jwks) -> + [ + {use_jwks, sc(hoconsc:enum([true]), #{required => true, desc => ?DESC(use_jwks)})}, + {endpoint, fun endpoint/1}, + {pool_size, fun emqx_connector_schema_lib:pool_size/1}, + {refresh_interval, fun refresh_interval/1}, + {ssl, #{ + type => hoconsc:ref(emqx_schema, "ssl_client_opts"), + default => #{<<"enable">> => false}, + desc => ?DESC("ssl") + }} + ] ++ common_fields(). + +desc(jwt_hmac) -> + ?DESC(jwt_hmac); +desc(jwt_public_key) -> + ?DESC(jwt_public_key); +desc(jwt_jwks) -> + ?DESC(jwt_jwks); +desc(undefined) -> + undefined. + +common_fields() -> + [ + {mechanism, emqx_authn_schema:mechanism(?AUTHN_MECHANISM)}, + {acl_claim_name, #{ + type => binary(), + default => <<"acl">>, + desc => ?DESC(acl_claim_name) + }}, + {verify_claims, fun verify_claims/1}, + {from, fun from/1} + ] ++ emqx_authn_schema:common_fields(). + +secret(type) -> binary(); +secret(desc) -> ?DESC(?FUNCTION_NAME); +secret(required) -> true; +secret(_) -> undefined. + +secret_base64_encoded(type) -> boolean(); +secret_base64_encoded(desc) -> ?DESC(?FUNCTION_NAME); +secret_base64_encoded(default) -> false; +secret_base64_encoded(_) -> undefined. + +public_key(type) -> string(); +public_key(desc) -> ?DESC(?FUNCTION_NAME); +public_key(required) -> ture; +public_key(_) -> undefined. + +endpoint(type) -> string(); +endpoint(desc) -> ?DESC(?FUNCTION_NAME); +endpoint(required) -> true; +endpoint(_) -> undefined. + +refresh_interval(type) -> integer(); +refresh_interval(desc) -> ?DESC(?FUNCTION_NAME); +refresh_interval(default) -> 300; +refresh_interval(validator) -> [fun(I) -> I > 0 end]; +refresh_interval(_) -> undefined. + +verify_claims(type) -> + list(); +verify_claims(desc) -> + ?DESC(?FUNCTION_NAME); +verify_claims(default) -> + []; +verify_claims(validator) -> + [fun do_check_verify_claims/1]; +verify_claims(converter) -> + fun + (VerifyClaims) when is_map(VerifyClaims) -> + [{to_binary(K), V} || {K, V} <- maps:to_list(VerifyClaims)]; + (VerifyClaims) -> + VerifyClaims + end; +verify_claims(required) -> + false; +verify_claims(_) -> + undefined. + +do_check_verify_claims([]) -> + true; +do_check_verify_claims([{Name, Expected} | More]) -> + check_claim_name(Name) andalso + check_claim_expected(Expected) andalso + do_check_verify_claims(More). + +check_claim_name(exp) -> + false; +check_claim_name(iat) -> + false; +check_claim_name(nbf) -> + false; +check_claim_name(Name) when + Name == <<>>; + Name == "" +-> + false; +check_claim_name(_) -> + true. + +check_claim_expected(Expected) -> + try emqx_authn_jwt:handle_placeholder(Expected) of + _ -> true + catch + _:_ -> + false + end. + +from(type) -> hoconsc:enum([username, password]); +from(desc) -> ?DESC(?FUNCTION_NAME); +from(default) -> password; +from(_) -> undefined. + +%% this field is technically a boolean type, +%% but union member selection is done before type casting (by typrefl), +%% so we have to allow strings too +boolean(<<"true">>) -> true; +boolean(<<"false">>) -> false; +boolean(Other) -> Other. + +to_binary(A) when is_atom(A) -> + atom_to_binary(A); +to_binary(B) when is_binary(B) -> + B. + +sc(Type, Meta) -> hoconsc:mk(Type, Meta). diff --git a/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl b/apps/emqx_auth_jwt/test/emqx_authn_jwt_SUITE.erl similarity index 99% rename from apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl rename to apps/emqx_auth_jwt/test/emqx_authn_jwt_SUITE.erl index 75dfcbc6f..1d7312b3e 100644 --- a/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl +++ b/apps/emqx_auth_jwt/test/emqx_authn_jwt_SUITE.erl @@ -32,7 +32,7 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_auth, emqx_auth_jwt], #{ work_dir => ?config(priv_dir, Config) }), [{apps, Apps} | Config]. @@ -471,7 +471,7 @@ test_rsa_key(private) -> data_file("private_key.pem"). data_file(Name) -> - Dir = code:lib_dir(emqx_authn, test), + Dir = code:lib_dir(emqx_auth, test), list_to_binary(filename:join([Dir, "data", Name])). cert_file(Name) -> diff --git a/apps/emqx_authz/test/emqx_authz_jwt_SUITE.erl b/apps/emqx_auth_jwt/test/emqx_authz_jwt_SUITE.erl similarity index 95% rename from apps/emqx_authz/test/emqx_authz_jwt_SUITE.erl rename to apps/emqx_auth_jwt/test/emqx_authz_jwt_SUITE.erl index fcaa378c5..4be420202 100644 --- a/apps/emqx_authz/test/emqx_authz_jwt_SUITE.erl +++ b/apps/emqx_auth_jwt/test/emqx_authz_jwt_SUITE.erl @@ -20,7 +20,7 @@ -compile(export_all). -include_lib("emqx/include/emqx_placeholder.hrl"). --include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("emqx/include/emqx_mqtt.hrl"). -include_lib("emqx/include/emqx_access_control.hrl"). @@ -37,15 +37,20 @@ groups() -> []. init_per_suite(Config) -> - ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authn, emqx_authz], - fun set_special_configs/1 + Apps = emqx_cth_suite:start( + [ + emqx, + {emqx_conf, "authorization.no_match = deny, authorization.cache.enable = false"}, + emqx_auth, + emqx_auth_jwt + ], + #{work_dir => ?config(priv_dir, Config)} ), - ok = emqx_authentication:initialize_authentication(?GLOBAL, []), - Config. + [{suite_apps, Apps} | Config]. -end_per_suite(_Config) -> - ok = emqx_common_test_helpers:stop_apps([emqx_authn, emqx_authz, emqx_conf]). +end_per_suite(Config) -> + ok = emqx_authz_test_lib:restore_authorizers(), + emqx_cth_suite:stop(?config(suite_apps, Config)). init_per_testcase(_TestCase, Config) -> emqx_authn_test_lib:delete_authenticators( @@ -68,11 +73,6 @@ end_per_testcase(_TestCase, _Config) -> ), ok = emqx_authz_test_lib:restore_authorizers(). -set_special_configs(emqx_authz) -> - ok = emqx_authz_test_lib:reset_authorizers(); -set_special_configs(_) -> - ok. - %%------------------------------------------------------------------------------ %% Tests %%------------------------------------------------------------------------------ diff --git a/apps/emqx_auth_ldap/BSL.txt b/apps/emqx_auth_ldap/BSL.txt new file mode 100644 index 000000000..0acc0e696 --- /dev/null +++ b/apps/emqx_auth_ldap/BSL.txt @@ -0,0 +1,94 @@ +Business Source License 1.1 + +Licensor: Hangzhou EMQ Technologies Co., Ltd. +Licensed Work: EMQX Enterprise Edition + The Licensed Work is (c) 2023 + Hangzhou EMQ Technologies Co., Ltd. +Additional Use Grant: Students and educators are granted right to copy, + modify, and create derivative work for research + or education. +Change Date: 2027-02-01 +Change License: Apache License, Version 2.0 + +For information about alternative licensing arrangements for the Software, +please contact Licensor: https://www.emqx.com/en/contact + +Notice + +The Business Source License (this document, or the “License”) is not an Open +Source license. However, the Licensed Work will eventually be made available +under an Open Source License, as stated in this License. + +License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. +“Business Source License” is a trademark of MariaDB Corporation Ab. + +----------------------------------------------------------------------------- + +Business Source License 1.1 + +Terms + +The Licensor hereby grants you the right to copy, modify, create derivative +works, redistribute, and make non-production use of the Licensed Work. The +Licensor may make an Additional Use Grant, above, permitting limited +production use. + +Effective on the Change Date, or the fourth anniversary of the first publicly +available distribution of a specific version of the Licensed Work under this +License, whichever comes first, the Licensor hereby grants you rights under +the terms of the Change License, and the rights granted in the paragraph +above terminate. + +If your use of the Licensed Work does not comply with the requirements +currently in effect as described in this License, you must purchase a +commercial license from the Licensor, its affiliated entities, or authorized +resellers, or you must refrain from using the Licensed Work. + +All copies of the original and modified Licensed Work, and derivative works +of the Licensed Work, are subject to this License. This License applies +separately for each version of the Licensed Work and the Change Date may vary +for each version of the Licensed Work released by Licensor. + +You must conspicuously display this License on each original or modified copy +of the Licensed Work. If you receive the Licensed Work in original or +modified form from a third party, the terms and conditions set forth in this +License apply to your use of that work. + +Any use of the Licensed Work in violation of this License will automatically +terminate your rights under this License for the current and all other +versions of the Licensed Work. + +This License does not grant you any right in any trademark or logo of +Licensor or its affiliates (provided that you may use a trademark or logo of +Licensor as expressly required by this License). + +TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +TITLE. + +MariaDB hereby grants you permission to use this License’s text to license +your works, and to refer to it using the trademark “Business Source License”, +as long as you comply with the Covenants of Licensor below. + +Covenants of Licensor + +In consideration of the right to use this License’s text and the “Business +Source License” name and trademark, Licensor covenants to MariaDB, and to all +other recipients of the licensed work to be provided by Licensor: + +1. To specify as the Change License the GPL Version 2.0 or any later version, + or a license that is compatible with GPL Version 2.0 or a later version, + where “compatible” means that software provided under the Change License can + be included in a program with software provided under GPL Version 2.0 or a + later version. Licensor may specify additional Change Licenses without + limitation. + +2. To either: (a) specify an additional grant of rights to use that does not + impose any additional restriction on the right granted in this License, as + the Additional Use Grant; or (b) insert the text “None”. + +3. To specify a Change Date. + +4. Not to modify this License in any other way. diff --git a/apps/emqx_auth_ldap/README.md b/apps/emqx_auth_ldap/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/apps/emqx_auth_ldap/docker-ct b/apps/emqx_auth_ldap/docker-ct new file mode 100644 index 000000000..c1142c3c5 --- /dev/null +++ b/apps/emqx_auth_ldap/docker-ct @@ -0,0 +1 @@ +ldap diff --git a/apps/emqx_auth_ldap/include/emqx_auth_ldap.hrl b/apps/emqx_auth_ldap/include/emqx_auth_ldap.hrl new file mode 100644 index 000000000..9cf6ac3c0 --- /dev/null +++ b/apps/emqx_auth_ldap/include/emqx_auth_ldap.hrl @@ -0,0 +1,35 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-ifndef(EMQX_AUTH_LDAP_HRL). +-define(EMQX_AUTH_LDAP_HRL, true). + +-define(AUTHZ_TYPE, ldap). +-define(AUTHZ_TYPE_BIN, <<"ldap">>). + +-define(AUTHN_MECHANISM, password_based). +-define(AUTHN_MECHANISM_BIN, <<"password_based">>). + +-define(AUTHN_BACKEND, ldap). +-define(AUTHN_BACKEND_BIN, <<"ldap">>). + +-define(AUTHN_BACKEND_BIND, ldap_bind). +-define(AUTHN_BACKEND_BIND_BIN, <<"ldap_bind">>). + +-define(AUTHN_TYPE, {?AUTHN_MECHANISM, ?AUTHN_BACKEND}). +-define(AUTHN_TYPE_BIND, {?AUTHN_MECHANISM, ?AUTHN_BACKEND_BIND}). + +-endif. diff --git a/apps/emqx_auth_ldap/rebar.config b/apps/emqx_auth_ldap/rebar.config new file mode 100644 index 000000000..261f68cdc --- /dev/null +++ b/apps/emqx_auth_ldap/rebar.config @@ -0,0 +1,6 @@ +%% -*- mode: erlang -*- +{deps, [ + {emqx, {path, "../emqx"}}, + {emqx_utils, {path, "../emqx_utils"}}, + {emqx_auth, {path, "../emqx_auth"}} + ]}. diff --git a/apps/emqx_auth_ldap/src/emqx_auth_ldap.app.src b/apps/emqx_auth_ldap/src/emqx_auth_ldap.app.src new file mode 100644 index 000000000..4ad21d48f --- /dev/null +++ b/apps/emqx_auth_ldap/src/emqx_auth_ldap.app.src @@ -0,0 +1,17 @@ +%% -*- mode: erlang -*- +{application, emqx_auth_ldap, [ + {description, "EMQX LDAP Authentication and Authorization"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, {emqx_auth_ldap_app, []}}, + {applications, [ + kernel, + stdlib, + emqx_auth + ]}, + {env, []}, + {modules, []}, + + {licenses, ["Apache 2.0"]}, + {links, []} +]}. diff --git a/apps/emqx_auth_ldap/src/emqx_auth_ldap_app.erl b/apps/emqx_auth_ldap/src/emqx_auth_ldap_app.erl new file mode 100644 index 000000000..7d05faab9 --- /dev/null +++ b/apps/emqx_auth_ldap/src/emqx_auth_ldap_app.erl @@ -0,0 +1,36 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_ldap_app). + +-include("emqx_auth_ldap.hrl"). + +-behaviour(application). + +-export([start/2, stop/1]). + +start(_StartType, _StartArgs) -> + ok = emqx_authz:register_source(?AUTHZ_TYPE, emqx_authz_ldap), + ok = emqx_authn:register_provider(?AUTHN_TYPE, emqx_authn_ldap), + ok = emqx_authn:register_provider(?AUTHN_TYPE_BIND, emqx_authn_ldap_bind), + {ok, Sup} = emqx_auth_ldap_sup:start_link(), + {ok, Sup}. + +stop(_State) -> + ok = emqx_authn:deregister_provider(?AUTHN_TYPE), + ok = emqx_authn:deregister_provider(?AUTHN_TYPE_BIND), + ok = emqx_authz:unregister_source(?AUTHZ_TYPE), + ok. diff --git a/apps/emqx_auth_ldap/src/emqx_auth_ldap_sup.erl b/apps/emqx_auth_ldap/src/emqx_auth_ldap_sup.erl new file mode 100644 index 000000000..71a160d16 --- /dev/null +++ b/apps/emqx_auth_ldap/src/emqx_auth_ldap_sup.erl @@ -0,0 +1,37 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_ldap_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(SERVER, ?MODULE). + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +init([]) -> + SupFlags = #{ + strategy => one_for_all, + intensity => 0, + period => 1 + }, + ChildSpecs = [], + {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/emqx_ldap/src/emqx_ldap_authn.erl b/apps/emqx_auth_ldap/src/emqx_authn_ldap.erl similarity index 80% rename from apps/emqx_ldap/src/emqx_ldap_authn.erl rename to apps/emqx_auth_ldap/src/emqx_authn_ldap.erl index 16540bec8..8685faecd 100644 --- a/apps/emqx_ldap/src/emqx_ldap_authn.erl +++ b/apps/emqx_auth_ldap/src/emqx_authn_ldap.erl @@ -2,15 +2,13 @@ %% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. %%-------------------------------------------------------------------- --module(emqx_ldap_authn). +-module(emqx_authn_ldap). --include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("emqx/include/logger.hrl"). --include_lib("hocon/include/hoconsc.hrl"). -include_lib("eldap/include/eldap.hrl"). --behaviour(hocon_schema). --behaviour(emqx_authentication). +-behaviour(emqx_authn_provider). %% a compatible attribute for version 4.x -define(ISENABLED_ATTR, "isEnabled"). @@ -20,15 +18,6 @@ %% 2. Supports https://datatracker.ietf.org/doc/html/rfc3112 -export([ - namespace/0, - tags/0, - roots/0, - fields/1, - desc/1 -]). - --export([ - refs/0, create/2, update/2, authenticate/2, @@ -37,57 +26,11 @@ ]). -import(proplists, [get_value/2, get_value/3]). -%%------------------------------------------------------------------------------ -%% Hocon Schema -%%------------------------------------------------------------------------------ - -namespace() -> "authn". - -tags() -> - [<<"Authentication">>]. - -%% used for config check when the schema module is resolved -roots() -> - [{?CONF_NS, hoconsc:mk(hoconsc:ref(?MODULE, ldap))}]. - -fields(ldap) -> - [ - {mechanism, emqx_authn_schema:mechanism(password_based)}, - {backend, emqx_authn_schema:backend(ldap)}, - {password_attribute, fun password_attribute/1}, - {is_superuser_attribute, fun is_superuser_attribute/1}, - {query_timeout, fun query_timeout/1} - ] ++ - emqx_authn_schema:common_fields() ++ - emqx_ldap:fields(config). - -desc(ldap) -> - ?DESC(ldap); -desc(_) -> - undefined. - -password_attribute(type) -> string(); -password_attribute(desc) -> ?DESC(?FUNCTION_NAME); -password_attribute(default) -> <<"userPassword">>; -password_attribute(_) -> undefined. - -is_superuser_attribute(type) -> string(); -is_superuser_attribute(desc) -> ?DESC(?FUNCTION_NAME); -is_superuser_attribute(default) -> <<"isSuperuser">>; -is_superuser_attribute(_) -> undefined. - -query_timeout(type) -> emqx_schema:timeout_duration_ms(); -query_timeout(desc) -> ?DESC(?FUNCTION_NAME); -query_timeout(default) -> <<"5s">>; -query_timeout(_) -> undefined. %%------------------------------------------------------------------------------ %% APIs %%------------------------------------------------------------------------------ -refs() -> - [hoconsc:ref(?MODULE, ldap)]. - create(_AuthenticatorID, Config) -> do_create(?MODULE, Config). diff --git a/apps/emqx_ldap/src/emqx_ldap_authn_bind.erl b/apps/emqx_auth_ldap/src/emqx_authn_ldap_bind.erl similarity index 61% rename from apps/emqx_ldap/src/emqx_ldap_authn_bind.erl rename to apps/emqx_auth_ldap/src/emqx_authn_ldap_bind.erl index fce78312c..82f8b9443 100644 --- a/apps/emqx_ldap/src/emqx_ldap_authn_bind.erl +++ b/apps/emqx_auth_ldap/src/emqx_authn_ldap_bind.erl @@ -2,79 +2,33 @@ %% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. %%-------------------------------------------------------------------- --module(emqx_ldap_authn_bind). +-module(emqx_authn_ldap_bind). --include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("emqx/include/logger.hrl"). --include_lib("hocon/include/hoconsc.hrl"). -include_lib("eldap/include/eldap.hrl"). --behaviour(hocon_schema). --behaviour(emqx_authentication). +-behaviour(emqx_authn_provider). -export([ - namespace/0, - tags/0, - roots/0, - fields/1, - desc/1 -]). - --export([ - refs/0, create/2, update/2, authenticate/2, destroy/1 ]). -%%------------------------------------------------------------------------------ -%% Hocon Schema -%%------------------------------------------------------------------------------ - -namespace() -> "authn". - -tags() -> - [<<"Authentication">>]. - -%% used for config check when the schema module is resolved -roots() -> - [{?CONF_NS, hoconsc:mk(hoconsc:ref(?MODULE, ldap_bind))}]. - -fields(ldap_bind) -> - [ - {mechanism, emqx_authn_schema:mechanism(password_based)}, - {backend, emqx_authn_schema:backend(ldap_bind)}, - {query_timeout, fun query_timeout/1} - ] ++ - emqx_authn_schema:common_fields() ++ - emqx_ldap:fields(config) ++ emqx_ldap:fields(bind_opts). - -desc(ldap_bind) -> - ?DESC(ldap_bind); -desc(_) -> - undefined. - -query_timeout(type) -> emqx_schema:timeout_duration_ms(); -query_timeout(desc) -> ?DESC(?FUNCTION_NAME); -query_timeout(default) -> <<"5s">>; -query_timeout(_) -> undefined. - %%------------------------------------------------------------------------------ %% APIs %%------------------------------------------------------------------------------ -refs() -> - [hoconsc:ref(?MODULE, ldap_bind)]. - create(_AuthenticatorID, Config) -> - emqx_ldap_authn:do_create(?MODULE, Config). + emqx_authn_ldap:do_create(?MODULE, Config). update(Config, State) -> - emqx_ldap_authn:update(Config, State). + emqx_authn_ldap:update(Config, State). destroy(State) -> - emqx_ldap_authn:destroy(State). + emqx_authn_ldap:destroy(State). authenticate(#{auth_method := _}, _) -> ignore; diff --git a/apps/emqx_auth_ldap/src/emqx_authn_ldap_bind_schema.erl b/apps/emqx_auth_ldap/src/emqx_authn_ldap_bind_schema.erl new file mode 100644 index 000000000..9f07f984f --- /dev/null +++ b/apps/emqx_auth_ldap/src/emqx_authn_ldap_bind_schema.erl @@ -0,0 +1,62 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authn_ldap_bind_schema). + +-include("emqx_auth_ldap.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authn_schema). + +-export([ + fields/1, + refs/0, + select_union_member/1 +]). + +refs() -> + [?R_REF(ldap_bind)]. + +select_union_member(#{ + <<"mechanism">> := ?AUTHN_MECHANISM_BIN, <<"backend">> := ?AUTHN_BACKEND_BIND_BIN +}) -> + refs(); +select_union_member(#{<<"backend">> := ?AUTHN_BACKEND_BIND_BIN}) -> + throw(#{ + reason => "unknown_mechanism", + expected => ?AUTHN_MECHANISM + }); +select_union_member(_) -> + undefined. + +fields(ldap_bind) -> + [ + {mechanism, emqx_authn_schema:mechanism(?AUTHN_MECHANISM)}, + {backend, emqx_authn_schema:backend(?AUTHN_BACKEND_BIND)}, + {query_timeout, fun query_timeout/1} + ] ++ + emqx_authn_schema:common_fields() ++ + emqx_ldap:fields(config) ++ emqx_ldap:fields(bind_opts). + +% desc(ldap_bind) -> +% ?DESC(ldap_bind); +% desc(_) -> +% undefined. + +query_timeout(type) -> emqx_schema:timeout_duration_ms(); +query_timeout(desc) -> ?DESC(?FUNCTION_NAME); +query_timeout(default) -> <<"5s">>; +query_timeout(_) -> undefined. diff --git a/apps/emqx_auth_ldap/src/emqx_authn_ldap_schema.erl b/apps/emqx_auth_ldap/src/emqx_authn_ldap_schema.erl new file mode 100644 index 000000000..6b096e857 --- /dev/null +++ b/apps/emqx_auth_ldap/src/emqx_authn_ldap_schema.erl @@ -0,0 +1,72 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authn_ldap_schema). + +-include("emqx_auth_ldap.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authn_schema). + +-export([ + fields/1, + refs/0, + select_union_member/1 +]). + +refs() -> + [?R_REF(ldap)]. + +select_union_member(#{<<"mechanism">> := ?AUTHN_MECHANISM_BIN, <<"backend">> := ?AUTHN_BACKEND_BIN}) -> + refs(); +select_union_member(#{<<"backend">> := ?AUTHN_BACKEND_BIN}) -> + throw(#{ + reason => "unknown_mechanism", + expected => ?AUTHN_MECHANISM + }); +select_union_member(_) -> + undefined. + +fields(ldap) -> + [ + {mechanism, emqx_authn_schema:mechanism(?AUTHN_MECHANISM)}, + {backend, emqx_authn_schema:backend(?AUTHN_BACKEND)}, + {password_attribute, fun password_attribute/1}, + {is_superuser_attribute, fun is_superuser_attribute/1}, + {query_timeout, fun query_timeout/1} + ] ++ + emqx_authn_schema:common_fields() ++ + emqx_ldap:fields(config). + +% desc(ldap) -> +% ?DESC(ldap); +% desc(_) -> +% undefined. + +password_attribute(type) -> string(); +password_attribute(desc) -> ?DESC(?FUNCTION_NAME); +password_attribute(default) -> <<"userPassword">>; +password_attribute(_) -> undefined. + +is_superuser_attribute(type) -> string(); +is_superuser_attribute(desc) -> ?DESC(?FUNCTION_NAME); +is_superuser_attribute(default) -> <<"isSuperuser">>; +is_superuser_attribute(_) -> undefined. + +query_timeout(type) -> emqx_schema:timeout_duration_ms(); +query_timeout(desc) -> ?DESC(?FUNCTION_NAME); +query_timeout(default) -> <<"5s">>; +query_timeout(_) -> undefined. diff --git a/apps/emqx_ldap/src/emqx_ldap_authz.erl b/apps/emqx_auth_ldap/src/emqx_authz_ldap.erl similarity index 71% rename from apps/emqx_ldap/src/emqx_ldap_authz.erl rename to apps/emqx_auth_ldap/src/emqx_authz_ldap.erl index bfff9149f..eb12fdd37 100644 --- a/apps/emqx_ldap/src/emqx_ldap_authz.erl +++ b/apps/emqx_auth_ldap/src/emqx_authz_ldap.erl @@ -14,18 +14,12 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_ldap_authz). +-module(emqx_authz_ldap). --include_lib("emqx_authz/include/emqx_authz.hrl"). --include_lib("emqx/include/emqx.hrl"). --include_lib("hocon/include/hoconsc.hrl"). -include_lib("emqx/include/logger.hrl"). --include_lib("emqx/include/emqx_placeholder.hrl"). -include_lib("eldap/include/eldap.hrl"). --behaviour(emqx_authz). - --define(PREPARE_KEY, ?MODULE). +-behaviour(emqx_authz_source). %% AuthZ Callbacks -export([ @@ -36,43 +30,11 @@ authorize/4 ]). --export([fields/1]). - -ifdef(TEST). -compile(export_all). -compile(nowarn_export_all). -endif. -%%------------------------------------------------------------------------------ -%% Hocon Schema -%%------------------------------------------------------------------------------ - -fields(config) -> - emqx_authz_schema:authz_common_fields(ldap) ++ - [ - {publish_attribute, attribute_meta(publish_attribute, <<"mqttPublishTopic">>)}, - {subscribe_attribute, attribute_meta(subscribe_attribute, <<"mqttSubscriptionTopic">>)}, - {all_attribute, attribute_meta(all_attribute, <<"mqttPubSubTopic">>)}, - {query_timeout, - ?HOCON( - emqx_schema:timeout_duration_ms(), - #{ - desc => ?DESC(query_timeout), - default => <<"5s">> - } - )} - ] ++ - emqx_ldap:fields(config). - -attribute_meta(Name, Default) -> - ?HOCON( - string(), - #{ - default => Default, - desc => ?DESC(Name) - } - ). - %%------------------------------------------------------------------------------ %% AuthZ Callbacks %%------------------------------------------------------------------------------ @@ -96,7 +58,7 @@ update(Source) -> end. destroy(#{annotations := #{id := Id}}) -> - ok = emqx_resource:remove_local(Id). + emqx_authz_utils:remove_resource(Id). authorize( Client, diff --git a/apps/emqx_auth_ldap/src/emqx_authz_ldap_schema.erl b/apps/emqx_auth_ldap/src/emqx_authz_ldap_schema.erl new file mode 100644 index 000000000..491b0debf --- /dev/null +++ b/apps/emqx_auth_ldap/src/emqx_authz_ldap_schema.erl @@ -0,0 +1,75 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authz_ldap_schema). + +-include("emqx_auth_ldap.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authz_schema). + +-export([ + type/0, + fields/1, + desc/1, + source_refs/0, + select_union_member/1 +]). + +type() -> ?AUTHZ_TYPE. + +fields(ldap) -> + emqx_authz_schema:authz_common_fields(?AUTHZ_TYPE) ++ + [ + {publish_attribute, attribute_meta(publish_attribute, <<"mqttPublishTopic">>)}, + {subscribe_attribute, attribute_meta(subscribe_attribute, <<"mqttSubscriptionTopic">>)}, + {all_attribute, attribute_meta(all_attribute, <<"mqttPubSubTopic">>)}, + {query_timeout, + ?HOCON( + emqx_schema:timeout_duration_ms(), + #{ + desc => ?DESC(query_timeout), + default => <<"5s">> + } + )} + ] ++ + emqx_ldap:fields(config). + +desc(ldap) -> + emqx_authz_ldap:description(); +desc(_) -> + undefined. + +source_refs() -> + [?R_REF(ldap)]. + +select_union_member(#{<<"type">> := ?AUTHZ_TYPE_BIN}) -> + ?R_REF(ldap); +select_union_member(_Value) -> + undefined. + +%%-------------------------------------------------------------------- +%% Internal Functions +%%-------------------------------------------------------------------- + +attribute_meta(Name, Default) -> + ?HOCON( + string(), + #{ + default => Default, + desc => ?DESC(Name) + } + ). diff --git a/apps/emqx_ldap/test/emqx_ldap_authn_SUITE.erl b/apps/emqx_auth_ldap/test/emqx_authn_ldap_SUITE.erl similarity index 92% rename from apps/emqx_ldap/test/emqx_ldap_authn_SUITE.erl rename to apps/emqx_auth_ldap/test/emqx_authn_ldap_SUITE.erl index 7b5220b04..71ef144d2 100644 --- a/apps/emqx_ldap/test/emqx_ldap_authn_SUITE.erl +++ b/apps/emqx_auth_ldap/test/emqx_authn_ldap_SUITE.erl @@ -1,12 +1,12 @@ %%-------------------------------------------------------------------- %% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. %%-------------------------------------------------------------------- --module(emqx_ldap_authn_SUITE). +-module(emqx_authn_ldap_SUITE). -compile(nowarn_export_all). -compile(export_all). --include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). @@ -21,7 +21,6 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_testcase(_, Config) -> - emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL @@ -32,12 +31,12 @@ init_per_suite(Config) -> _ = application:load(emqx_conf), case emqx_common_test_helpers:is_tcp_server_available(?LDAP_HOST, ?LDAP_DEFAULT_PORT) of true -> - Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_auth], #{ work_dir => ?config(priv_dir, Config) }), {ok, _} = emqx_resource:create_local( ?LDAP_RESOURCE, - ?RESOURCE_GROUP, + ?AUTHN_RESOURCE_GROUP, emqx_ldap, ldap_config(), #{} @@ -67,7 +66,7 @@ t_create(_Config) -> {create_authenticator, ?GLOBAL, AuthConfig} ), - {ok, [#{provider := emqx_ldap_authn}]} = emqx_authentication:list_authenticators(?GLOBAL), + {ok, [#{provider := emqx_authn_ldap}]} = emqx_authn_chains:list_authenticators(?GLOBAL), emqx_authn_test_lib:delete_config(?ResourceID). t_create_invalid(_Config) -> @@ -88,7 +87,7 @@ t_create_invalid(_Config) -> emqx_authn_test_lib:delete_config(?ResourceID), ?assertEqual( {error, {not_found, {chain, ?GLOBAL}}}, - emqx_authentication:list_authenticators(?GLOBAL) + emqx_authn_chains:list_authenticators(?GLOBAL) ) end, InvalidConfigs @@ -135,10 +134,10 @@ t_destroy(_Config) -> {create_authenticator, ?GLOBAL, AuthConfig} ), - {ok, [#{provider := emqx_ldap_authn, state := State}]} = - emqx_authentication:list_authenticators(?GLOBAL), + {ok, [#{provider := emqx_authn_ldap, state := State}]} = + emqx_authn_chains:list_authenticators(?GLOBAL), - {ok, _} = emqx_ldap_authn:authenticate( + {ok, _} = emqx_authn_ldap:authenticate( #{ username => <<"mqttuser0001">>, password => <<"mqttuser0001">> @@ -154,7 +153,7 @@ t_destroy(_Config) -> % Authenticator should not be usable anymore ?assertMatch( ignore, - emqx_ldap_authn:authenticate( + emqx_authn_ldap:authenticate( #{ username => <<"mqttuser0001">>, password => <<"mqttuser0001">> diff --git a/apps/emqx_ldap/test/emqx_ldap_authn_bind_SUITE.erl b/apps/emqx_auth_ldap/test/emqx_authn_ldap_bind_SUITE.erl similarity index 98% rename from apps/emqx_ldap/test/emqx_ldap_authn_bind_SUITE.erl rename to apps/emqx_auth_ldap/test/emqx_authn_ldap_bind_SUITE.erl index 996416f52..827eb0079 100644 --- a/apps/emqx_ldap/test/emqx_ldap_authn_bind_SUITE.erl +++ b/apps/emqx_auth_ldap/test/emqx_authn_ldap_bind_SUITE.erl @@ -1,12 +1,12 @@ %%-------------------------------------------------------------------- %% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. %%-------------------------------------------------------------------- --module(emqx_ldap_authn_bind_SUITE). +-module(emqx_authn_ldap_bind_SUITE). -compile(nowarn_export_all). -compile(export_all). --include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). @@ -37,7 +37,7 @@ init_per_suite(Config) -> }), {ok, _} = emqx_resource:create_local( ?LDAP_RESOURCE, - ?RESOURCE_GROUP, + ?AUTHN_RESOURCE_GROUP, emqx_ldap, ldap_config(), #{} diff --git a/apps/emqx_ldap/test/emqx_ldap_authz_SUITE.erl b/apps/emqx_auth_ldap/test/emqx_authz_ldap_SUITE.erl similarity index 97% rename from apps/emqx_ldap/test/emqx_ldap_authz_SUITE.erl rename to apps/emqx_auth_ldap/test/emqx_authz_ldap_SUITE.erl index e6424e8ca..1fcc547fc 100644 --- a/apps/emqx_ldap/test/emqx_ldap_authz_SUITE.erl +++ b/apps/emqx_auth_ldap/test/emqx_authz_ldap_SUITE.erl @@ -1,18 +1,18 @@ %%-------------------------------------------------------------------- %% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. %%-------------------------------------------------------------------- --module(emqx_ldap_authz_SUITE). +-module(emqx_authz_ldap_SUITE). -compile(nowarn_export_all). -compile(export_all). --include("emqx_authz.hrl"). +-include_lib("emqx_auth/include/emqx_authz.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). -define(LDAP_HOST, "ldap"). -define(LDAP_DEFAULT_PORT, 389). --define(LDAP_RESOURCE, <<"emqx_ldap_authz_SUITE">>). +-define(LDAP_RESOURCE, <<"emqx_authz_ldap_SUITE">>). all() -> emqx_authz_test_lib:all_with_table_case(?MODULE, t_run_case, cases()). @@ -165,7 +165,7 @@ stop_apps(Apps) -> create_ldap_resource() -> {ok, _} = emqx_resource:create_local( ?LDAP_RESOURCE, - ?RESOURCE_GROUP, + ?AUTHZ_RESOURCE_GROUP, emqx_ldap, ldap_config(), #{} diff --git a/apps/emqx_auth_mnesia/include/emqx_auth_mnesia.hrl b/apps/emqx_auth_mnesia/include/emqx_auth_mnesia.hrl new file mode 100644 index 000000000..8cf53cf79 --- /dev/null +++ b/apps/emqx_auth_mnesia/include/emqx_auth_mnesia.hrl @@ -0,0 +1,37 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-ifndef(EMQX_AUTH_MNESIA_HRL). +-define(EMQX_AUTH_MNESIA_HRL, true). + +-define(AUTHN_SHARD, emqx_authn_shard). + +-define(AUTHZ_TYPE, built_in_database). +-define(AUTHZ_TYPE_BIN, <<"built_in_database">>). + +-define(AUTHN_MECHANISM_SIMPLE, password_based). +-define(AUTHN_MECHANISM_SIMPLE_BIN, <<"password_based">>). + +-define(AUTHN_MECHANISM_SCRAM, scram). +-define(AUTHN_MECHANISM_SCRAM_BIN, <<"scram">>). + +-define(AUTHN_BACKEND, built_in_database). +-define(AUTHN_BACKEND_BIN, <<"built_in_database">>). + +-define(AUTHN_TYPE_SIMPLE, {?AUTHN_MECHANISM_SIMPLE, ?AUTHN_BACKEND}). +-define(AUTHN_TYPE_SCRAM, {?AUTHN_MECHANISM_SCRAM, ?AUTHN_BACKEND}). + +-endif. diff --git a/apps/emqx_auth_mnesia/rebar.config b/apps/emqx_auth_mnesia/rebar.config new file mode 100644 index 000000000..db2424602 --- /dev/null +++ b/apps/emqx_auth_mnesia/rebar.config @@ -0,0 +1,6 @@ +%% -*- mode: erlang -*- +{deps, [ + {emqx, {path, "../emqx"}}, + {emqx_utils, {path, "../emqx_utils"}}, + {emqx_auth, {path, "../emqx_auth"}} +]}. diff --git a/apps/emqx_auth_mnesia/src/emqx_auth_mnesia.app.src b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia.app.src new file mode 100644 index 000000000..988d300fb --- /dev/null +++ b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia.app.src @@ -0,0 +1,18 @@ +%% -*- mode: erlang -*- +{application, emqx_auth_mnesia, [ + {description, "EMQX Buitl-in Database Authentication and Authorization"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, {emqx_auth_mnesia_app, []}}, + {applications, [ + kernel, + stdlib, + emqx, + emqx_auth + ]}, + {env, []}, + {modules, []}, + + {licenses, ["Apache 2.0"]}, + {links, []} +]}. diff --git a/apps/emqx_auth_mnesia/src/emqx_auth_mnesia_app.erl b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia_app.erl new file mode 100644 index 000000000..d08d38e10 --- /dev/null +++ b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia_app.erl @@ -0,0 +1,38 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_mnesia_app). + +-include("emqx_auth_mnesia.hrl"). + +-behaviour(application). + +-export([start/2, stop/1]). + +start(_StartType, _StartArgs) -> + ok = emqx_authz_mnesia:init_tables(), + ok = emqx_authn_mnesia:init_tables(), + ok = emqx_authz:register_source(?AUTHZ_TYPE, emqx_authz_mnesia), + ok = emqx_authn:register_provider(?AUTHN_TYPE_SIMPLE, emqx_authn_mnesia), + ok = emqx_authn:register_provider(?AUTHN_TYPE_SCRAM, emqx_authn_scram_mnesia), + {ok, Sup} = emqx_auth_mnesia_sup:start_link(), + {ok, Sup}. + +stop(_State) -> + ok = emqx_authn:deregister_provider(?AUTHN_TYPE_SIMPLE), + ok = emqx_authn:deregister_provider(?AUTHN_TYPE_SCRAM), + ok = emqx_authz:unregister_source(?AUTHZ_TYPE), + ok. diff --git a/apps/emqx_auth_mnesia/src/emqx_auth_mnesia_sup.erl b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia_sup.erl new file mode 100644 index 000000000..017b41ade --- /dev/null +++ b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia_sup.erl @@ -0,0 +1,37 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_mnesia_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(SERVER, ?MODULE). + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +init([]) -> + SupFlags = #{ + strategy => one_for_all, + intensity => 0, + period => 1 + }, + ChildSpecs = [], + {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl b/apps/emqx_auth_mnesia/src/emqx_authn_mnesia.erl similarity index 90% rename from apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl rename to apps/emqx_auth_mnesia/src/emqx_authn_mnesia.erl index 2a124ae98..8e59d94e7 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl +++ b/apps/emqx_auth_mnesia/src/emqx_authn_mnesia.erl @@ -16,25 +16,15 @@ -module(emqx_authn_mnesia). --include("emqx_authn.hrl"). +-include("emqx_auth_mnesia.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("emqx/include/logger.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). --include_lib("hocon/include/hoconsc.hrl"). --behaviour(hocon_schema). --behaviour(emqx_authentication). +-behaviour(emqx_authn_provider). -behaviour(emqx_db_backup). -export([ - namespace/0, - tags/0, - roots/0, - fields/1, - desc/1 -]). - --export([ - refs/0, create/2, update/2, authenticate/2, @@ -67,7 +57,7 @@ import_csv/3 ]). --export([mnesia/1]). +-export([mnesia/1, init_tables/0]). -export([backup_tables/0]). @@ -98,7 +88,7 @@ -spec mnesia(boot | copy) -> ok. mnesia(boot) -> ok = mria:create_table(?TAB, [ - {rlog_shard, ?AUTH_SHARD}, + {rlog_shard, ?AUTHN_SHARD}, {type, ordered_set}, {storage, disc_copies}, {record_name, user_info}, @@ -106,50 +96,21 @@ mnesia(boot) -> {storage_properties, [{ets, [{read_concurrency, true}]}]} ]). +%% Init +-spec init_tables() -> ok. +init_tables() -> + ok = mria_rlog:wait_for_shards([?AUTHN_SHARD], infinity). + %%------------------------------------------------------------------------------ %% Data backup %%------------------------------------------------------------------------------ + backup_tables() -> [?TAB]. -%%------------------------------------------------------------------------------ -%% Hocon Schema -%%------------------------------------------------------------------------------ - -namespace() -> "authn". - -tags() -> - [<<"Authentication">>]. - -%% used for config check when the schema module is resolved -roots() -> - [{?CONF_NS, hoconsc:mk(hoconsc:ref(?MODULE, builtin_db))}]. - -fields(builtin_db) -> - [ - {mechanism, emqx_authn_schema:mechanism(password_based)}, - {backend, emqx_authn_schema:backend(built_in_database)}, - {user_id_type, fun user_id_type/1}, - {password_hash_algorithm, fun emqx_authn_password_hashing:type_rw/1} - ] ++ emqx_authn_schema:common_fields(). - -desc(builtin_db) -> - ?DESC(builtin_db); -desc(_) -> - undefined. - -user_id_type(type) -> hoconsc:enum([clientid, username]); -user_id_type(desc) -> ?DESC(?FUNCTION_NAME); -user_id_type(default) -> <<"username">>; -user_id_type(required) -> true; -user_id_type(_) -> undefined. - %%------------------------------------------------------------------------------ %% APIs %%------------------------------------------------------------------------------ -refs() -> - [hoconsc:ref(?MODULE, builtin_db)]. - create(_AuthenticatorID, Config) -> create(Config). @@ -447,7 +408,7 @@ get_user_identity(_, Type) -> {error, {bad_user_identity_type, Type}}. trans(Fun, Args) -> - case mria:transaction(?AUTH_SHARD, Fun, Args) of + case mria:transaction(?AUTHN_SHARD, Fun, Args) of {atomic, Res} -> Res; {aborted, Reason} -> {error, Reason} end. diff --git a/apps/emqx_auth_mnesia/src/emqx_authn_mnesia_schema.erl b/apps/emqx_auth_mnesia/src/emqx_authn_mnesia_schema.erl new file mode 100644 index 000000000..2d57abc90 --- /dev/null +++ b/apps/emqx_auth_mnesia/src/emqx_authn_mnesia_schema.erl @@ -0,0 +1,58 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authn_mnesia_schema). + +-include("emqx_auth_mnesia.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authn_schema). + +-export([ + fields/1, + desc/1, + refs/0, + select_union_member/1 +]). + +refs() -> + [?R_REF(builtin_db)]. + +select_union_member(#{ + <<"mechanism">> := ?AUTHN_MECHANISM_SIMPLE_BIN, <<"backend">> := ?AUTHN_BACKEND_BIN +}) -> + refs(); +select_union_member(_) -> + undefined. + +fields(builtin_db) -> + [ + {mechanism, emqx_authn_schema:mechanism(?AUTHN_MECHANISM_SIMPLE)}, + {backend, emqx_authn_schema:backend(?AUTHN_BACKEND)}, + {user_id_type, fun user_id_type/1}, + {password_hash_algorithm, fun emqx_authn_password_hashing:type_rw/1} + ] ++ emqx_authn_schema:common_fields(). + +desc(builtin_db) -> + ?DESC(builtin_db); +desc(_) -> + undefined. + +user_id_type(type) -> hoconsc:enum([clientid, username]); +user_id_type(desc) -> ?DESC(?FUNCTION_NAME); +user_id_type(default) -> <<"username">>; +user_id_type(required) -> true; +user_id_type(_) -> undefined. diff --git a/apps/emqx_authn/src/enhanced_authn/emqx_enhanced_authn_scram_mnesia.erl b/apps/emqx_auth_mnesia/src/emqx_authn_scram_mnesia.erl similarity index 88% rename from apps/emqx_authn/src/enhanced_authn/emqx_enhanced_authn_scram_mnesia.erl rename to apps/emqx_auth_mnesia/src/emqx_authn_scram_mnesia.erl index 158112747..641efcf74 100644 --- a/apps/emqx_authn/src/enhanced_authn/emqx_enhanced_authn_scram_mnesia.erl +++ b/apps/emqx_auth_mnesia/src/emqx_authn_scram_mnesia.erl @@ -14,26 +14,17 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_enhanced_authn_scram_mnesia). +-module(emqx_authn_scram_mnesia). --include("emqx_authn.hrl"). +-include("emqx_auth_mnesia.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). -include_lib("typerefl/include/types.hrl"). --behaviour(hocon_schema). --behaviour(emqx_authentication). +-behaviour(emqx_authn_provider). -behaviour(emqx_db_backup). -export([ - namespace/0, - tags/0, - roots/0, - fields/1, - desc/1 -]). - --export([ - refs/0, create/2, update/2, authenticate/2, @@ -96,7 +87,7 @@ -spec mnesia(boot | copy) -> ok. mnesia(boot) -> ok = mria:create_table(?TAB, [ - {rlog_shard, ?AUTH_SHARD}, + {rlog_shard, ?AUTHN_SHARD}, {type, ordered_set}, {storage, disc_copies}, {record_name, user_info}, @@ -110,50 +101,10 @@ mnesia(boot) -> backup_tables() -> [?TAB]. -%%------------------------------------------------------------------------------ -%% Hocon Schema -%%------------------------------------------------------------------------------ - -namespace() -> "authn". - -tags() -> - [<<"Authentication">>]. - -%% used for config check when the schema module is resolved -roots() -> - [{?CONF_NS, hoconsc:mk(hoconsc:ref(?MODULE, scram))}]. - -fields(scram) -> - [ - {mechanism, emqx_authn_schema:mechanism(scram)}, - {backend, emqx_authn_schema:backend(built_in_database)}, - {algorithm, fun algorithm/1}, - {iteration_count, fun iteration_count/1} - ] ++ emqx_authn_schema:common_fields(). - -desc(scram) -> - "Settings for Salted Challenge Response Authentication Mechanism\n" - "(SCRAM) authentication."; -desc(_) -> - undefined. - -algorithm(type) -> hoconsc:enum([sha256, sha512]); -algorithm(desc) -> "Hashing algorithm."; -algorithm(default) -> sha256; -algorithm(_) -> undefined. - -iteration_count(type) -> non_neg_integer(); -iteration_count(desc) -> "Iteration count."; -iteration_count(default) -> 4096; -iteration_count(_) -> undefined. - %%------------------------------------------------------------------------------ %% APIs %%------------------------------------------------------------------------------ -refs() -> - [hoconsc:ref(?MODULE, scram)]. - create( AuthenticatorID, #{ @@ -400,7 +351,7 @@ retrieve(UserID, #{user_group := UserGroup}) -> %% TODO: Move to emqx_authn_utils.erl trans(Fun, Args) -> - case mria:transaction(?AUTH_SHARD, Fun, Args) of + case mria:transaction(?AUTHN_SHARD, Fun, Args) of {atomic, Res} -> Res; {aborted, {function_clause, Stack}} -> erlang:raise(error, function_clause, Stack); {aborted, Reason} -> {error, Reason} diff --git a/apps/emqx_auth_mnesia/src/emqx_authn_scram_mnesia_schema.erl b/apps/emqx_auth_mnesia/src/emqx_authn_scram_mnesia_schema.erl new file mode 100644 index 000000000..fa22693b3 --- /dev/null +++ b/apps/emqx_auth_mnesia/src/emqx_authn_scram_mnesia_schema.erl @@ -0,0 +1,68 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authn_scram_mnesia_schema). + +-include("emqx_auth_mnesia.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authn_schema). + +-export([ + fields/1, + desc/1, + refs/0, + select_union_member/1 +]). + +refs() -> + [?R_REF(scram)]. + +select_union_member(#{ + <<"mechanism">> := ?AUTHN_MECHANISM_SCRAM_BIN, <<"backend">> := ?AUTHN_BACKEND_BIN +}) -> + refs(); +select_union_member(#{<<"mechanism">> := ?AUTHN_MECHANISM_SCRAM_BIN}) -> + throw(#{ + reason => "unknown_backend", + expected => ?AUTHN_BACKEND + }); +select_union_member(_) -> + undefined. + +fields(scram) -> + [ + {mechanism, emqx_authn_schema:mechanism(?AUTHN_MECHANISM_SCRAM)}, + {backend, emqx_authn_schema:backend(?AUTHN_BACKEND)}, + {algorithm, fun algorithm/1}, + {iteration_count, fun iteration_count/1} + ] ++ emqx_authn_schema:common_fields(). + +desc(scram) -> + "Settings for Salted Challenge Response Authentication Mechanism\n" + "(SCRAM) authentication."; +desc(_) -> + undefined. + +algorithm(type) -> hoconsc:enum([sha256, sha512]); +algorithm(desc) -> "Hashing algorithm."; +algorithm(default) -> sha256; +algorithm(_) -> undefined. + +iteration_count(type) -> non_neg_integer(); +iteration_count(desc) -> "Iteration count."; +iteration_count(default) -> 4096; +iteration_count(_) -> undefined. diff --git a/apps/emqx_authz/src/emqx_authz_api_mnesia.erl b/apps/emqx_auth_mnesia/src/emqx_authz_api_mnesia.erl similarity index 99% rename from apps/emqx_authz/src/emqx_authz_api_mnesia.erl rename to apps/emqx_auth_mnesia/src/emqx_authz_api_mnesia.erl index a2a8f2525..e71b44add 100644 --- a/apps/emqx_authz/src/emqx_authz_api_mnesia.erl +++ b/apps/emqx_auth_mnesia/src/emqx_authz_api_mnesia.erl @@ -18,7 +18,7 @@ -behaviour(minirest_api). --include("emqx_authz.hrl"). +-include_lib("emqx_auth/include/emqx_authz.hrl"). -include_lib("emqx/include/logger.hrl"). -include_lib("hocon/include/hoconsc.hrl"). diff --git a/apps/emqx_authz/src/emqx_authz_mnesia.erl b/apps/emqx_auth_mnesia/src/emqx_authz_mnesia.erl similarity index 98% rename from apps/emqx_authz/src/emqx_authz_mnesia.erl rename to apps/emqx_auth_mnesia/src/emqx_authz_mnesia.erl index 2cecd0c71..401d5a494 100644 --- a/apps/emqx_authz/src/emqx_authz_mnesia.erl +++ b/apps/emqx_auth_mnesia/src/emqx_authz_mnesia.erl @@ -19,7 +19,7 @@ -include_lib("stdlib/include/ms_transform.hrl"). -include_lib("emqx/include/logger.hrl"). --include("emqx_authz.hrl"). +-include_lib("emqx_auth/include/emqx_authz.hrl"). -define(ACL_SHARDED, emqx_acl_sharded). @@ -40,7 +40,7 @@ rules :: rules() }). --behaviour(emqx_authz). +-behaviour(emqx_authz_source). -behaviour(emqx_db_backup). %% AuthZ Callbacks diff --git a/apps/emqx_auth_mnesia/src/emqx_authz_mnesia_schema.erl b/apps/emqx_auth_mnesia/src/emqx_authz_mnesia_schema.erl new file mode 100644 index 000000000..cab544bf7 --- /dev/null +++ b/apps/emqx_auth_mnesia/src/emqx_authz_mnesia_schema.erl @@ -0,0 +1,48 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authz_mnesia_schema). + +-include("emqx_auth_mnesia.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authz_schema). + +-export([ + type/0, + fields/1, + desc/1, + source_refs/0, + select_union_member/1 +]). + +type() -> ?AUTHZ_TYPE. + +fields(builtin_db) -> + emqx_authz_schema:authz_common_fields(?AUTHZ_TYPE). + +source_refs() -> + [?R_REF(builtin_db)]. + +desc(builtin_db) -> + ?DESC(builtin_db); +desc(_) -> + undefined. + +select_union_member(#{<<"type">> := ?AUTHZ_TYPE_BIN}) -> + ?R_REF(builtin_db); +select_union_member(_Value) -> + undefined. diff --git a/apps/emqx_auth_mnesia/test/emqx_authn_api_mnesia_SUITE.erl b/apps/emqx_auth_mnesia/test/emqx_authn_api_mnesia_SUITE.erl new file mode 100644 index 000000000..2035cf2fa --- /dev/null +++ b/apps/emqx_auth_mnesia/test/emqx_authn_api_mnesia_SUITE.erl @@ -0,0 +1,346 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2021-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authn_api_mnesia_SUITE). + +-compile(nowarn_export_all). +-compile(export_all). + +-import(emqx_dashboard_api_test_helpers, [multipart_formdata_request/3]). +-import(emqx_mgmt_api_test_util, [request/3, uri/1]). + +-include_lib("emqx_auth/include/emqx_authn.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). + +-define(TCP_DEFAULT, 'tcp:default'). + +all() -> + emqx_common_test_helpers:all(?MODULE). + +groups() -> + []. + +init_per_testcase(_Case, Config) -> + emqx_authn_test_lib:delete_authenticators( + [?CONF_NS_ATOM], + ?GLOBAL + ), + + emqx_authn_test_lib:delete_authenticators( + [listeners, tcp, default, ?CONF_NS_ATOM], + ?TCP_DEFAULT + ), + Config. + +end_per_testcase(_, Config) -> + Config. + +init_per_suite(Config) -> + Apps = emqx_cth_suite:start( + [ + emqx, + emqx_conf, + emqx_auth, + emqx_auth_mnesia, + emqx_management, + {emqx_dashboard, "dashboard.listeners.http { enable = true, bind = 18083 }"} + ], + #{ + work_dir => ?config(priv_dir, Config) + } + ), + _ = emqx_common_test_http:create_default_app(), + ?AUTHN:delete_chain(?GLOBAL), + {ok, Chains} = ?AUTHN:list_chains(), + ?assertEqual(length(Chains), 0), + [{apps, Apps} | Config]. + +end_per_suite(Config) -> + _ = emqx_common_test_http:delete_default_app(), + ok = emqx_cth_suite:stop(?config(apps, Config)), + ok. + +%%------------------------------------------------------------------------------ +%% Tests +%%------------------------------------------------------------------------------ + +t_authenticator_users(_) -> + test_authenticator_users([]). + +t_authenticator_user(_) -> + test_authenticator_user([]). + +t_authenticator_import_users(_) -> + test_authenticator_import_users([]). + +% t_listener_authenticator_users(_) -> +% test_authenticator_users(["listeners", ?TCP_DEFAULT]). + +% t_listener_authenticator_user(_) -> +% test_authenticator_user(["listeners", ?TCP_DEFAULT]). + +% t_listener_authenticator_import_users(_) -> +% test_authenticator_import_users(["listeners", ?TCP_DEFAULT]). + +test_authenticator_users(PathPrefix) -> + UsersUri = uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database", "users"]), + + {ok, 200, _} = request( + post, + uri(PathPrefix ++ [?CONF_NS]), + emqx_authn_test_lib:built_in_database_example() + ), + + {ok, Client} = emqtt:start_link( + [ + {username, <<"u_event">>}, + {clientid, <<"c_event">>}, + {proto_ver, v5}, + {properties, #{'Session-Expiry-Interval' => 60}} + ] + ), + + process_flag(trap_exit, true), + ?assertMatch({error, _}, emqtt:connect(Client)), + timer:sleep(300), + + UsersUri0 = uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database", "status"]), + {ok, 200, PageData0} = request(get, UsersUri0), + case PathPrefix of + [] -> + #{ + <<"metrics">> := #{ + <<"total">> := 1, + <<"success">> := 0, + <<"failed">> := 1 + } + } = emqx_utils_json:decode(PageData0, [return_maps]); + ["listeners", 'tcp:default'] -> + #{ + <<"metrics">> := #{ + <<"total">> := 1, + <<"success">> := 0, + <<"nomatch">> := 1 + } + } = emqx_utils_json:decode(PageData0, [return_maps]) + end, + + InvalidUsers = [ + #{clientid => <<"u1">>, password => <<"p1">>}, + #{user_id => <<"u2">>}, + #{user_id => <<"u3">>, password => <<"p3">>, foobar => <<"foobar">>} + ], + + lists:foreach( + fun(User) -> {ok, 400, _} = request(post, UsersUri, User) end, + InvalidUsers + ), + + ValidUsers = [ + #{user_id => <<"u1">>, password => <<"p1">>}, + #{user_id => <<"u2">>, password => <<"p2">>, is_superuser => true}, + #{user_id => <<"u3">>, password => <<"p3">>} + ], + + lists:foreach( + fun(User) -> + {ok, 201, UserData} = request(post, UsersUri, User), + CreatedUser = emqx_utils_json:decode(UserData, [return_maps]), + ?assertMatch(#{<<"user_id">> := _}, CreatedUser) + end, + ValidUsers + ), + + {ok, Client1} = emqtt:start_link( + [ + {username, <<"u1">>}, + {password, <<"p1">>}, + {clientid, <<"c_event">>}, + {proto_ver, v5}, + {properties, #{'Session-Expiry-Interval' => 60}} + ] + ), + {ok, _} = emqtt:connect(Client1), + timer:sleep(300), + UsersUri01 = uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database", "status"]), + {ok, 200, PageData01} = request(get, UsersUri01), + case PathPrefix of + [] -> + #{ + <<"metrics">> := #{ + <<"total">> := 2, + <<"success">> := 1, + <<"failed">> := 1 + } + } = emqx_utils_json:decode(PageData01, [return_maps]); + ["listeners", 'tcp:default'] -> + #{ + <<"metrics">> := #{ + <<"total">> := 2, + <<"success">> := 1, + <<"nomatch">> := 1 + } + } = emqx_utils_json:decode(PageData01, [return_maps]) + end, + + {ok, 200, Page1Data} = request(get, UsersUri ++ "?page=1&limit=2"), + + #{ + <<"data">> := Page1Users, + <<"meta">> := + #{ + <<"page">> := 1, + <<"limit">> := 2, + <<"count">> := 3 + } + } = + emqx_utils_json:decode(Page1Data, [return_maps]), + + {ok, 200, Page2Data} = request(get, UsersUri ++ "?page=2&limit=2"), + + #{ + <<"data">> := Page2Users, + <<"meta">> := + #{ + <<"page">> := 2, + <<"limit">> := 2, + <<"count">> := 3 + } + } = emqx_utils_json:decode(Page2Data, [return_maps]), + + ?assertEqual(2, length(Page1Users)), + ?assertEqual(1, length(Page2Users)), + + ?assertEqual( + [<<"u1">>, <<"u2">>, <<"u3">>], + lists:usort([UserId || #{<<"user_id">> := UserId} <- Page1Users ++ Page2Users]) + ), + + {ok, 200, Super1Data} = request(get, UsersUri ++ "?page=1&limit=3&is_superuser=true"), + + #{ + <<"data">> := Super1Users, + <<"meta">> := + #{ + <<"page">> := 1, + <<"limit">> := 3, + <<"count">> := 1 + } + } = emqx_utils_json:decode(Super1Data, [return_maps]), + + ?assertEqual( + [<<"u2">>], + lists:usort([UserId || #{<<"user_id">> := UserId} <- Super1Users]) + ), + + {ok, 200, Super2Data} = request(get, UsersUri ++ "?page=1&limit=3&is_superuser=false"), + + #{ + <<"data">> := Super2Users, + <<"meta">> := + #{ + <<"page">> := 1, + <<"limit">> := 3, + <<"count">> := 2 + } + } = emqx_utils_json:decode(Super2Data, [return_maps]), + + ?assertEqual( + [<<"u1">>, <<"u3">>], + lists:usort([UserId || #{<<"user_id">> := UserId} <- Super2Users]) + ), + + ok. + +test_authenticator_user(PathPrefix) -> + UsersUri = uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database", "users"]), + + {ok, 200, _} = request( + post, + uri(PathPrefix ++ [?CONF_NS]), + emqx_authn_test_lib:built_in_database_example() + ), + + User = #{user_id => <<"u1">>, password => <<"p1">>}, + {ok, 201, _} = request(post, UsersUri, User), + + {ok, 404, _} = request(get, UsersUri ++ "/u123"), + + {ok, 409, _} = request(post, UsersUri, User), + + {ok, 200, UserData} = request(get, UsersUri ++ "/u1"), + + FetchedUser = emqx_utils_json:decode(UserData, [return_maps]), + ?assertMatch(#{<<"user_id">> := <<"u1">>}, FetchedUser), + ?assertNotMatch(#{<<"password">> := _}, FetchedUser), + + ValidUserUpdates = [ + #{password => <<"p1">>}, + #{password => <<"p1">>, is_superuser => true} + ], + + lists:foreach( + fun(UserUpdate) -> {ok, 200, _} = request(put, UsersUri ++ "/u1", UserUpdate) end, + ValidUserUpdates + ), + + InvalidUserUpdates = [#{user_id => <<"u1">>, password => <<"p1">>}], + + lists:foreach( + fun(UserUpdate) -> {ok, 400, _} = request(put, UsersUri ++ "/u1", UserUpdate) end, + InvalidUserUpdates + ), + + {ok, 404, _} = request(delete, UsersUri ++ "/u123"), + {ok, 204, _} = request(delete, UsersUri ++ "/u1"). + +test_authenticator_import_users(PathPrefix) -> + ImportUri = uri( + PathPrefix ++ + [?CONF_NS, "password_based:built_in_database", "import_users"] + ), + + {ok, 200, _} = request( + post, + uri(PathPrefix ++ [?CONF_NS]), + emqx_authn_test_lib:built_in_database_example() + ), + + {ok, 400, _} = multipart_formdata_request(ImportUri, [], []), + {ok, 400, _} = multipart_formdata_request(ImportUri, [], [ + {filenam, "user-credentials.json", <<>>} + ]), + + Dir = code:lib_dir(emqx_auth, test), + JSONFileName = filename:join([Dir, <<"data/user-credentials.json">>]), + CSVFileName = filename:join([Dir, <<"data/user-credentials.csv">>]), + + {ok, JSONData} = file:read_file(JSONFileName), + {ok, 204, _} = multipart_formdata_request(ImportUri, [], [ + {filename, "user-credentials.json", JSONData} + ]), + + {ok, CSVData} = file:read_file(CSVFileName), + {ok, 204, _} = multipart_formdata_request(ImportUri, [], [ + {filename, "user-credentials.csv", CSVData} + ]). + +%%------------------------------------------------------------------------------ +%% Helpers +%%------------------------------------------------------------------------------ + +request(Method, Url) -> + request(Method, Url, []). diff --git a/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl b/apps/emqx_auth_mnesia/test/emqx_authn_mnesia_SUITE.erl similarity index 98% rename from apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl rename to apps/emqx_auth_mnesia/test/emqx_authn_mnesia_SUITE.erl index 9781b8ca7..80e6789d9 100644 --- a/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl +++ b/apps/emqx_auth_mnesia/test/emqx_authn_mnesia_SUITE.erl @@ -28,7 +28,7 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_auth, emqx_auth_mnesia], #{ work_dir => ?config(priv_dir, Config) }), [{apps, Apps} | Config]. @@ -258,7 +258,7 @@ t_import_users(_) -> %%------------------------------------------------------------------------------ sample_filename(Name) -> - Dir = code:lib_dir(emqx_authn, test), + Dir = code:lib_dir(emqx_auth, test), filename:join([Dir, <<"data">>, Name]). sample_filename_and_data(Name) -> diff --git a/apps/emqx_authn/test/emqx_authn_mqtt_test_client.erl b/apps/emqx_auth_mnesia/test/emqx_authn_mqtt_test_client.erl similarity index 100% rename from apps/emqx_authn/test/emqx_authn_mqtt_test_client.erl rename to apps/emqx_auth_mnesia/test/emqx_authn_mqtt_test_client.erl diff --git a/apps/emqx_authn/test/emqx_enhanced_authn_scram_mnesia_SUITE.erl b/apps/emqx_auth_mnesia/test/emqx_authn_scram_mnesia_SUITE.erl similarity index 79% rename from apps/emqx_authn/test/emqx_enhanced_authn_scram_mnesia_SUITE.erl rename to apps/emqx_auth_mnesia/test/emqx_authn_scram_mnesia_SUITE.erl index baaf15175..abd5518a6 100644 --- a/apps/emqx_authn/test/emqx_enhanced_authn_scram_mnesia_SUITE.erl +++ b/apps/emqx_auth_mnesia/test/emqx_authn_scram_mnesia_SUITE.erl @@ -14,7 +14,7 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_enhanced_authn_scram_mnesia_SUITE). +-module(emqx_authn_scram_mnesia_SUITE). -compile(export_all). -compile(nowarn_export_all). @@ -23,7 +23,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("emqx/include/emqx_mqtt.hrl"). --include("emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -define(PATH, [authentication]). @@ -36,7 +36,7 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_auth, emqx_auth_mnesia], #{ work_dir => ?config(priv_dir, Config) }), IdleTimeout = emqx_config:get([mqtt, idle_timeout]), @@ -48,7 +48,7 @@ end_per_suite(Config) -> ok. init_per_testcase(_Case, Config) -> - mria:clear_table(emqx_enhanced_authn_scram_mnesia), + mria:clear_table(emqx_authn_scram_mnesia), emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL @@ -75,8 +75,8 @@ t_create(_Config) -> {create_authenticator, ?GLOBAL, ValidConfig} ), - {ok, [#{provider := emqx_enhanced_authn_scram_mnesia}]} = - emqx_authentication:list_authenticators(?GLOBAL). + {ok, [#{provider := emqx_authn_scram_mnesia}]} = + emqx_authn_chains:list_authenticators(?GLOBAL). t_create_invalid(_Config) -> InvalidConfig = #{ @@ -93,7 +93,7 @@ t_create_invalid(_Config) -> ?assertEqual( {error, {not_found, {chain, ?GLOBAL}}}, - emqx_authentication:list_authenticators(?GLOBAL) + emqx_authn_chains:list_authenticators(?GLOBAL) ). t_authenticate(_Config) -> @@ -259,64 +259,64 @@ t_authenticate_bad_password(_Config) -> t_destroy(_) -> Config = config(), OtherId = list_to_binary([<<"id-other">>]), - {ok, State0} = emqx_enhanced_authn_scram_mnesia:create(<<"id">>, Config), - {ok, StateOther} = emqx_enhanced_authn_scram_mnesia:create(OtherId, Config), + {ok, State0} = emqx_authn_scram_mnesia:create(<<"id">>, Config), + {ok, StateOther} = emqx_authn_scram_mnesia:create(OtherId, Config), User = #{user_id => <<"u">>, password => <<"p">>}, - {ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(User, State0), - {ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(User, StateOther), + {ok, _} = emqx_authn_scram_mnesia:add_user(User, State0), + {ok, _} = emqx_authn_scram_mnesia:add_user(User, StateOther), - {ok, _} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, State0), - {ok, _} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, StateOther), + {ok, _} = emqx_authn_scram_mnesia:lookup_user(<<"u">>, State0), + {ok, _} = emqx_authn_scram_mnesia:lookup_user(<<"u">>, StateOther), - ok = emqx_enhanced_authn_scram_mnesia:destroy(State0), + ok = emqx_authn_scram_mnesia:destroy(State0), - {ok, State1} = emqx_enhanced_authn_scram_mnesia:create(<<"id">>, Config), - {error, not_found} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, State1), - {ok, _} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, StateOther). + {ok, State1} = emqx_authn_scram_mnesia:create(<<"id">>, Config), + {error, not_found} = emqx_authn_scram_mnesia:lookup_user(<<"u">>, State1), + {ok, _} = emqx_authn_scram_mnesia:lookup_user(<<"u">>, StateOther). t_add_user(_) -> Config = config(), - {ok, State} = emqx_enhanced_authn_scram_mnesia:create(<<"id">>, Config), + {ok, State} = emqx_authn_scram_mnesia:create(<<"id">>, Config), User = #{user_id => <<"u">>, password => <<"p">>}, - {ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(User, State), - {error, already_exist} = emqx_enhanced_authn_scram_mnesia:add_user(User, State). + {ok, _} = emqx_authn_scram_mnesia:add_user(User, State), + {error, already_exist} = emqx_authn_scram_mnesia:add_user(User, State). t_delete_user(_) -> Config = config(), - {ok, State} = emqx_enhanced_authn_scram_mnesia:create(<<"id">>, Config), + {ok, State} = emqx_authn_scram_mnesia:create(<<"id">>, Config), - {error, not_found} = emqx_enhanced_authn_scram_mnesia:delete_user(<<"u">>, State), + {error, not_found} = emqx_authn_scram_mnesia:delete_user(<<"u">>, State), User = #{user_id => <<"u">>, password => <<"p">>}, - {ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(User, State), + {ok, _} = emqx_authn_scram_mnesia:add_user(User, State), - ok = emqx_enhanced_authn_scram_mnesia:delete_user(<<"u">>, State), - {error, not_found} = emqx_enhanced_authn_scram_mnesia:delete_user(<<"u">>, State). + ok = emqx_authn_scram_mnesia:delete_user(<<"u">>, State), + {error, not_found} = emqx_authn_scram_mnesia:delete_user(<<"u">>, State). t_update_user(_) -> Config = config(), - {ok, State} = emqx_enhanced_authn_scram_mnesia:create(<<"id">>, Config), + {ok, State} = emqx_authn_scram_mnesia:create(<<"id">>, Config), User = #{user_id => <<"u">>, password => <<"p">>}, - {ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(User, State), - {ok, #{is_superuser := false}} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, State), + {ok, _} = emqx_authn_scram_mnesia:add_user(User, State), + {ok, #{is_superuser := false}} = emqx_authn_scram_mnesia:lookup_user(<<"u">>, State), {ok, #{ user_id := <<"u">>, is_superuser := true - }} = emqx_enhanced_authn_scram_mnesia:update_user( + }} = emqx_authn_scram_mnesia:update_user( <<"u">>, #{password => <<"p1">>, is_superuser => true}, State ), - {ok, #{is_superuser := true}} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, State). + {ok, #{is_superuser := true}} = emqx_authn_scram_mnesia:lookup_user(<<"u">>, State). t_list_users(_) -> Config = config(), - {ok, State} = emqx_enhanced_authn_scram_mnesia:create(<<"id">>, Config), + {ok, State} = emqx_authn_scram_mnesia:create(<<"id">>, Config), Users = [ #{user_id => <<"u1">>, password => <<"p">>}, @@ -325,21 +325,21 @@ t_list_users(_) -> ], lists:foreach( - fun(U) -> {ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(U, State) end, + fun(U) -> {ok, _} = emqx_authn_scram_mnesia:add_user(U, State) end, Users ), #{ data := [?USER_MAP, ?USER_MAP], meta := #{page := 1, limit := 2, count := 3, hasnext := true} - } = emqx_enhanced_authn_scram_mnesia:list_users( + } = emqx_authn_scram_mnesia:list_users( #{<<"page">> => 1, <<"limit">> => 2}, State ), #{ data := [?USER_MAP], meta := #{page := 2, limit := 2, count := 3, hasnext := false} - } = emqx_enhanced_authn_scram_mnesia:list_users( + } = emqx_authn_scram_mnesia:list_users( #{<<"page">> => 2, <<"limit">> => 2}, State ), @@ -351,7 +351,7 @@ t_list_users(_) -> } ], meta := #{page := 1, limit := 3, hasnext := false} - } = emqx_enhanced_authn_scram_mnesia:list_users( + } = emqx_authn_scram_mnesia:list_users( #{ <<"page">> => 1, <<"limit">> => 3, @@ -367,7 +367,7 @@ t_is_superuser(_Config) -> test_is_superuser(UserInfo, ExpectedIsSuperuser) -> Config = config(), - {ok, State} = emqx_enhanced_authn_scram_mnesia:create(<<"id">>, Config), + {ok, State} = emqx_authn_scram_mnesia:create(<<"id">>, Config), Username = <<"u">>, Password = <<"p">>, @@ -377,12 +377,12 @@ test_is_superuser(UserInfo, ExpectedIsSuperuser) -> password => Password }, - {ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(UserInfo0, State), + {ok, _} = emqx_authn_scram_mnesia:add_user(UserInfo0, State), ClientFirstMessage = esasl_scram:client_first_message(Username), {continue, ServerFirstMessage, ServerCache} = - emqx_enhanced_authn_scram_mnesia:authenticate( + emqx_authn_scram_mnesia:authenticate( #{ auth_method => <<"SCRAM-SHA-512">>, auth_data => ClientFirstMessage, @@ -402,7 +402,7 @@ test_is_superuser(UserInfo, ExpectedIsSuperuser) -> ), {ok, UserInfo1, ServerFinalMessage} = - emqx_enhanced_authn_scram_mnesia:authenticate( + emqx_authn_scram_mnesia:authenticate( #{ auth_method => <<"SCRAM-SHA-512">>, auth_data => ClientFinalMessage, @@ -417,7 +417,7 @@ test_is_superuser(UserInfo, ExpectedIsSuperuser) -> ?assertMatch(#{is_superuser := ExpectedIsSuperuser}, UserInfo1), - ok = emqx_enhanced_authn_scram_mnesia:destroy(State). + ok = emqx_authn_scram_mnesia:destroy(State). %%------------------------------------------------------------------------------ %% Helpers @@ -447,9 +447,9 @@ init_auth(Username, Password, Algorithm) -> {create_authenticator, ?GLOBAL, Config} ), - {ok, [#{state := State}]} = emqx_authentication:list_authenticators(?GLOBAL), + {ok, [#{state := State}]} = emqx_authn_chains:list_authenticators(?GLOBAL), - emqx_enhanced_authn_scram_mnesia:add_user( + emqx_authn_scram_mnesia:add_user( #{user_id => Username, password => Password}, State ). diff --git a/apps/emqx_authz/test/emqx_authz_api_mnesia_SUITE.erl b/apps/emqx_auth_mnesia/test/emqx_authz_api_mnesia_SUITE.erl similarity index 91% rename from apps/emqx_authz/test/emqx_authz_api_mnesia_SUITE.erl rename to apps/emqx_auth_mnesia/test/emqx_authz_api_mnesia_SUITE.erl index 7f03a38a2..e4b96b08b 100644 --- a/apps/emqx_authz/test/emqx_authz_api_mnesia_SUITE.erl +++ b/apps/emqx_auth_mnesia/test/emqx_authz_api_mnesia_SUITE.erl @@ -18,7 +18,7 @@ -compile(nowarn_export_all). -compile(export_all). --include("emqx_authz.hrl"). +-include_lib("emqx_auth/include/emqx_authz.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). @@ -31,35 +31,27 @@ groups() -> []. init_per_suite(Config) -> - ok = emqx_mgmt_api_test_util:init_suite( - [emqx_conf, emqx_authz], - fun set_special_configs/1 - ), - Config. - -end_per_suite(_Config) -> - {ok, _} = emqx:update_config( - [authorization], + Apps = emqx_cth_suite:start( + [ + {emqx_conf, + "authorization.cache { enable = false }," + "authorization.no_match = deny," + "authorization.sources = [{type = built_in_database}]"}, + emqx, + emqx_auth, + emqx_auth_mnesia, + emqx_management, + {emqx_dashboard, "dashboard.listeners.http { enable = true, bind = 18083 }"} + ], #{ - <<"no_match">> => <<"allow">>, - <<"cache">> => #{<<"enable">> => <<"true">>}, - <<"sources">> => [] + work_dir => filename:join(?config(priv_dir, Config), ?MODULE) } ), - emqx_mgmt_api_test_util:end_suite([emqx_authz, emqx_conf]), - ok. - -set_special_configs(emqx_dashboard) -> - emqx_dashboard_api_test_helpers:set_default_config(); -set_special_configs(emqx_authz) -> - {ok, _} = emqx:update_config([authorization, cache, enable], false), - {ok, _} = emqx:update_config([authorization, no_match], deny), - {ok, _} = emqx:update_config( - [authorization, sources], - [#{<<"type">> => <<"built_in_database">>}] - ), - ok; -set_special_configs(_App) -> + _ = emqx_common_test_http:create_default_app(), + [{suite_apps, Apps} | Config]. +end_per_suite(_Config) -> + ok = emqx_cth_suite:stop(?config(suite_apps, _Config)), + _ = emqx_common_test_http:delete_default_app(), ok. %%------------------------------------------------------------------------------ diff --git a/apps/emqx_authz/test/emqx_authz_mnesia_SUITE.erl b/apps/emqx_auth_mnesia/test/emqx_authz_mnesia_SUITE.erl similarity index 95% rename from apps/emqx_authz/test/emqx_authz_mnesia_SUITE.erl rename to apps/emqx_auth_mnesia/test/emqx_authz_mnesia_SUITE.erl index c82bbd56e..8f4f92ea2 100644 --- a/apps/emqx_authz/test/emqx_authz_mnesia_SUITE.erl +++ b/apps/emqx_auth_mnesia/test/emqx_authz_mnesia_SUITE.erl @@ -30,15 +30,20 @@ groups() -> []. init_per_suite(Config) -> - ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1 + Apps = emqx_cth_suite:start( + [ + {emqx_conf, "authorization.no_match = deny, authorization.cache.enable = false"}, + emqx, + emqx_auth, + emqx_auth_mnesia + ], + #{work_dir => ?config(priv_dir, Config)} ), - Config. + [{suite_apps, Apps} | Config]. end_per_suite(_Config) -> ok = emqx_authz_test_lib:restore_authorizers(), - ok = emqx_common_test_helpers:stop_apps([emqx_conf, emqx_authz]). + emqx_cth_suite:stop(?config(suite_apps, _Config)). init_per_testcase(_TestCase, Config) -> ok = emqx_authz_test_lib:reset_authorizers(), @@ -49,11 +54,6 @@ end_per_testcase(_TestCase, _Config) -> _ = emqx_authz:set_feature_available(rich_actions, true), ok = emqx_authz_mnesia:purge_rules(). -set_special_configs(emqx_authz) -> - ok = emqx_authz_test_lib:reset_authorizers(); -set_special_configs(_) -> - ok. - %%------------------------------------------------------------------------------ %% Testcases %%------------------------------------------------------------------------------ diff --git a/apps/emqx_auth_mongodb/docker-ct b/apps/emqx_auth_mongodb/docker-ct new file mode 100644 index 000000000..b9c3499d5 --- /dev/null +++ b/apps/emqx_auth_mongodb/docker-ct @@ -0,0 +1 @@ +mongo diff --git a/apps/emqx_auth_mongodb/include/emqx_auth_mongodb.hrl b/apps/emqx_auth_mongodb/include/emqx_auth_mongodb.hrl new file mode 100644 index 000000000..c72e9747e --- /dev/null +++ b/apps/emqx_auth_mongodb/include/emqx_auth_mongodb.hrl @@ -0,0 +1,31 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-ifndef(EMQX_AUTH_MONGODB_HRL). +-define(EMQX_AUTH_MONGODB_HRL, true). + +-define(AUTHZ_TYPE, mongodb). +-define(AUTHZ_TYPE_BIN, <<"mongodb">>). + +-define(AUTHN_MECHANISM, password_based). +-define(AUTHN_MECHANISM_BIN, <<"password_based">>). + +-define(AUTHN_BACKEND, mongodb). +-define(AUTHN_BACKEND_BIN, <<"mongodb">>). + +-define(AUTHN_TYPE, {?AUTHN_MECHANISM, ?AUTHN_BACKEND}). + +-endif. diff --git a/apps/emqx_auth_mongodb/rebar.config b/apps/emqx_auth_mongodb/rebar.config new file mode 100644 index 000000000..db2424602 --- /dev/null +++ b/apps/emqx_auth_mongodb/rebar.config @@ -0,0 +1,6 @@ +%% -*- mode: erlang -*- +{deps, [ + {emqx, {path, "../emqx"}}, + {emqx_utils, {path, "../emqx_utils"}}, + {emqx_auth, {path, "../emqx_auth"}} +]}. diff --git a/apps/emqx_auth_mongodb/src/emqx_auth_mongodb.app.src b/apps/emqx_auth_mongodb/src/emqx_auth_mongodb.app.src new file mode 100644 index 000000000..38cf0138f --- /dev/null +++ b/apps/emqx_auth_mongodb/src/emqx_auth_mongodb.app.src @@ -0,0 +1,19 @@ +%% -*- mode: erlang -*- +{application, emqx_auth_mongodb, [ + {description, "EMQX MongoDB Authentication and Authorization"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, {emqx_auth_mongodb_app, []}}, + {applications, [ + kernel, + stdlib, + emqx, + emqx_auth, + emqx_mongodb + ]}, + {env, []}, + {modules, []}, + + {licenses, ["Apache 2.0"]}, + {links, []} +]}. diff --git a/apps/emqx_auth_mongodb/src/emqx_auth_mongodb_app.erl b/apps/emqx_auth_mongodb/src/emqx_auth_mongodb_app.erl new file mode 100644 index 000000000..df1c8c560 --- /dev/null +++ b/apps/emqx_auth_mongodb/src/emqx_auth_mongodb_app.erl @@ -0,0 +1,34 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_mongodb_app). + +-include("emqx_auth_mongodb.hrl"). + +-behaviour(application). + +-export([start/2, stop/1]). + +start(_StartType, _StartArgs) -> + ok = emqx_authz:register_source(?AUTHZ_TYPE, emqx_authz_mongodb), + ok = emqx_authn:register_provider(?AUTHN_TYPE, emqx_authn_mongodb), + {ok, Sup} = emqx_auth_mongodb_sup:start_link(), + {ok, Sup}. + +stop(_State) -> + ok = emqx_authz:unregister_source(?AUTHZ_TYPE), + ok = emqx_authn:deregister_provider(?AUTHN_TYPE), + ok. diff --git a/apps/emqx_auth_mongodb/src/emqx_auth_mongodb_sup.erl b/apps/emqx_auth_mongodb/src/emqx_auth_mongodb_sup.erl new file mode 100644 index 000000000..6e22cfe0a --- /dev/null +++ b/apps/emqx_auth_mongodb/src/emqx_auth_mongodb_sup.erl @@ -0,0 +1,37 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_mongodb_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(SERVER, ?MODULE). + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +init([]) -> + SupFlags = #{ + strategy => one_for_all, + intensity => 0, + period => 1 + }, + ChildSpecs = [], + {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl b/apps/emqx_auth_mongodb/src/emqx_authn_mongodb.erl similarity index 62% rename from apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl rename to apps/emqx_auth_mongodb/src/emqx_authn_mongodb.erl index 9cbd1f2dc..d559d5d9c 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl +++ b/apps/emqx_auth_mongodb/src/emqx_authn_mongodb.erl @@ -16,122 +16,20 @@ -module(emqx_authn_mongodb). --include("emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("emqx/include/logger.hrl"). --include_lib("hocon/include/hoconsc.hrl"). - --behaviour(hocon_schema). --behaviour(emqx_authentication). -export([ - namespace/0, - tags/0, - roots/0, - fields/1, - desc/1 -]). - --export([ - refs/0, - union_member_selector/1, create/2, update/2, authenticate/2, destroy/1 ]). -%%------------------------------------------------------------------------------ -%% Hocon Schema -%%------------------------------------------------------------------------------ - -namespace() -> "authn". - -tags() -> - [<<"Authentication">>]. - -%% used for config check when the schema module is resolved -roots() -> - [ - {?CONF_NS, - hoconsc:mk( - hoconsc:union(fun ?MODULE:union_member_selector/1), - #{} - )} - ]. - -fields(mongo_single) -> - common_fields() ++ emqx_mongodb:fields(single); -fields(mongo_rs) -> - common_fields() ++ emqx_mongodb:fields(rs); -fields(mongo_sharded) -> - common_fields() ++ emqx_mongodb:fields(sharded). - -desc(mongo_single) -> - ?DESC(single); -desc(mongo_rs) -> - ?DESC('replica-set'); -desc(mongo_sharded) -> - ?DESC('sharded-cluster'); -desc(_) -> - undefined. - -common_fields() -> - [ - {mechanism, emqx_authn_schema:mechanism(password_based)}, - {backend, emqx_authn_schema:backend(mongodb)}, - {collection, fun collection/1}, - {filter, fun filter/1}, - {password_hash_field, fun password_hash_field/1}, - {salt_field, fun salt_field/1}, - {is_superuser_field, fun is_superuser_field/1}, - {password_hash_algorithm, fun emqx_authn_password_hashing:type_ro/1} - ] ++ emqx_authn_schema:common_fields(). - -collection(type) -> binary(); -collection(desc) -> ?DESC(?FUNCTION_NAME); -collection(required) -> true; -collection(_) -> undefined. - -filter(type) -> - map(); -filter(desc) -> - ?DESC(?FUNCTION_NAME); -filter(required) -> - false; -filter(default) -> - #{}; -filter(_) -> - undefined. - -password_hash_field(type) -> binary(); -password_hash_field(desc) -> ?DESC(?FUNCTION_NAME); -password_hash_field(required) -> false; -password_hash_field(default) -> <<"password_hash">>; -password_hash_field(_) -> undefined. - -salt_field(type) -> binary(); -salt_field(desc) -> ?DESC(?FUNCTION_NAME); -salt_field(required) -> false; -salt_field(default) -> <<"salt">>; -salt_field(_) -> undefined. - -is_superuser_field(type) -> binary(); -is_superuser_field(desc) -> ?DESC(?FUNCTION_NAME); -is_superuser_field(required) -> false; -is_superuser_field(default) -> <<"is_superuser">>; -is_superuser_field(_) -> undefined. - %%------------------------------------------------------------------------------ %% APIs %%------------------------------------------------------------------------------ -refs() -> - [ - hoconsc:ref(?MODULE, mongo_single), - hoconsc:ref(?MODULE, mongo_rs), - hoconsc:ref(?MODULE, mongo_sharded) - ]. - create(_AuthenticatorID, Config) -> create(Config). @@ -250,20 +148,3 @@ is_superuser(Doc, #{is_superuser_field := IsSuperuserField}) -> emqx_authn_utils:is_superuser(#{<<"is_superuser">> => IsSuperuser}); is_superuser(_, _) -> emqx_authn_utils:is_superuser(#{<<"is_superuser">> => false}). - -union_member_selector(all_union_members) -> - refs(); -union_member_selector({value, Value}) -> - refs(Value). - -refs(#{<<"mongo_type">> := <<"single">>}) -> - [hoconsc:ref(?MODULE, mongo_single)]; -refs(#{<<"mongo_type">> := <<"rs">>}) -> - [hoconsc:ref(?MODULE, mongo_rs)]; -refs(#{<<"mongo_type">> := <<"sharded">>}) -> - [hoconsc:ref(?MODULE, mongo_sharded)]; -refs(_) -> - throw(#{ - field_name => mongo_type, - expected => "single | rs | sharded" - }). diff --git a/apps/emqx_auth_mongodb/src/emqx_authn_mongodb_schema.erl b/apps/emqx_auth_mongodb/src/emqx_authn_mongodb_schema.erl new file mode 100644 index 000000000..8f76bedc2 --- /dev/null +++ b/apps/emqx_auth_mongodb/src/emqx_authn_mongodb_schema.erl @@ -0,0 +1,124 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authn_mongodb_schema). + +-include("emqx_auth_mongodb.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authn_schema). + +-export([ + fields/1, + desc/1, + refs/0, + select_union_member/1 +]). + +refs() -> + [ + ?R_REF(mongo_single), + ?R_REF(mongo_rs), + ?R_REF(mongo_sharded) + ]. + +select_union_member( + #{ + <<"mechanism">> := ?AUTHN_MECHANISM_BIN, <<"backend">> := ?AUTHN_BACKEND_BIN + } = Value +) -> + refs(Value); +select_union_member(#{<<"backend">> := ?AUTHN_BACKEND_BIN}) -> + throw(#{ + reason => "unknown_mechanism", + expected => ?AUTHN_MECHANISM + }); +select_union_member(_) -> + undefined. + +refs(#{<<"mongo_type">> := <<"single">>}) -> + [?R_REF(mongo_single)]; +refs(#{<<"mongo_type">> := <<"rs">>}) -> + [?R_REF(mongo_rs)]; +refs(#{<<"mongo_type">> := <<"sharded">>}) -> + [?R_REF(mongo_sharded)]; +refs(_) -> + throw(#{ + field_name => mongo_type, + expected => "single | rs | sharded" + }). + +fields(mongo_single) -> + common_fields() ++ emqx_mongodb:fields(single); +fields(mongo_rs) -> + common_fields() ++ emqx_mongodb:fields(rs); +fields(mongo_sharded) -> + common_fields() ++ emqx_mongodb:fields(sharded). + +desc(mongo_single) -> + ?DESC(single); +desc(mongo_rs) -> + ?DESC('replica-set'); +desc(mongo_sharded) -> + ?DESC('sharded-cluster'); +desc(_) -> + undefined. + +common_fields() -> + [ + {mechanism, emqx_authn_schema:mechanism(password_based)}, + {backend, emqx_authn_schema:backend(mongodb)}, + {collection, fun collection/1}, + {filter, fun filter/1}, + {password_hash_field, fun password_hash_field/1}, + {salt_field, fun salt_field/1}, + {is_superuser_field, fun is_superuser_field/1}, + {password_hash_algorithm, fun emqx_authn_password_hashing:type_ro/1} + ] ++ emqx_authn_schema:common_fields(). + +collection(type) -> binary(); +collection(desc) -> ?DESC(?FUNCTION_NAME); +collection(required) -> true; +collection(_) -> undefined. + +filter(type) -> + map(); +filter(desc) -> + ?DESC(?FUNCTION_NAME); +filter(required) -> + false; +filter(default) -> + #{}; +filter(_) -> + undefined. + +password_hash_field(type) -> binary(); +password_hash_field(desc) -> ?DESC(?FUNCTION_NAME); +password_hash_field(required) -> false; +password_hash_field(default) -> <<"password_hash">>; +password_hash_field(_) -> undefined. + +salt_field(type) -> binary(); +salt_field(desc) -> ?DESC(?FUNCTION_NAME); +salt_field(required) -> false; +salt_field(default) -> <<"salt">>; +salt_field(_) -> undefined. + +is_superuser_field(type) -> binary(); +is_superuser_field(desc) -> ?DESC(?FUNCTION_NAME); +is_superuser_field(required) -> false; +is_superuser_field(default) -> <<"is_superuser">>; +is_superuser_field(_) -> undefined. diff --git a/apps/emqx_authz/src/emqx_authz_mongodb.erl b/apps/emqx_auth_mongodb/src/emqx_authz_mongodb.erl similarity index 96% rename from apps/emqx_authz/src/emqx_authz_mongodb.erl rename to apps/emqx_auth_mongodb/src/emqx_authz_mongodb.erl index 52a920d3a..3b235ad2c 100644 --- a/apps/emqx_authz/src/emqx_authz_mongodb.erl +++ b/apps/emqx_auth_mongodb/src/emqx_authz_mongodb.erl @@ -16,12 +16,10 @@ -module(emqx_authz_mongodb). --include("emqx_authz.hrl"). --include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl"). --behaviour(emqx_authz). +-behaviour(emqx_authz_source). %% AuthZ Callbacks -export([ @@ -64,7 +62,7 @@ update(#{filter := Filter} = Source) -> end. destroy(#{annotations := #{id := Id}}) -> - ok = emqx_resource:remove_local(Id). + emqx_authz_utils:remove_resource(Id). authorize( Client, diff --git a/apps/emqx_auth_mongodb/src/emqx_authz_mongodb_schema.erl b/apps/emqx_auth_mongodb/src/emqx_authz_mongodb_schema.erl new file mode 100644 index 000000000..aff399e68 --- /dev/null +++ b/apps/emqx_auth_mongodb/src/emqx_authz_mongodb_schema.erl @@ -0,0 +1,93 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authz_mongodb_schema). + +-include("emqx_auth_mongodb.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-export([ + type/0, + fields/1, + desc/1, + source_refs/0, + select_union_member/1 +]). + +type() -> ?AUTHZ_TYPE. + +source_refs() -> + [?R_REF(mongo_single), ?R_REF(mongo_rs), ?R_REF(mongo_sharded)]. + +fields(mongo_single) -> + emqx_authz_schema:authz_common_fields(?AUTHZ_TYPE) ++ + mongo_common_fields() ++ + emqx_mongodb:fields(single); +fields(mongo_rs) -> + emqx_authz_schema:authz_common_fields(?AUTHZ_TYPE) ++ + mongo_common_fields() ++ + emqx_mongodb:fields(rs); +fields(mongo_sharded) -> + emqx_authz_schema:authz_common_fields(?AUTHZ_TYPE) ++ + mongo_common_fields() ++ + emqx_mongodb:fields(sharded). + +desc(mongo_single) -> + ?DESC(mongo_single); +desc(mongo_rs) -> + ?DESC(mongo_rs); +desc(mongo_sharded) -> + ?DESC(mongo_sharded); +desc(_) -> + undefined. + +select_union_member(#{<<"type">> := ?AUTHZ_TYPE_BIN} = Value) -> + MongoType = maps:get(<<"mongo_type">>, Value, undefined), + case MongoType of + <<"single">> -> + ?R_REF(mongo_single); + <<"rs">> -> + ?R_REF(mongo_rs); + <<"sharded">> -> + ?R_REF(mongo_sharded); + Else -> + throw(#{ + reason => "unknown_mongo_type", + expected => "single | rs | sharded", + got => Else + }) + end; +select_union_member(_Value) -> + undefined. + +%%-------------------------------------------------------------------- +%% Internal Functions +%%-------------------------------------------------------------------- + +mongo_common_fields() -> + [ + {collection, + ?HOCON(binary(), #{ + required => true, + desc => ?DESC(collection) + })}, + {filter, + ?HOCON(map(), #{ + required => false, + default => #{}, + desc => ?DESC(filter) + })} + ]. diff --git a/apps/emqx_authn/test/emqx_authn_mongo_SUITE.erl b/apps/emqx_auth_mongodb/test/emqx_authn_mongodb_SUITE.erl similarity index 97% rename from apps/emqx_authn/test/emqx_authn_mongo_SUITE.erl rename to apps/emqx_auth_mongodb/test/emqx_authn_mongodb_SUITE.erl index 9ea7f9eb2..c6623c11f 100644 --- a/apps/emqx_authn/test/emqx_authn_mongo_SUITE.erl +++ b/apps/emqx_auth_mongodb/test/emqx_authn_mongodb_SUITE.erl @@ -14,13 +14,13 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_authn_mongo_SUITE). +-module(emqx_authn_mongodb_SUITE). -compile(nowarn_export_all). -compile(export_all). -include_lib("emqx_connector/include/emqx_connector.hrl"). --include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). @@ -33,7 +33,6 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_testcase(_TestCase, Config) -> - emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL @@ -47,7 +46,7 @@ end_per_testcase(_TestCase, _Config) -> init_per_suite(Config) -> case emqx_common_test_helpers:is_tcp_server_available(?MONGO_HOST, ?MONGO_DEFAULT_PORT) of true -> - Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_auth, emqx_auth_mongodb], #{ work_dir => ?config(priv_dir, Config) }), [{apps, Apps} | Config]; @@ -75,7 +74,7 @@ t_create(_Config) -> {create_authenticator, ?GLOBAL, AuthConfig} ), - {ok, [#{provider := emqx_authn_mongodb}]} = emqx_authentication:list_authenticators(?GLOBAL). + {ok, [#{provider := emqx_authn_mongodb}]} = emqx_authn_chains:list_authenticators(?GLOBAL). t_create_invalid(_Config) -> AuthConfig = raw_mongo_auth_config(), @@ -96,7 +95,7 @@ t_create_invalid(_Config) -> ?assertEqual( {error, {not_found, {chain, ?GLOBAL}}}, - emqx_authentication:list_authenticators(?GLOBAL) + emqx_authn_chains:list_authenticators(?GLOBAL) ) end, InvalidConfigs @@ -146,7 +145,7 @@ t_destroy(_Config) -> ), {ok, [#{provider := emqx_authn_mongodb, state := State}]} = - emqx_authentication:list_authenticators(?GLOBAL), + emqx_authn_chains:list_authenticators(?GLOBAL), {ok, _} = emqx_authn_mongodb:authenticate( #{ diff --git a/apps/emqx_authn/test/emqx_authn_mongo_tls_SUITE.erl b/apps/emqx_auth_mongodb/test/emqx_authn_mongodb_tls_SUITE.erl similarity index 97% rename from apps/emqx_authn/test/emqx_authn_mongo_tls_SUITE.erl rename to apps/emqx_auth_mongodb/test/emqx_authn_mongodb_tls_SUITE.erl index af550379b..0aa645abc 100644 --- a/apps/emqx_authn/test/emqx_authn_mongo_tls_SUITE.erl +++ b/apps/emqx_auth_mongodb/test/emqx_authn_mongodb_tls_SUITE.erl @@ -14,13 +14,13 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_authn_mongo_tls_SUITE). +-module(emqx_authn_mongodb_tls_SUITE). -compile(nowarn_export_all). -compile(export_all). -include_lib("emqx_connector/include/emqx_connector.hrl"). --include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl"). @@ -33,7 +33,6 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_testcase(_TestCase, Config) -> - emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL @@ -43,7 +42,7 @@ init_per_testcase(_TestCase, Config) -> init_per_suite(Config) -> case emqx_common_test_helpers:is_tcp_server_available(?MONGO_HOST, ?MONGO_DEFAULT_PORT) of true -> - Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_auth, emqx_auth_mongodb], #{ work_dir => ?config(priv_dir, Config) }), [{apps, Apps} | Config]; diff --git a/apps/emqx_authz/test/emqx_authz_mongodb_SUITE.erl b/apps/emqx_auth_mongodb/test/emqx_authz_mongodb_SUITE.erl similarity index 96% rename from apps/emqx_authz/test/emqx_authz_mongodb_SUITE.erl rename to apps/emqx_auth_mongodb/test/emqx_authz_mongodb_SUITE.erl index 4476deda2..c57dce860 100644 --- a/apps/emqx_authz/test/emqx_authz_mongodb_SUITE.erl +++ b/apps/emqx_auth_mongodb/test/emqx_authz_mongodb_SUITE.erl @@ -18,7 +18,7 @@ -compile(nowarn_export_all). -compile(export_all). --include("emqx_authz.hrl"). +-include_lib("emqx_auth/include/emqx_authz.hrl"). -include_lib("emqx_connector/include/emqx_connector.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). @@ -34,28 +34,26 @@ groups() -> emqx_authz_test_lib:table_groups(t_run_case, cases()). init_per_suite(Config) -> - ok = stop_apps([emqx_resource]), case emqx_common_test_helpers:is_tcp_server_available(?MONGO_HOST, ?MONGO_DEFAULT_PORT) of true -> - ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1 + Apps = emqx_cth_suite:start( + [ + emqx, + {emqx_conf, + "authorization.no_match = deny, authorization.cache.enable = false"}, + emqx_auth, + emqx_auth_mongodb + ], + #{work_dir => ?config(priv_dir, Config)} ), - ok = start_apps([emqx_resource]), - Config; + [{suite_apps, Apps} | Config]; false -> {skip, no_mongo} end. -end_per_suite(_Config) -> +end_per_suite(Config) -> ok = emqx_authz_test_lib:restore_authorizers(), - ok = stop_apps([emqx_resource]), - ok = emqx_common_test_helpers:stop_apps([emqx_conf, emqx_authz]). - -set_special_configs(emqx_authz) -> - ok = emqx_authz_test_lib:reset_authorizers(); -set_special_configs(_) -> - ok. + emqx_cth_suite:stop(?config(suite_apps, Config)). init_per_group(Group, Config) -> [{test_case, emqx_authz_test_lib:get_case(Group, cases())} | Config]. diff --git a/apps/emqx_auth_mysql/docker-ct b/apps/emqx_auth_mysql/docker-ct new file mode 100644 index 000000000..0eaebf127 --- /dev/null +++ b/apps/emqx_auth_mysql/docker-ct @@ -0,0 +1 @@ +mysql diff --git a/apps/emqx_auth_mysql/include/emqx_auth_mysql.hrl b/apps/emqx_auth_mysql/include/emqx_auth_mysql.hrl new file mode 100644 index 000000000..3a371c3cb --- /dev/null +++ b/apps/emqx_auth_mysql/include/emqx_auth_mysql.hrl @@ -0,0 +1,31 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-ifndef(EMQX_AUTH_MYSQL_HRL). +-define(EMQX_AUTH_MYSQL_HRL, true). + +-define(AUTHZ_TYPE, mysql). +-define(AUTHZ_TYPE_BIN, <<"mysql">>). + +-define(AUTHN_MECHANISM, password_based). +-define(AUTHN_MECHANISM_BIN, <<"password_based">>). + +-define(AUTHN_BACKEND, mysql). +-define(AUTHN_BACKEND_BIN, <<"mysql">>). + +-define(AUTHN_TYPE, {?AUTHN_MECHANISM, ?AUTHN_BACKEND}). + +-endif. diff --git a/apps/emqx_auth_mysql/rebar.config b/apps/emqx_auth_mysql/rebar.config new file mode 100644 index 000000000..db2424602 --- /dev/null +++ b/apps/emqx_auth_mysql/rebar.config @@ -0,0 +1,6 @@ +%% -*- mode: erlang -*- +{deps, [ + {emqx, {path, "../emqx"}}, + {emqx_utils, {path, "../emqx_utils"}}, + {emqx_auth, {path, "../emqx_auth"}} +]}. diff --git a/apps/emqx_auth_mysql/src/emqx_auth_mysql.app.src b/apps/emqx_auth_mysql/src/emqx_auth_mysql.app.src new file mode 100644 index 000000000..933e8f819 --- /dev/null +++ b/apps/emqx_auth_mysql/src/emqx_auth_mysql.app.src @@ -0,0 +1,19 @@ +%% -*- mode: erlang -*- +{application, emqx_auth_mysql, [ + {description, "EMQX MySQL Authentication and Authorization"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, {emqx_auth_mysql_app, []}}, + {applications, [ + kernel, + stdlib, + emqx, + emqx_auth, + emqx_mysql + ]}, + {env, []}, + {modules, []}, + + {licenses, ["Apache 2.0"]}, + {links, []} +]}. diff --git a/apps/emqx_auth_mysql/src/emqx_auth_mysql_app.erl b/apps/emqx_auth_mysql/src/emqx_auth_mysql_app.erl new file mode 100644 index 000000000..7df3168ac --- /dev/null +++ b/apps/emqx_auth_mysql/src/emqx_auth_mysql_app.erl @@ -0,0 +1,34 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_mysql_app). + +-include("emqx_auth_mysql.hrl"). + +-behaviour(application). + +-export([start/2, stop/1]). + +start(_StartType, _StartArgs) -> + ok = emqx_authz:register_source(?AUTHZ_TYPE, emqx_authz_mysql), + ok = emqx_authn:register_provider(?AUTHN_TYPE, emqx_authn_mysql), + {ok, Sup} = emqx_auth_mysql_sup:start_link(), + {ok, Sup}. + +stop(_State) -> + ok = emqx_authn:deregister_provider(?AUTHN_TYPE), + ok = emqx_authz:unregister_source(?AUTHZ_TYPE), + ok. diff --git a/apps/emqx_auth_mysql/src/emqx_auth_mysql_sup.erl b/apps/emqx_auth_mysql/src/emqx_auth_mysql_sup.erl new file mode 100644 index 000000000..f8858b494 --- /dev/null +++ b/apps/emqx_auth_mysql/src/emqx_auth_mysql_sup.erl @@ -0,0 +1,37 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_mysql_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(SERVER, ?MODULE). + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +init([]) -> + SupFlags = #{ + strategy => one_for_all, + intensity => 0, + period => 1 + }, + ChildSpecs = [], + {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl b/apps/emqx_auth_mysql/src/emqx_authn_mysql.erl similarity index 72% rename from apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl rename to apps/emqx_auth_mysql/src/emqx_authn_mysql.erl index 49471eb23..4280184ff 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl +++ b/apps/emqx_auth_mysql/src/emqx_authn_mysql.erl @@ -16,76 +16,23 @@ -module(emqx_authn_mysql). --include("emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("emqx/include/logger.hrl"). --include_lib("hocon/include/hoconsc.hrl"). - --behaviour(hocon_schema). --behaviour(emqx_authentication). +-behaviour(emqx_authn_provider). -define(PREPARE_KEY, ?MODULE). -export([ - namespace/0, - tags/0, - roots/0, - fields/1, - desc/1 -]). - --export([ - refs/0, create/2, update/2, authenticate/2, destroy/1 ]). -%%------------------------------------------------------------------------------ -%% Hocon Schema -%%------------------------------------------------------------------------------ - -namespace() -> "authn". - -tags() -> - [<<"Authentication">>]. - -%% used for config check when the schema module is resolved -roots() -> - [{?CONF_NS, hoconsc:mk(hoconsc:ref(?MODULE, mysql))}]. - -fields(mysql) -> - [ - {mechanism, emqx_authn_schema:mechanism(password_based)}, - {backend, emqx_authn_schema:backend(mysql)}, - {password_hash_algorithm, fun emqx_authn_password_hashing:type_ro/1}, - {query, fun query/1}, - {query_timeout, fun query_timeout/1} - ] ++ emqx_authn_schema:common_fields() ++ - proplists:delete(prepare_statement, emqx_mysql:fields(config)). - -desc(mysql) -> - ?DESC(mysql); -desc(_) -> - undefined. - -query(type) -> string(); -query(desc) -> ?DESC(?FUNCTION_NAME); -query(required) -> true; -query(_) -> undefined. - -query_timeout(type) -> emqx_schema:duration_ms(); -query_timeout(desc) -> ?DESC(?FUNCTION_NAME); -query_timeout(default) -> <<"5s">>; -query_timeout(_) -> undefined. - %%------------------------------------------------------------------------------ %% APIs %%------------------------------------------------------------------------------ -refs() -> - [hoconsc:ref(?MODULE, mysql)]. - create(_AuthenticatorID, Config) -> create(Config). diff --git a/apps/emqx_auth_mysql/src/emqx_authn_mysql_schema.erl b/apps/emqx_auth_mysql/src/emqx_authn_mysql_schema.erl new file mode 100644 index 000000000..0189ecc61 --- /dev/null +++ b/apps/emqx_auth_mysql/src/emqx_authn_mysql_schema.erl @@ -0,0 +1,71 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authn_mysql_schema). + +-include("emqx_auth_mysql.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authn_schema). + +-export([ + fields/1, + desc/1, + refs/0, + select_union_member/1 +]). + +refs() -> + [?R_REF(mysql)]. + +select_union_member( + #{ + <<"mechanism">> := ?AUTHN_MECHANISM_BIN, <<"backend">> := ?AUTHN_BACKEND_BIN + } +) -> + refs(); +select_union_member(#{<<"backend">> := ?AUTHN_BACKEND_BIN}) -> + throw(#{ + reason => "unknown_mechanism", + expected => ?AUTHN_MECHANISM + }); +select_union_member(_) -> + undefined. + +fields(mysql) -> + [ + {mechanism, emqx_authn_schema:mechanism(?AUTHN_MECHANISM)}, + {backend, emqx_authn_schema:backend(?AUTHN_BACKEND)}, + {password_hash_algorithm, fun emqx_authn_password_hashing:type_ro/1}, + {query, fun query/1}, + {query_timeout, fun query_timeout/1} + ] ++ emqx_authn_schema:common_fields() ++ + proplists:delete(prepare_statement, emqx_mysql:fields(config)). + +desc(mysql) -> + ?DESC(mysql); +desc(_) -> + undefined. + +query(type) -> string(); +query(desc) -> ?DESC(?FUNCTION_NAME); +query(required) -> true; +query(_) -> undefined. + +query_timeout(type) -> emqx_schema:duration_ms(); +query_timeout(desc) -> ?DESC(?FUNCTION_NAME); +query_timeout(default) -> <<"5s">>; +query_timeout(_) -> undefined. diff --git a/apps/emqx_authz/src/emqx_authz_mysql.erl b/apps/emqx_auth_mysql/src/emqx_authz_mysql.erl similarity index 96% rename from apps/emqx_authz/src/emqx_authz_mysql.erl rename to apps/emqx_auth_mysql/src/emqx_authz_mysql.erl index a724e451c..4ca71e332 100644 --- a/apps/emqx_authz/src/emqx_authz_mysql.erl +++ b/apps/emqx_auth_mysql/src/emqx_authz_mysql.erl @@ -16,12 +16,10 @@ -module(emqx_authz_mysql). --include("emqx_authz.hrl"). --include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl"). --behaviour(emqx_authz). +-behaviour(emqx_authz_source). -define(PREPARE_KEY, ?MODULE). @@ -68,7 +66,7 @@ update(#{query := SQL} = Source0) -> end. destroy(#{annotations := #{id := Id}}) -> - ok = emqx_resource:remove_local(Id). + emqx_authz_utils:remove_resource(Id). authorize( Client, diff --git a/apps/emqx_auth_mysql/src/emqx_authz_mysql_schema.erl b/apps/emqx_auth_mysql/src/emqx_authz_mysql_schema.erl new file mode 100644 index 000000000..a9ce422e6 --- /dev/null +++ b/apps/emqx_auth_mysql/src/emqx_authz_mysql_schema.erl @@ -0,0 +1,66 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authz_mysql_schema). + +-include("emqx_auth_mysql.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authz_schema). + +-export([ + type/0, + fields/1, + desc/1, + source_refs/0, + select_union_member/1 +]). + +type() -> ?AUTHZ_TYPE. + +fields(mysql) -> + emqx_authz_schema:authz_common_fields(?AUTHZ_TYPE) ++ + emqx_mysql:fields(config) ++ + [{query, query()}]. + +desc(mysql) -> + ?DESC(mysql); +desc(_) -> + undefined. + +source_refs() -> + [?R_REF(mysql)]. + +select_union_member(#{<<"type">> := ?AUTHZ_TYPE_BIN}) -> + ?R_REF(mysql); +select_union_member(_Value) -> + undefined. + +%%-------------------------------------------------------------------- +%% Internal Functions +%%-------------------------------------------------------------------- + +query() -> + ?HOCON(binary(), #{ + desc => ?DESC(query), + required => true, + validator => fun(S) -> + case size(S) > 0 of + true -> ok; + _ -> {error, "Request query"} + end + end + }). diff --git a/apps/emqx_authn/test/emqx_authn_mysql_SUITE.erl b/apps/emqx_auth_mysql/test/emqx_authn_mysql_SUITE.erl similarity index 97% rename from apps/emqx_authn/test/emqx_authn_mysql_SUITE.erl rename to apps/emqx_auth_mysql/test/emqx_authn_mysql_SUITE.erl index 2173b943b..614a6ef44 100644 --- a/apps/emqx_authn/test/emqx_authn_mysql_SUITE.erl +++ b/apps/emqx_auth_mysql/test/emqx_authn_mysql_SUITE.erl @@ -20,7 +20,7 @@ -compile(export_all). -include_lib("emqx_connector/include/emqx_connector.hrl"). --include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). @@ -37,7 +37,6 @@ groups() -> [{require_seeds, [], [t_authenticate, t_update, t_destroy]}]. init_per_testcase(_, Config) -> - emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL @@ -55,12 +54,12 @@ end_per_group(require_seeds, Config) -> init_per_suite(Config) -> case emqx_common_test_helpers:is_tcp_server_available(?MYSQL_HOST, ?MYSQL_DEFAULT_PORT) of true -> - Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_auth, emqx_auth_mysql], #{ work_dir => ?config(priv_dir, Config) }), {ok, _} = emqx_resource:create_local( ?MYSQL_RESOURCE, - ?RESOURCE_GROUP, + ?AUTHN_RESOURCE_GROUP, emqx_mysql, mysql_config(), #{} @@ -91,7 +90,7 @@ t_create(_Config) -> {create_authenticator, ?GLOBAL, AuthConfig} ), - {ok, [#{provider := emqx_authn_mysql}]} = emqx_authentication:list_authenticators(?GLOBAL), + {ok, [#{provider := emqx_authn_mysql}]} = emqx_authn_chains:list_authenticators(?GLOBAL), emqx_authn_test_lib:delete_config(?ResourceID). t_create_invalid(_Config) -> @@ -113,7 +112,7 @@ t_create_invalid(_Config) -> emqx_authn_test_lib:delete_config(?ResourceID), ?assertEqual( {error, {not_found, {chain, ?GLOBAL}}}, - emqx_authentication:list_authenticators(?GLOBAL) + emqx_authn_chains:list_authenticators(?GLOBAL) ) end, InvalidConfigs @@ -161,7 +160,7 @@ t_destroy(_Config) -> ), {ok, [#{provider := emqx_authn_mysql, state := State}]} = - emqx_authentication:list_authenticators(?GLOBAL), + emqx_authn_chains:list_authenticators(?GLOBAL), {ok, _} = emqx_authn_mysql:authenticate( #{ diff --git a/apps/emqx_authn/test/emqx_authn_mysql_tls_SUITE.erl b/apps/emqx_auth_mysql/test/emqx_authn_mysql_tls_SUITE.erl similarity index 97% rename from apps/emqx_authn/test/emqx_authn_mysql_tls_SUITE.erl rename to apps/emqx_auth_mysql/test/emqx_authn_mysql_tls_SUITE.erl index 888ff5e6b..8558ce764 100644 --- a/apps/emqx_authn/test/emqx_authn_mysql_tls_SUITE.erl +++ b/apps/emqx_auth_mysql/test/emqx_authn_mysql_tls_SUITE.erl @@ -20,7 +20,7 @@ -compile(export_all). -include_lib("emqx_connector/include/emqx_connector.hrl"). --include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). @@ -36,7 +36,6 @@ groups() -> []. init_per_testcase(_, Config) -> - emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL @@ -46,7 +45,7 @@ init_per_testcase(_, Config) -> init_per_suite(Config) -> case emqx_common_test_helpers:is_tcp_server_available(?MYSQL_HOST, ?MYSQL_DEFAULT_PORT) of true -> - Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_auth, emqx_auth_mysql], #{ work_dir => ?config(priv_dir, Config) }), [{apps, Apps} | Config]; diff --git a/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl b/apps/emqx_auth_mysql/test/emqx_authz_mysql_SUITE.erl similarity index 96% rename from apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl rename to apps/emqx_auth_mysql/test/emqx_authz_mysql_SUITE.erl index 4304dd505..b9b7d9966 100644 --- a/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl +++ b/apps/emqx_auth_mysql/test/emqx_authz_mysql_SUITE.erl @@ -19,7 +19,7 @@ -compile(export_all). -include("emqx_connector.hrl"). --include("emqx_authz.hrl"). +-include_lib("emqx_auth/include/emqx_authz.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). @@ -33,25 +33,28 @@ groups() -> emqx_authz_test_lib:table_groups(t_run_case, cases()). init_per_suite(Config) -> - ok = stop_apps([emqx_resource]), case emqx_common_test_helpers:is_tcp_server_available(?MYSQL_HOST, ?MYSQL_DEFAULT_PORT) of true -> - ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1 + Apps = emqx_cth_suite:start( + [ + emqx, + {emqx_conf, + "authorization.no_match = deny, authorization.cache.enable = false"}, + emqx_auth, + emqx_auth_mysql + ], + #{work_dir => ?config(priv_dir, Config)} ), - ok = start_apps([emqx_resource]), ok = create_mysql_resource(), - Config; + [{suite_apps, Apps} | Config]; false -> {skip, no_mysql} end. -end_per_suite(_Config) -> +end_per_suite(Config) -> ok = emqx_authz_test_lib:restore_authorizers(), ok = emqx_resource:remove_local(?MYSQL_RESOURCE), - ok = stop_apps([emqx_resource]), - ok = emqx_common_test_helpers:stop_apps([emqx_conf, emqx_authz]). + ok = emqx_cth_suite:stop(?config(suite_apps, Config)). init_per_group(Group, Config) -> [{test_case, emqx_authz_test_lib:get_case(Group, cases())} | Config]. @@ -66,11 +69,6 @@ end_per_testcase(_TestCase, _Config) -> ok = drop_table(), ok. -set_special_configs(emqx_authz) -> - ok = emqx_authz_test_lib:reset_authorizers(); -set_special_configs(_) -> - ok. - %%------------------------------------------------------------------------------ %% Testcases %%------------------------------------------------------------------------------ @@ -404,7 +402,8 @@ raw_mysql_authz_config() -> "SELECT permission, action, topic " "FROM acl WHERE username = ${username}" >>, - <<"server">> => <> + <<"server">> => <>, + <<"pool_size">> => <<"1">> }. q(Sql) -> @@ -434,7 +433,7 @@ mysql_config() -> database => <<"mqtt">>, username => <<"root">>, password => <<"public">>, - pool_size => 8, + pool_size => 1, server => <>, ssl => #{enable => false} }. @@ -448,7 +447,7 @@ stop_apps(Apps) -> create_mysql_resource() -> {ok, _} = emqx_resource:create_local( ?MYSQL_RESOURCE, - ?RESOURCE_GROUP, + ?AUTHZ_RESOURCE_GROUP, emqx_mysql, mysql_config(), #{} diff --git a/apps/emqx_auth_postgresql/docker-ct b/apps/emqx_auth_postgresql/docker-ct new file mode 100644 index 000000000..7ecf66a95 --- /dev/null +++ b/apps/emqx_auth_postgresql/docker-ct @@ -0,0 +1 @@ +pgsql diff --git a/apps/emqx_auth_postgresql/include/emqx_auth_postgresql.hrl b/apps/emqx_auth_postgresql/include/emqx_auth_postgresql.hrl new file mode 100644 index 000000000..dda55522f --- /dev/null +++ b/apps/emqx_auth_postgresql/include/emqx_auth_postgresql.hrl @@ -0,0 +1,31 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-ifndef(EMQX_AUTH_POSTGRESQL_HRL). +-define(EMQX_AUTH_POSTGRESQL_HRL, true). + +-define(AUTHZ_TYPE, postgresql). +-define(AUTHZ_TYPE_BIN, <<"postgresql">>). + +-define(AUTHN_MECHANISM, password_based). +-define(AUTHN_MECHANISM_BIN, <<"password_based">>). + +-define(AUTHN_BACKEND, postgresql). +-define(AUTHN_BACKEND_BIN, <<"postgresql">>). + +-define(AUTHN_TYPE, {?AUTHN_MECHANISM, ?AUTHN_BACKEND}). + +-endif. diff --git a/apps/emqx_auth_postgresql/rebar.config b/apps/emqx_auth_postgresql/rebar.config new file mode 100644 index 000000000..db2424602 --- /dev/null +++ b/apps/emqx_auth_postgresql/rebar.config @@ -0,0 +1,6 @@ +%% -*- mode: erlang -*- +{deps, [ + {emqx, {path, "../emqx"}}, + {emqx_utils, {path, "../emqx_utils"}}, + {emqx_auth, {path, "../emqx_auth"}} +]}. diff --git a/apps/emqx_auth_postgresql/src/emqx_auth_postgresql.app.src b/apps/emqx_auth_postgresql/src/emqx_auth_postgresql.app.src new file mode 100644 index 000000000..3157b7bd7 --- /dev/null +++ b/apps/emqx_auth_postgresql/src/emqx_auth_postgresql.app.src @@ -0,0 +1,19 @@ +%% -*- mode: erlang -*- +{application, emqx_auth_postgresql, [ + {description, "EMQX PostgreSQL Authentication and Authorization"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, {emqx_auth_postgresql_app, []}}, + {applications, [ + kernel, + stdlib, + emqx, + emqx_auth, + emqx_connector + ]}, + {env, []}, + {modules, []}, + + {licenses, ["Apache 2.0"]}, + {links, []} +]}. diff --git a/apps/emqx_auth_postgresql/src/emqx_auth_postgresql_app.erl b/apps/emqx_auth_postgresql/src/emqx_auth_postgresql_app.erl new file mode 100644 index 000000000..c59f36e53 --- /dev/null +++ b/apps/emqx_auth_postgresql/src/emqx_auth_postgresql_app.erl @@ -0,0 +1,34 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_postgresql_app). + +-include("emqx_auth_postgresql.hrl"). + +-behaviour(application). + +-export([start/2, stop/1]). + +start(_StartType, _StartArgs) -> + ok = emqx_authz:register_source(?AUTHZ_TYPE, emqx_authz_postgresql), + ok = emqx_authn:register_provider(?AUTHN_TYPE, emqx_authn_postgresql), + {ok, Sup} = emqx_auth_postgresql_sup:start_link(), + {ok, Sup}. + +stop(_State) -> + ok = emqx_authz:unregister_source(?AUTHZ_TYPE), + ok = emqx_authn:deregister_provider(?AUTHN_TYPE), + ok. diff --git a/apps/emqx_auth_postgresql/src/emqx_auth_postgresql_sup.erl b/apps/emqx_auth_postgresql/src/emqx_auth_postgresql_sup.erl new file mode 100644 index 000000000..2e1a17645 --- /dev/null +++ b/apps/emqx_auth_postgresql/src/emqx_auth_postgresql_sup.erl @@ -0,0 +1,37 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_postgresql_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(SERVER, ?MODULE). + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +init([]) -> + SupFlags = #{ + strategy => one_for_all, + intensity => 0, + period => 1 + }, + ChildSpecs = [], + {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl b/apps/emqx_auth_postgresql/src/emqx_authn_postgresql.erl similarity index 74% rename from apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl rename to apps/emqx_auth_postgresql/src/emqx_authn_postgresql.erl index b9ce9db8d..1ce2e405c 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl +++ b/apps/emqx_auth_postgresql/src/emqx_authn_postgresql.erl @@ -14,26 +14,15 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_authn_pgsql). +-module(emqx_authn_postgresql). --include("emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("emqx/include/logger.hrl"). -include_lib("epgsql/include/epgsql.hrl"). --include_lib("hocon/include/hoconsc.hrl"). --behaviour(hocon_schema). --behaviour(emqx_authentication). +-behaviour(emqx_authn_provider). -export([ - namespace/0, - tags/0, - roots/0, - fields/1, - desc/1 -]). - --export([ - refs/0, create/2, update/2, authenticate/2, @@ -45,46 +34,10 @@ -compile(nowarn_export_all). -endif. -%%------------------------------------------------------------------------------ -%% Hocon Schema -%%------------------------------------------------------------------------------ - -namespace() -> "authn". - -tags() -> - [<<"Authentication">>]. - -%% used for config check when the schema module is resolved -roots() -> - [{?CONF_NS, hoconsc:mk(hoconsc:ref(?MODULE, postgresql))}]. - -fields(postgresql) -> - [ - {mechanism, emqx_authn_schema:mechanism(password_based)}, - {backend, emqx_authn_schema:backend(postgresql)}, - {password_hash_algorithm, fun emqx_authn_password_hashing:type_ro/1}, - {query, fun query/1} - ] ++ - emqx_authn_schema:common_fields() ++ - proplists:delete(prepare_statement, emqx_connector_pgsql:fields(config)). - -desc(postgresql) -> - ?DESC(postgresql); -desc(_) -> - undefined. - -query(type) -> string(); -query(desc) -> ?DESC(?FUNCTION_NAME); -query(required) -> true; -query(_) -> undefined. - %%------------------------------------------------------------------------------ %% APIs %%------------------------------------------------------------------------------ -refs() -> - [hoconsc:ref(?MODULE, postgresql)]. - create(_AuthenticatorID, Config) -> create(Config). diff --git a/apps/emqx_auth_postgresql/src/emqx_authn_postgresql_schema.erl b/apps/emqx_auth_postgresql/src/emqx_authn_postgresql_schema.erl new file mode 100644 index 000000000..93819d7bf --- /dev/null +++ b/apps/emqx_auth_postgresql/src/emqx_authn_postgresql_schema.erl @@ -0,0 +1,66 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authn_postgresql_schema). + +-include("emqx_auth_postgresql.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authn_schema). + +-export([ + fields/1, + desc/1, + refs/0, + select_union_member/1 +]). + +select_union_member( + #{ + <<"mechanism">> := ?AUTHN_MECHANISM_BIN, <<"backend">> := ?AUTHN_BACKEND_BIN + } +) -> + refs(); +select_union_member(#{<<"backend">> := ?AUTHN_BACKEND_BIN}) -> + throw(#{ + reason => "unknown_mechanism", + expected => ?AUTHN_MECHANISM + }); +select_union_member(_) -> + undefined. + +fields(postgresql) -> + [ + {mechanism, emqx_authn_schema:mechanism(?AUTHN_MECHANISM)}, + {backend, emqx_authn_schema:backend(?AUTHN_BACKEND)}, + {password_hash_algorithm, fun emqx_authn_password_hashing:type_ro/1}, + {query, fun query/1} + ] ++ + emqx_authn_schema:common_fields() ++ + proplists:delete(prepare_statement, emqx_connector_pgsql:fields(config)). + +desc(postgresql) -> + ?DESC(postgresql); +desc(_) -> + undefined. + +query(type) -> string(); +query(desc) -> ?DESC(?FUNCTION_NAME); +query(required) -> true; +query(_) -> undefined. + +refs() -> + [hoconsc:ref(?MODULE, postgresql)]. diff --git a/apps/emqx_authz/src/emqx_authz_postgresql.erl b/apps/emqx_auth_postgresql/src/emqx_authz_postgresql.erl similarity index 96% rename from apps/emqx_authz/src/emqx_authz_postgresql.erl rename to apps/emqx_auth_postgresql/src/emqx_authz_postgresql.erl index f0bdf77be..27f2d31ee 100644 --- a/apps/emqx_authz/src/emqx_authz_postgresql.erl +++ b/apps/emqx_auth_postgresql/src/emqx_authz_postgresql.erl @@ -16,14 +16,12 @@ -module(emqx_authz_postgresql). --include("emqx_authz.hrl"). --include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl"). -include_lib("epgsql/include/epgsql.hrl"). --behaviour(emqx_authz). +-behaviour(emqx_authz_source). %% AuthZ Callbacks -export([ @@ -75,7 +73,7 @@ update(#{query := SQL0, annotations := #{id := ResourceID}} = Source) -> end. destroy(#{annotations := #{id := Id}}) -> - ok = emqx_resource:remove_local(Id). + emqx_authz_utils:remove_resource(Id). authorize( Client, diff --git a/apps/emqx_auth_postgresql/src/emqx_authz_postgresql_schema.erl b/apps/emqx_auth_postgresql/src/emqx_authz_postgresql_schema.erl new file mode 100644 index 000000000..a52cc4fdd --- /dev/null +++ b/apps/emqx_auth_postgresql/src/emqx_authz_postgresql_schema.erl @@ -0,0 +1,66 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authz_postgresql_schema). + +-include("emqx_auth_postgresql.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authz_schema). + +-export([ + type/0, + fields/1, + desc/1, + source_refs/0, + select_union_member/1 +]). + +type() -> ?AUTHZ_TYPE. + +fields(postgresql) -> + emqx_authz_schema:authz_common_fields(?AUTHZ_TYPE) ++ + emqx_connector_pgsql:fields(config) ++ + [{query, query()}]. + +desc(postgresql) -> + ?DESC(postgresql); +desc(_) -> + undefined. + +source_refs() -> + [?R_REF(postgresql)]. + +select_union_member(#{<<"type">> := ?AUTHZ_TYPE_BIN}) -> + ?R_REF(postgresql); +select_union_member(_Value) -> + undefined. + +%%-------------------------------------------------------------------- +%% Internal Functions +%%-------------------------------------------------------------------- + +query() -> + ?HOCON(binary(), #{ + desc => ?DESC(query), + required => true, + validator => fun(S) -> + case size(S) > 0 of + true -> ok; + _ -> {error, "Request query"} + end + end + }). diff --git a/apps/emqx_authn/test/emqx_authn_pgsql_SUITE.erl b/apps/emqx_auth_postgresql/test/emqx_authn_postgresql_SUITE.erl similarity index 96% rename from apps/emqx_authn/test/emqx_authn_pgsql_SUITE.erl rename to apps/emqx_auth_postgresql/test/emqx_authn_postgresql_SUITE.erl index 1c9f0f86b..752202610 100644 --- a/apps/emqx_authn/test/emqx_authn_pgsql_SUITE.erl +++ b/apps/emqx_auth_postgresql/test/emqx_authn_postgresql_SUITE.erl @@ -14,18 +14,18 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_authn_pgsql_SUITE). +-module(emqx_authn_postgresql_SUITE). -compile(nowarn_export_all). -compile(export_all). -include_lib("emqx_connector/include/emqx_connector.hrl"). --include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). -define(PGSQL_HOST, "pgsql"). --define(PGSQL_RESOURCE, <<"emqx_authn_pgsql_SUITE">>). +-define(PGSQL_RESOURCE, <<"emqx_authn_postgresql_SUITE">>). -define(ResourceID, <<"password_based:postgresql">>). -define(PATH, [authentication]). @@ -41,7 +41,6 @@ groups() -> [{require_seeds, [], [t_create, t_authenticate, t_update, t_destroy, t_is_superuser]}]. init_per_testcase(_, Config) -> - emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL @@ -59,12 +58,12 @@ end_per_group(require_seeds, Config) -> init_per_suite(Config) -> case emqx_common_test_helpers:is_tcp_server_available(?PGSQL_HOST, ?PGSQL_DEFAULT_PORT) of true -> - Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_auth, emqx_auth_postgresql], #{ work_dir => ?config(priv_dir, Config) }), {ok, _} = emqx_resource:create_local( ?PGSQL_RESOURCE, - ?RESOURCE_GROUP, + ?AUTHN_RESOURCE_GROUP, emqx_connector_pgsql, pgsql_config(), #{} @@ -95,7 +94,7 @@ t_create(_Config) -> {create_authenticator, ?GLOBAL, AuthConfig} ), - {ok, [#{provider := emqx_authn_pgsql}]} = emqx_authentication:list_authenticators(?GLOBAL), + {ok, [#{provider := emqx_authn_postgresql}]} = emqx_authn_chains:list_authenticators(?GLOBAL), emqx_authn_test_lib:delete_config(?ResourceID). %% invalid config which does not pass the schema check should result in an error @@ -105,7 +104,7 @@ t_update_with_invalid_config(_Config) -> ?assertMatch( {error, #{ kind := validation_error, - matched_type := "authn:postgresql", + matched_type := "postgresql", path := "authentication.1.server", reason := required_field }}, @@ -136,7 +135,7 @@ t_update_with_bad_config_value(_Config) -> emqx_authn_test_lib:delete_config(?ResourceID), ?assertEqual( {error, {not_found, {chain, ?GLOBAL}}}, - emqx_authentication:list_authenticators(?GLOBAL) + emqx_authn_chains:list_authenticators(?GLOBAL) ) end, InvalidConfigs @@ -183,10 +182,10 @@ t_destroy(_Config) -> {create_authenticator, ?GLOBAL, AuthConfig} ), - {ok, [#{provider := emqx_authn_pgsql, state := State}]} = - emqx_authentication:list_authenticators(?GLOBAL), + {ok, [#{provider := emqx_authn_postgresql, state := State}]} = + emqx_authn_chains:list_authenticators(?GLOBAL), - {ok, _} = emqx_authn_pgsql:authenticate( + {ok, _} = emqx_authn_postgresql:authenticate( #{ username => <<"plain">>, password => <<"plain">> @@ -202,7 +201,7 @@ t_destroy(_Config) -> % Authenticator should not be usable anymore ?assertMatch( ignore, - emqx_authn_pgsql:authenticate( + emqx_authn_postgresql:authenticate( #{ username => <<"plain">>, password => <<"plain">> diff --git a/apps/emqx_authn/test/emqx_authn_pgsql_tls_SUITE.erl b/apps/emqx_auth_postgresql/test/emqx_authn_postgresql_tls_SUITE.erl similarity index 96% rename from apps/emqx_authn/test/emqx_authn_pgsql_tls_SUITE.erl rename to apps/emqx_auth_postgresql/test/emqx_authn_postgresql_tls_SUITE.erl index 4862572e6..25a65f660 100644 --- a/apps/emqx_authn/test/emqx_authn_pgsql_tls_SUITE.erl +++ b/apps/emqx_auth_postgresql/test/emqx_authn_postgresql_tls_SUITE.erl @@ -14,13 +14,13 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_authn_pgsql_tls_SUITE). +-module(emqx_authn_postgresql_tls_SUITE). -compile(nowarn_export_all). -compile(export_all). -include_lib("emqx_connector/include/emqx_connector.hrl"). --include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). @@ -37,7 +37,6 @@ groups() -> init_per_testcase(_, Config) -> {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), - emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL @@ -48,7 +47,7 @@ init_per_suite(Config) -> _ = application:load(emqx_conf), case emqx_common_test_helpers:is_tcp_server_available(?PGSQL_HOST, ?PGSQL_DEFAULT_PORT) of true -> - Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_auth, emqx_auth_postgresql], #{ work_dir => ?config(priv_dir, Config) }), [{apps, Apps} | Config]; diff --git a/apps/emqx_authz/test/emqx_authz_postgresql_SUITE.erl b/apps/emqx_auth_postgresql/test/emqx_authz_postgresql_SUITE.erl similarity index 96% rename from apps/emqx_authz/test/emqx_authz_postgresql_SUITE.erl rename to apps/emqx_auth_postgresql/test/emqx_authz_postgresql_SUITE.erl index a9181879e..4d38e9c96 100644 --- a/apps/emqx_authz/test/emqx_authz_postgresql_SUITE.erl +++ b/apps/emqx_auth_postgresql/test/emqx_authz_postgresql_SUITE.erl @@ -19,7 +19,7 @@ -compile(export_all). -include("emqx_connector.hrl"). --include("emqx_authz.hrl"). +-include_lib("emqx_auth/include/emqx_authz.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). @@ -33,25 +33,28 @@ groups() -> emqx_authz_test_lib:table_groups(t_run_case, cases()). init_per_suite(Config) -> - ok = stop_apps([emqx_resource]), case emqx_common_test_helpers:is_tcp_server_available(?PGSQL_HOST, ?PGSQL_DEFAULT_PORT) of true -> - ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1 + Apps = emqx_cth_suite:start( + [ + emqx, + {emqx_conf, + "authorization.no_match = deny, authorization.cache.enable = false"}, + emqx_auth, + emqx_auth_postgresql + ], + #{work_dir => ?config(priv_dir, Config)} ), - ok = start_apps([emqx_resource]), {ok, _} = create_pgsql_resource(), - Config; + [{suite_apps, Apps} | Config]; false -> {skip, no_pgsql} end. -end_per_suite(_Config) -> +end_per_suite(Config) -> ok = emqx_authz_test_lib:restore_authorizers(), ok = emqx_resource:remove_local(?PGSQL_RESOURCE), - ok = stop_apps([emqx_resource]), - ok = emqx_common_test_helpers:stop_apps([emqx_conf, emqx_authz]). + ok = emqx_cth_suite:stop_apps(?config(suite_apps, Config)). init_per_group(Group, Config) -> [{test_case, emqx_authz_test_lib:get_case(Group, cases())} | Config]. @@ -66,7 +69,7 @@ end_per_testcase(_TestCase, _Config) -> ok = drop_table(), ok. -set_special_configs(emqx_authz) -> +set_special_configs(emqx_auth) -> ok = emqx_authz_test_lib:reset_authorizers(); set_special_configs(_) -> ok. @@ -434,7 +437,7 @@ pgsql_config() -> create_pgsql_resource() -> emqx_resource:create_local( ?PGSQL_RESOURCE, - ?RESOURCE_GROUP, + ?AUTHZ_RESOURCE_GROUP, emqx_connector_pgsql, pgsql_config(), #{} diff --git a/apps/emqx_auth_redis/docker-ct b/apps/emqx_auth_redis/docker-ct new file mode 100644 index 000000000..7800f0fad --- /dev/null +++ b/apps/emqx_auth_redis/docker-ct @@ -0,0 +1 @@ +redis diff --git a/apps/emqx_auth_redis/include/emqx_auth_redis.hrl b/apps/emqx_auth_redis/include/emqx_auth_redis.hrl new file mode 100644 index 000000000..b7c7d3998 --- /dev/null +++ b/apps/emqx_auth_redis/include/emqx_auth_redis.hrl @@ -0,0 +1,31 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-ifndef(EMQX_AUTH_REDIS_HRL). +-define(EMQX_AUTH_REDIS_HRL, true). + +-define(AUTHZ_TYPE, redis). +-define(AUTHZ_TYPE_BIN, <<"redis">>). + +-define(AUTHN_MECHANISM, password_based). +-define(AUTHN_MECHANISM_BIN, <<"password_based">>). + +-define(AUTHN_BACKEND, redis). +-define(AUTHN_BACKEND_BIN, <<"redis">>). + +-define(AUTHN_TYPE, {?AUTHN_MECHANISM, ?AUTHN_BACKEND}). + +-endif. diff --git a/apps/emqx_auth_redis/rebar.config b/apps/emqx_auth_redis/rebar.config new file mode 100644 index 000000000..db2424602 --- /dev/null +++ b/apps/emqx_auth_redis/rebar.config @@ -0,0 +1,6 @@ +%% -*- mode: erlang -*- +{deps, [ + {emqx, {path, "../emqx"}}, + {emqx_utils, {path, "../emqx_utils"}}, + {emqx_auth, {path, "../emqx_auth"}} +]}. diff --git a/apps/emqx_auth_redis/src/emqx_auth_redis.app.src b/apps/emqx_auth_redis/src/emqx_auth_redis.app.src new file mode 100644 index 000000000..388fd413c --- /dev/null +++ b/apps/emqx_auth_redis/src/emqx_auth_redis.app.src @@ -0,0 +1,19 @@ +%% -*- mode: erlang -*- +{application, emqx_auth_redis, [ + {description, "EMQX Redis Authentication and Authorization"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, {emqx_auth_redis_app, []}}, + {applications, [ + kernel, + stdlib, + emqx, + emqx_redis, + emqx_auth + ]}, + {env, []}, + {modules, []}, + + {licenses, ["Apache 2.0"]}, + {links, []} +]}. diff --git a/apps/emqx_auth_redis/src/emqx_auth_redis_app.erl b/apps/emqx_auth_redis/src/emqx_auth_redis_app.erl new file mode 100644 index 000000000..131b46748 --- /dev/null +++ b/apps/emqx_auth_redis/src/emqx_auth_redis_app.erl @@ -0,0 +1,34 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_redis_app). + +-include("emqx_auth_redis.hrl"). + +-behaviour(application). + +-export([start/2, stop/1]). + +start(_StartType, _StartArgs) -> + ok = emqx_authz:register_source(?AUTHZ_TYPE, emqx_authz_redis), + ok = emqx_authn:register_provider(?AUTHN_TYPE, emqx_authn_redis), + {ok, Sup} = emqx_auth_redis_sup:start_link(), + {ok, Sup}. + +stop(_State) -> + ok = emqx_authn:deregister_provider(?AUTHN_TYPE), + ok = emqx_authz:unregister_source(?AUTHZ_TYPE), + ok. diff --git a/apps/emqx_auth_redis/src/emqx_auth_redis_sup.erl b/apps/emqx_auth_redis/src/emqx_auth_redis_sup.erl new file mode 100644 index 000000000..82fff459f --- /dev/null +++ b/apps/emqx_auth_redis/src/emqx_auth_redis_sup.erl @@ -0,0 +1,37 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_auth_redis_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(SERVER, ?MODULE). + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +init([]) -> + SupFlags = #{ + strategy => one_for_all, + intensity => 0, + period => 1 + }, + ChildSpecs = [], + {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl b/apps/emqx_auth_redis/src/emqx_authn_redis.erl similarity index 98% rename from apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl rename to apps/emqx_auth_redis/src/emqx_authn_redis.erl index a5312e41b..2f0948faf 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl +++ b/apps/emqx_auth_redis/src/emqx_authn_redis.erl @@ -16,12 +16,12 @@ -module(emqx_authn_redis). --include("emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("emqx/include/logger.hrl"). -include_lib("hocon/include/hoconsc.hrl"). -behaviour(hocon_schema). --behaviour(emqx_authentication). +-behaviour(emqx_authn_provider). -export([ namespace/0, @@ -88,10 +88,6 @@ cmd(desc) -> ?DESC(?FUNCTION_NAME); cmd(required) -> true; cmd(_) -> undefined. -%%------------------------------------------------------------------------------ -%% APIs -%%------------------------------------------------------------------------------ - refs() -> [ hoconsc:ref(?MODULE, redis_single), @@ -116,6 +112,10 @@ refs(_) -> expected => "single | cluster | sentinel" }). +%%------------------------------------------------------------------------------ +%% APIs +%%------------------------------------------------------------------------------ + create(_AuthenticatorID, Config) -> create(Config). diff --git a/apps/emqx_auth_redis/src/emqx_authn_redis_schema.erl b/apps/emqx_auth_redis/src/emqx_authn_redis_schema.erl new file mode 100644 index 000000000..4f1b63633 --- /dev/null +++ b/apps/emqx_auth_redis/src/emqx_authn_redis_schema.erl @@ -0,0 +1,91 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authn_redis_schema). + +-include("emqx_auth_redis.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authn_schema). + +-export([ + fields/1, + desc/1, + refs/0, + select_union_member/1 +]). + +refs() -> + [ + ?R_REF(redis_single), + ?R_REF(redis_cluster), + ?R_REF(redis_sentinel) + ]. + +select_union_member( + #{ + <<"mechanism">> := ?AUTHN_MECHANISM_BIN, <<"backend">> := ?AUTHN_BACKEND_BIN + } = Value +) -> + refs(Value); +select_union_member(#{<<"backend">> := ?AUTHN_BACKEND_BIN}) -> + throw(#{ + reason => "unknown_mechanism", + expected => ?AUTHN_MECHANISM + }); +select_union_member(_) -> + undefined. + +refs(#{<<"redis_type">> := <<"single">>}) -> + [?R_REF(redis_single)]; +refs(#{<<"redis_type">> := <<"cluster">>}) -> + [?R_REF(redis_cluster)]; +refs(#{<<"redis_type">> := <<"sentinel">>}) -> + [?R_REF(redis_sentinel)]; +refs(_) -> + throw(#{ + field_name => redis_type, + expected => "single | cluster | sentinel" + }). + +fields(redis_single) -> + common_fields() ++ emqx_redis:fields(single); +fields(redis_cluster) -> + common_fields() ++ emqx_redis:fields(cluster); +fields(redis_sentinel) -> + common_fields() ++ emqx_redis:fields(sentinel). + +desc(redis_single) -> + ?DESC(single); +desc(redis_cluster) -> + ?DESC(cluster); +desc(redis_sentinel) -> + ?DESC(sentinel); +desc(_) -> + "". + +common_fields() -> + [ + {mechanism, emqx_authn_schema:mechanism(password_based)}, + {backend, emqx_authn_schema:backend(redis)}, + {cmd, fun cmd/1}, + {password_hash_algorithm, fun emqx_authn_password_hashing:type_ro/1} + ] ++ emqx_authn_schema:common_fields(). + +cmd(type) -> string(); +cmd(desc) -> ?DESC(?FUNCTION_NAME); +cmd(required) -> true; +cmd(_) -> undefined. diff --git a/apps/emqx_authz/src/emqx_authz_redis.erl b/apps/emqx_auth_redis/src/emqx_authz_redis.erl similarity index 97% rename from apps/emqx_authz/src/emqx_authz_redis.erl rename to apps/emqx_auth_redis/src/emqx_authz_redis.erl index d163c0d16..be83223e4 100644 --- a/apps/emqx_authz/src/emqx_authz_redis.erl +++ b/apps/emqx_auth_redis/src/emqx_authz_redis.erl @@ -16,12 +16,10 @@ -module(emqx_authz_redis). --include("emqx_authz.hrl"). --include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl"). --behaviour(emqx_authz). +-behaviour(emqx_authz_source). %% AuthZ Callbacks -export([ @@ -66,7 +64,7 @@ update(#{cmd := CmdStr} = Source) -> end. destroy(#{annotations := #{id := Id}}) -> - ok = emqx_resource:remove_local(Id). + emqx_authz_utils:remove_resource(Id). authorize( Client, diff --git a/apps/emqx_auth_redis/src/emqx_authz_redis_schema.erl b/apps/emqx_auth_redis/src/emqx_authz_redis_schema.erl new file mode 100644 index 000000000..755192bfc --- /dev/null +++ b/apps/emqx_auth_redis/src/emqx_authz_redis_schema.erl @@ -0,0 +1,93 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authz_redis_schema). + +-include("emqx_auth_redis.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authz_schema). + +-export([ + type/0, + fields/1, + desc/1, + source_refs/0, + select_union_member/1 +]). + +type() -> ?AUTHZ_TYPE. + +fields(redis_single) -> + emqx_authz_schema:authz_common_fields(?AUTHZ_TYPE) ++ + emqx_redis:fields(single) ++ + [{cmd, cmd()}]; +fields(redis_sentinel) -> + emqx_authz_schema:authz_common_fields(?AUTHZ_TYPE) ++ + emqx_redis:fields(sentinel) ++ + [{cmd, cmd()}]; +fields(redis_cluster) -> + emqx_authz_schema:authz_common_fields(?AUTHZ_TYPE) ++ + emqx_redis:fields(cluster) ++ + [{cmd, cmd()}]. + +desc(redis_single) -> + ?DESC(redis_single); +desc(redis_sentinel) -> + ?DESC(redis_sentinel); +desc(redis_cluster) -> + ?DESC(redis_cluster); +desc(_) -> + undefined. + +source_refs() -> + [?R_REF(redis_single), ?R_REF(redis_sentinel), ?R_REF(redis_cluster)]. + +select_union_member(#{<<"type">> := ?AUTHZ_TYPE_BIN} = Value) -> + RedisType = maps:get(<<"redis_type">>, Value, undefined), + case RedisType of + <<"single">> -> + ?R_REF(redis_single); + <<"cluster">> -> + ?R_REF(redis_cluster); + <<"sentinel">> -> + ?R_REF(redis_sentinel); + Else -> + throw(#{ + reason => "unknown_redis_type", + expected => "single | cluster | sentinel", + got => Else + }) + end; +select_union_member(_Value) -> + undefined. + +%%-------------------------------------------------------------------- +%% Internal Functions +%%-------------------------------------------------------------------- + +cmd() -> + ?HOCON(binary(), #{ + desc => ?DESC(cmd), + required => true, + validator => fun(S) -> + case size(S) > 0 of + true -> ok; + _ -> {error, "Request query"} + end + end, + example => <<"HGETALL mqtt_authz">> + }). diff --git a/apps/emqx_authn/test/emqx_authn_redis_SUITE.erl b/apps/emqx_auth_redis/test/emqx_authn_redis_SUITE.erl similarity index 96% rename from apps/emqx_authn/test/emqx_authn_redis_SUITE.erl rename to apps/emqx_auth_redis/test/emqx_authn_redis_SUITE.erl index c8ae3d2a2..b3f4a15a3 100644 --- a/apps/emqx_authn/test/emqx_authn_redis_SUITE.erl +++ b/apps/emqx_auth_redis/test/emqx_authn_redis_SUITE.erl @@ -20,7 +20,7 @@ -compile(export_all). -include_lib("emqx_connector/include/emqx_connector.hrl"). --include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). @@ -42,7 +42,6 @@ groups() -> [{require_seeds, [], [t_authenticate, t_update, t_destroy]}]. init_per_testcase(_, Config) -> - emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL @@ -60,12 +59,12 @@ end_per_group(require_seeds, Config) -> init_per_suite(Config) -> case emqx_common_test_helpers:is_tcp_server_available(?REDIS_HOST, ?REDIS_DEFAULT_PORT) of true -> - Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_auth, emqx_auth_redis], #{ work_dir => ?config(priv_dir, Config) }), {ok, _} = emqx_resource:create_local( ?REDIS_RESOURCE, - ?RESOURCE_GROUP, + ?AUTHN_RESOURCE_GROUP, emqx_redis, redis_config(), #{} @@ -91,7 +90,7 @@ end_per_suite(Config) -> t_create(_Config) -> ?assertEqual( {error, {not_found, {chain, ?GLOBAL}}}, - emqx_authentication:list_authenticators(?GLOBAL) + emqx_authn_chains:list_authenticators(?GLOBAL) ), AuthConfig = raw_redis_auth_config(), {ok, _} = emqx:update_config( @@ -99,7 +98,7 @@ t_create(_Config) -> {create_authenticator, ?GLOBAL, AuthConfig} ), - {ok, [#{provider := emqx_authn_redis}]} = emqx_authentication:list_authenticators(?GLOBAL). + {ok, [#{provider := emqx_authn_redis}]} = emqx_authn_chains:list_authenticators(?GLOBAL). t_create_with_config_values_wont_work(_Config) -> AuthConfig = raw_redis_auth_config(), @@ -127,7 +126,7 @@ t_create_with_config_values_wont_work(_Config) -> ?assertEqual( {error, {not_found, {chain, ?GLOBAL}}}, - emqx_authentication:list_authenticators(?GLOBAL) + emqx_authn_chains:list_authenticators(?GLOBAL) ) end, InvalidConfigs @@ -149,7 +148,7 @@ t_create_with_config_values_wont_work(_Config) -> emqx_authn_test_lib:delete_config(?ResourceID), ?assertEqual( {error, {not_found, {chain, ?GLOBAL}}}, - emqx_authentication:list_authenticators(?GLOBAL) + emqx_authn_chains:list_authenticators(?GLOBAL) ) end, InvalidConfigs1 @@ -171,7 +170,7 @@ test_create_invalid_config(InvalidAuthConfig, Path) -> ?assertMatch( {error, #{ kind := validation_error, - matched_type := "authn:redis_single", + matched_type := "redis_single", path := Path }}, emqx:update_config(?PATH, {create_authenticator, ?GLOBAL, InvalidAuthConfig}) @@ -179,7 +178,7 @@ test_create_invalid_config(InvalidAuthConfig, Path) -> ?assertMatch([], emqx_config:get_raw([authentication])), ?assertEqual( {error, {not_found, {chain, ?GLOBAL}}}, - emqx_authentication:list_authenticators(?GLOBAL) + emqx_authn_chains:list_authenticators(?GLOBAL) ), ok. @@ -207,7 +206,7 @@ test_user_auth( ), {ok, [#{provider := emqx_authn_redis, state := State}]} = - emqx_authentication:list_authenticators(?GLOBAL), + emqx_authn_chains:list_authenticators(?GLOBAL), Credentials = Credentials0#{ listener => 'tcp:default', @@ -237,7 +236,7 @@ t_destroy(_Config) -> ), {ok, [#{provider := emqx_authn_redis, state := State}]} = - emqx_authentication:list_authenticators(?GLOBAL), + emqx_authn_chains:list_authenticators(?GLOBAL), {ok, _} = emqx_authn_redis:authenticate( #{ diff --git a/apps/emqx_authn/test/emqx_authn_redis_tls_SUITE.erl b/apps/emqx_auth_redis/test/emqx_authn_redis_tls_SUITE.erl similarity index 97% rename from apps/emqx_authn/test/emqx_authn_redis_tls_SUITE.erl rename to apps/emqx_auth_redis/test/emqx_authn_redis_tls_SUITE.erl index 291caed1b..4ee427cca 100644 --- a/apps/emqx_authn/test/emqx_authn_redis_tls_SUITE.erl +++ b/apps/emqx_auth_redis/test/emqx_authn_redis_tls_SUITE.erl @@ -19,7 +19,7 @@ -compile(nowarn_export_all). -compile(export_all). --include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). @@ -35,7 +35,6 @@ groups() -> []. init_per_testcase(_, Config) -> - emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( [authentication], ?GLOBAL @@ -45,7 +44,7 @@ init_per_testcase(_, Config) -> init_per_suite(Config) -> case emqx_common_test_helpers:is_tcp_server_available(?REDIS_HOST, ?REDIS_TLS_PORT) of true -> - Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn], #{ + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_auth, emqx_auth_redis], #{ work_dir => ?config(priv_dir, Config) }), [{apps, Apps} | Config]; diff --git a/apps/emqx_authz/test/emqx_authz_redis_SUITE.erl b/apps/emqx_auth_redis/test/emqx_authz_redis_SUITE.erl similarity index 94% rename from apps/emqx_authz/test/emqx_authz_redis_SUITE.erl rename to apps/emqx_auth_redis/test/emqx_authz_redis_SUITE.erl index 28110a7a5..962333cd2 100644 --- a/apps/emqx_authz/test/emqx_authz_redis_SUITE.erl +++ b/apps/emqx_auth_redis/test/emqx_authz_redis_SUITE.erl @@ -20,7 +20,7 @@ -compile(export_all). -include("emqx_connector.hrl"). --include("emqx_authz.hrl"). +-include_lib("emqx_auth/include/emqx_authz.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). @@ -34,25 +34,28 @@ groups() -> emqx_authz_test_lib:table_groups(t_run_case, cases()). init_per_suite(Config) -> - ok = stop_apps([emqx_resource]), case emqx_common_test_helpers:is_tcp_server_available(?REDIS_HOST, ?REDIS_DEFAULT_PORT) of true -> - ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1 + Apps = emqx_cth_suite:start( + [ + emqx, + {emqx_conf, + "authorization.no_match = deny, authorization.cache.enable = false"}, + emqx_auth, + emqx_auth_redis + ], + #{work_dir => ?config(priv_dir, Config)} ), - ok = start_apps([emqx_resource]), ok = create_redis_resource(), - Config; + [{suite_apps, Apps} | Config]; false -> {skip, no_redis} end. -end_per_suite(_Config) -> +end_per_suite(Config) -> ok = emqx_authz_test_lib:restore_authorizers(), ok = emqx_resource:remove_local(?REDIS_RESOURCE), - ok = stop_apps([emqx_resource]), - ok = emqx_common_test_helpers:stop_apps([emqx_conf, emqx_authz]). + ok = emqx_cth_suite:stop_apps(?config(suite_apps, Config)). init_per_group(Group, Config) -> [{test_case, emqx_authz_test_lib:get_case(Group, cases())} | Config]. @@ -67,11 +70,6 @@ end_per_testcase(_TestCase, _Config) -> _ = cleanup_redis(), ok. -set_special_configs(emqx_authz) -> - ok = emqx_authz_test_lib:reset_authorizers(); -set_special_configs(_) -> - ok. - %%------------------------------------------------------------------------------ %% Tests %%------------------------------------------------------------------------------ @@ -89,7 +87,6 @@ t_create_with_config_values_wont_work(_Config) -> InvalidConfigs = [ - AuthzConfig#{<<"server">> => <<"unknownhost:3333">>}, AuthzConfig#{<<"password">> => <<"wrongpass">>}, AuthzConfig#{<<"database">> => <<"5678">>} ], @@ -368,7 +365,7 @@ stop_apps(Apps) -> create_redis_resource() -> {ok, _} = emqx_resource:create_local( ?REDIS_RESOURCE, - ?RESOURCE_GROUP, + ?AUTHZ_RESOURCE_GROUP, emqx_redis, redis_config(), #{} diff --git a/apps/emqx_authn/docker-ct b/apps/emqx_authn/docker-ct deleted file mode 100644 index 7b9a4c068..000000000 --- a/apps/emqx_authn/docker-ct +++ /dev/null @@ -1,4 +0,0 @@ -mongo -redis -mysql -pgsql diff --git a/apps/emqx_authn/src/emqx_authn.app.src b/apps/emqx_authn/src/emqx_authn.app.src deleted file mode 100644 index 0c6c73990..000000000 --- a/apps/emqx_authn/src/emqx_authn.app.src +++ /dev/null @@ -1,26 +0,0 @@ -%% -*- mode: erlang -*- -{application, emqx_authn, [ - {description, "EMQX Authentication"}, - {vsn, "0.1.28"}, - {modules, []}, - {registered, [emqx_authn_sup, emqx_authn_registry]}, - {applications, [ - kernel, - stdlib, - emqx_resource, - emqx_connector, - ehttpc, - epgsql, - mysql, - jose, - emqx_mongodb, - emqx_redis, - emqx_mysql, - emqx_bridge_http - ]}, - {mod, {emqx_authn_app, []}}, - {env, []}, - {licenses, ["Apache-2.0"]}, - {maintainers, ["EMQX Team "]}, - {links, [{"Homepage", "https://emqx.io/"}]} -]}. diff --git a/apps/emqx_authn/src/emqx_authn_enterprise.erl b/apps/emqx_authn/src/emqx_authn_enterprise.erl deleted file mode 100644 index 784074578..000000000 --- a/apps/emqx_authn/src/emqx_authn_enterprise.erl +++ /dev/null @@ -1,28 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. -%%-------------------------------------------------------------------- - --module(emqx_authn_enterprise). - --export([providers/0, resource_provider/0]). - --if(?EMQX_RELEASE_EDITION == ee). - -providers() -> - [ - {{password_based, ldap}, emqx_ldap_authn}, - {{password_based, ldap_bind}, emqx_ldap_authn_bind}, - {gcp_device, emqx_gcp_device_authn} - ]. - -resource_provider() -> - [emqx_ldap_authn, emqx_ldap_authn_bind]. - --else. - -providers() -> - []. - -resource_provider() -> - []. --endif. diff --git a/apps/emqx_authz/.gitignore b/apps/emqx_authz/.gitignore deleted file mode 100644 index f1c455451..000000000 --- a/apps/emqx_authz/.gitignore +++ /dev/null @@ -1,19 +0,0 @@ -.rebar3 -_* -.eunit -*.o -*.beam -*.plt -*.swp -*.swo -.erlang.cookie -ebin -log -erl_crash.dump -.rebar -logs -_build -.idea -*.iml -rebar3.crashdump -*~ diff --git a/apps/emqx_authz/README.md b/apps/emqx_authz/README.md deleted file mode 100644 index af543e478..000000000 --- a/apps/emqx_authz/README.md +++ /dev/null @@ -1,145 +0,0 @@ -# emqx_authz - -## Configure - -File: etc/plugins/authz.conf - -```json -authz:{ - rules: [ - { - type: mysql - config: { - server: "127.0.0.1:3306" - database: mqtt - pool_size: 1 - username: root - password: public - ssl: { - enable: true - cacertfile: "etc/certs/cacert.pem" - certfile: "etc/certs/client-cert.pem" - keyfile: "etc/certs/client-key.pem" - } - } - sql: "select ipaddress, username, clientid, action, permission, topic from mqtt_authz where ipaddr = ${peerhost} or username = ${username} or clientid = ${clientid}" - }, - { - type: postgresql - config: { - server: "127.0.0.1:5432" - database: mqtt - pool_size: 1 - username: root - password: public - ssl: {enable: false} - } - sql: "select ipaddress, username, clientid, action, permission, topic from mqtt_authz where ipaddr = ${peerhost} or username = ${username} or username = '$all' or clientid = ${clientid}" - }, - { - type: redis - config: { - servers: "127.0.0.1:6379" - database: 0 - pool_size: 1 - password: public - ssl: {enable: false} - } - cmd: "HGETALL mqtt_authz:${username}" - }, - { - principal: {username: "^admin?"} - permission: allow - action: subscribe - topics: ["$SYS/#"] - }, - { - permission: deny - action: subscribe - topics: ["$SYS/#"] - }, - { - permission: allow - action: all - topics: ["#"] - } - ] -} -``` - -## Database Management - -#### MySQL - -Create Example Table - -```sql -CREATE TABLE `mqtt_authz` ( - `id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `ipaddress` VARCHAR(60) NOT NULL DEFAULT '', - `username` VARCHAR(100) NOT NULL DEFAULT '', - `clientid` VARCHAR(100) NOT NULL DEFAULT '', - `action` ENUM('publish', 'subscribe', 'all') NOT NULL, - `permission` ENUM('allow', 'deny') NOT NULL, - `topic` VARCHAR(100) NOT NULL DEFAULT '', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -``` - -Sample data in the default configuration: - -```sql --- Only 127.0.0.1 users can subscribe to system topics -INSERT INTO mqtt_authz (ipaddress, username, clientid, action, permission, topic) VALUES ('127.0.0.1', '', '', 'subscribe', 'allow', '$SYS/#'); -``` - -#### PostgreSQL - -Create Example Table - -```sql -CREATE TYPE ACTION AS ENUM('publish','subscribe','all'); -CREATE TYPE PERMISSION AS ENUM('allow','deny'); - -CREATE TABLE mqtt_authz ( - id SERIAL PRIMARY KEY, - ipaddress CHARACTER VARYING(60) NOT NULL DEFAULT '', - username CHARACTER VARYING(100) NOT NULL DEFAULT '', - clientid CHARACTER VARYING(100) NOT NULL DEFAULT '', - action ACTION, - permission PERMISSION, - topic CHARACTER VARYING(100) NOT NULL -); - -``` - -Sample data in the default configuration: - -```sql --- Only 127.0.0.1 users can subscribe to system topics -INSERT INTO mqtt_authz (ipaddress, username, clientid, action, permission, topic) VALUES ('127.0.0.1', '', '', 'subscribe', 'allow', '$SYS/#'); -``` - -#### Redis - -Sample data in the default configuration: - -``` -HSET mqtt_authz:emqx '$SYS/#' subscribe -``` - -A rule of Redis AuthZ defines `publish`, `subscribe`, or `all `information. All lists in the rule are **allow** lists. - -#### MongoDB - -Create Example BSON documents -```sql -db.inventory.insertOne( - {username: "emqx", - clientid: "emqx", - ipaddress: "127.0.0.1", - permission: "allow", - action: "all", - topics: ["#"] - }) -``` diff --git a/apps/emqx_authz/docker-ct b/apps/emqx_authz/docker-ct deleted file mode 100644 index 7b9a4c068..000000000 --- a/apps/emqx_authz/docker-ct +++ /dev/null @@ -1,4 +0,0 @@ -mongo -redis -mysql -pgsql diff --git a/apps/emqx_authz/etc/emqx_authz.conf b/apps/emqx_authz/etc/emqx_authz.conf deleted file mode 100644 index 8b1378917..000000000 --- a/apps/emqx_authz/etc/emqx_authz.conf +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apps/emqx_authz/rebar.config b/apps/emqx_authz/rebar.config deleted file mode 100644 index ecfc81765..000000000 --- a/apps/emqx_authz/rebar.config +++ /dev/null @@ -1,19 +0,0 @@ -%% -*- mode: erlang -*- - -{erl_opts, [debug_info, nowarn_unused_import]}. -{deps, [ - {emqx, {path, "../emqx"}}, - {emqx_utils, {path, "../emqx_utils"}}, - {emqx_connector, {path, "../emqx_connector"}}, - {emqx_mongodb, {path, "../emqx_mongodb"}}, - {emqx_redis, {path, "../emqx_redis"}}, - {emqx_mysql, {path, "../emqx_mysql"}}, - {emqx_bridge_http, {path, "../emqx_bridge_http"}} -]}. - -{shell, [ - % {config, "config/sys.config"}, - {apps, [emqx_authz]} -]}. - -{project_plugins, [erlfmt]}. diff --git a/apps/emqx_authz/src/emqx_authz.app.src b/apps/emqx_authz/src/emqx_authz.app.src deleted file mode 100644 index ca1766a8c..000000000 --- a/apps/emqx_authz/src/emqx_authz.app.src +++ /dev/null @@ -1,23 +0,0 @@ -%% -*- mode: erlang -*- -{application, emqx_authz, [ - {description, "emqx authorization application"}, - {vsn, "0.1.26"}, - {registered, []}, - {mod, {emqx_authz_app, []}}, - {applications, [ - kernel, - stdlib, - crypto, - emqx_resource, - emqx_connector, - emqx_mongodb, - emqx_redis, - emqx_mysql, - emqx_bridge_http - ]}, - {env, []}, - {modules, []}, - - {licenses, ["Apache 2.0"]}, - {links, []} -]}. diff --git a/apps/emqx_authz/src/emqx_authz.appup.src b/apps/emqx_authz/src/emqx_authz.appup.src deleted file mode 100644 index 2cefe9f7b..000000000 --- a/apps/emqx_authz/src/emqx_authz.appup.src +++ /dev/null @@ -1,11 +0,0 @@ -%% -*- mode: erlang -*- -%% Unless you know what you are doing, DO NOT edit manually!! -{VSN, - [{<<"0\\.1\\.[0-1]">>,[ - {load_module,emqx_authz_utils,brutal_purge,soft_purge,[]}, - {load_module,emqx_authz_http,brutal_purge,soft_purge,[]}]} - ], - [{<<"0\\.1\\.[0-1]">>,[ - {load_module,emqx_authz_utils,brutal_purge,soft_purge,[]}, - {load_module,emqx_authz_http,brutal_purge,soft_purge,[]}]} - ]}. diff --git a/apps/emqx_authz/src/emqx_authz_api_schema.erl b/apps/emqx_authz/src/emqx_authz_api_schema.erl deleted file mode 100644 index 7aa34c266..000000000 --- a/apps/emqx_authz/src/emqx_authz_api_schema.erl +++ /dev/null @@ -1,293 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --module(emqx_authz_api_schema). - --include("emqx_authz.hrl"). --include_lib("hocon/include/hoconsc.hrl"). --include_lib("emqx_connector/include/emqx_connector.hrl"). - --import(hoconsc, [mk/2, enum/1]). --import(emqx_schema, [mk_duration/2]). - --export([ - fields/1, - authz_sources_types/1 -]). - -%%------------------------------------------------------------------------------ -%% Hocon Schema -%%------------------------------------------------------------------------------ - -fields(file) -> - authz_common_fields(file) ++ - [ - {rules, #{ - type => binary(), - required => true, - example => - << - "{allow,{username,{re,\"^dashboard$\"}},subscribe,[\"$SYS/#\"]}.\n", - "{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}." - >>, - desc => ?DESC(rules) - }} - ]; -fields(http_get) -> - [ - {method, #{type => get, default => get, required => true, desc => ?DESC(method)}}, - {headers, fun headers_no_content_type/1} - ] ++ authz_http_common_fields(); -fields(http_post) -> - [ - {method, #{type => post, default => post, required => true, desc => ?DESC(method)}}, - {headers, fun headers/1} - ] ++ authz_http_common_fields(); -fields(built_in_database) -> - authz_common_fields(built_in_database); -fields(mongo_single) -> - authz_mongo_common_fields() ++ - emqx_mongodb:fields(single); -fields(mongo_rs) -> - authz_mongo_common_fields() ++ - emqx_mongodb:fields(rs); -fields(mongo_sharded) -> - authz_mongo_common_fields() ++ - emqx_mongodb:fields(sharded); -fields(mysql) -> - authz_common_fields(mysql) ++ - [{query, query()}] ++ - proplists:delete(prepare_statement, emqx_mysql:fields(config)); -fields(postgresql) -> - authz_common_fields(postgresql) ++ - [{query, query()}] ++ - proplists:delete(prepare_statement, emqx_connector_pgsql:fields(config)); -fields(redis_single) -> - authz_redis_common_fields() ++ - emqx_redis:fields(single); -fields(redis_sentinel) -> - authz_redis_common_fields() ++ - emqx_redis:fields(sentinel); -fields(redis_cluster) -> - authz_redis_common_fields() ++ - emqx_redis:fields(cluster); -fields(position) -> - [ - {position, - mk( - string(), - #{ - desc => ?DESC(position), - required => true, - in => body - } - )} - ]; -fields(MaybeEnterprise) -> - emqx_authz_enterprise:fields(MaybeEnterprise). - -%%------------------------------------------------------------------------------ -%% http type funcs - -authz_http_common_fields() -> - authz_common_fields(http) ++ - [ - {url, fun url/1}, - {body, - hoconsc:mk(map([{fuzzy, term(), binary()}]), #{ - required => false, desc => ?DESC(body) - })}, - {request_timeout, - mk_duration("Request timeout", #{ - required => false, default => <<"30s">>, desc => ?DESC(request_timeout) - })} - ] ++ - maps:to_list( - maps:without( - [ - pool_type - ], - maps:from_list(emqx_bridge_http_connector:fields(config)) - ) - ). - -url(type) -> binary(); -url(validator) -> [?NOT_EMPTY("the value of the field 'url' cannot be empty")]; -url(required) -> true; -url(desc) -> ?DESC(?FUNCTION_NAME); -url(_) -> undefined. - -headers(type) -> - map(); -headers(desc) -> - ?DESC(?FUNCTION_NAME); -headers(converter) -> - fun(Headers) -> - maps:merge(default_headers(), transform_header_name(Headers)) - end; -headers(default) -> - default_headers(); -headers(_) -> - undefined. - -headers_no_content_type(type) -> - map(); -headers_no_content_type(desc) -> - ?DESC(?FUNCTION_NAME); -headers_no_content_type(converter) -> - fun(Headers) -> - maps:without( - [<<"content-type">>], - maps:merge(default_headers_no_content_type(), transform_header_name(Headers)) - ) - end; -headers_no_content_type(default) -> - default_headers_no_content_type(); -headers_no_content_type(_) -> - undefined. - -%% headers -default_headers() -> - maps:put( - <<"content-type">>, - <<"application/json">>, - default_headers_no_content_type() - ). - -default_headers_no_content_type() -> - #{ - <<"accept">> => <<"application/json">>, - <<"cache-control">> => <<"no-cache">>, - <<"connection">> => <<"keep-alive">>, - <<"keep-alive">> => <<"timeout=30, max=1000">> - }. - -transform_header_name(Headers) -> - maps:fold( - fun(K0, V, Acc) -> - K = list_to_binary(string:to_lower(to_list(K0))), - maps:put(K, V, Acc) - end, - #{}, - Headers - ). - -%%------------------------------------------------------------------------------ -%% MonogDB type funcs - -authz_mongo_common_fields() -> - authz_common_fields(mongodb) ++ - [ - {collection, fun collection/1}, - {filter, fun filter/1} - ]. - -collection(type) -> binary(); -collection(desc) -> ?DESC(?FUNCTION_NAME); -collection(required) -> true; -collection(_) -> undefined. - -filter(type) -> - map(); -filter(desc) -> - ?DESC(?FUNCTION_NAME); -filter(required) -> - false; -filter(default) -> - #{}; -filter(_) -> - undefined. - -%%------------------------------------------------------------------------------ -%% Redis type funcs - -authz_redis_common_fields() -> - authz_common_fields(redis) ++ - [ - {cmd, - mk(binary(), #{ - required => true, - desc => ?DESC(cmd), - example => <<"HGETALL mqtt_authz">> - })} - ]. - -%%------------------------------------------------------------------------------ -%% Authz api type funcs - -authz_common_fields(Type) when is_atom(Type) -> - [ - {enable, fun enable/1}, - {type, #{ - type => Type, - default => Type, - required => true, - desc => ?DESC(type), - in => body - }} - ]. - -enable(type) -> boolean(); -enable(default) -> true; -enable(desc) -> ?DESC(?FUNCTION_NAME); -enable(_) -> undefined. - -%%------------------------------------------------------------------------------ -%% Authz DB query - -query() -> - #{ - type => binary(), - desc => ?DESC(query), - required => true, - validator => fun(S) -> - case size(S) > 0 of - true -> ok; - _ -> {error, "Request query"} - end - end - }. - -%%------------------------------------------------------------------------------ -%% Internal funcs - -authz_sources_types(Type) -> - case Type of - simple -> - [http, mongodb, redis]; - detailed -> - [ - http_get, - http_post, - mongo_single, - mongo_rs, - mongo_sharded, - redis_single, - redis_sentinel, - redis_cluster - ] - end ++ - [ - built_in_database, - mysql, - postgresql, - file - ] ++ emqx_authz_enterprise:authz_sources_types(). - -to_list(A) when is_atom(A) -> - atom_to_list(A); -to_list(B) when is_binary(B) -> - binary_to_list(B). diff --git a/apps/emqx_authz/src/emqx_authz_enterprise.erl b/apps/emqx_authz/src/emqx_authz_enterprise.erl deleted file mode 100644 index 7362a003a..000000000 --- a/apps/emqx_authz/src/emqx_authz_enterprise.erl +++ /dev/null @@ -1,66 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. -%%-------------------------------------------------------------------- --module(emqx_authz_enterprise). - --export([ - type_names/0, - fields/1, - is_enterprise_module/1, - authz_sources_types/0, - type/1, - desc/1 -]). - --if(?EMQX_RELEASE_EDITION == ee). - -%% type name set -type_names() -> - [ldap]. - -%% type -> type schema -fields(ldap) -> - emqx_ldap_authz:fields(config). - -%% type -> type module -is_enterprise_module(ldap) -> - {ok, emqx_ldap_authz}; -is_enterprise_module(_) -> - false. - -%% api sources set -authz_sources_types() -> - [ldap]. - -%% atom-able name -> type -type(<<"ldap">>) -> ldap; -type(ldap) -> ldap; -type(Unknown) -> throw({unknown_authz_source_type, Unknown}). - -desc(ldap) -> - emqx_ldap_authz:description(); -desc(_) -> - undefined. - --else. - --dialyzer({nowarn_function, [fields/1, type/1, desc/1]}). - -type_names() -> - []. - -fields(Any) -> - error({invalid_field, Any}). - -is_enterprise_module(_) -> - false. - -authz_sources_types() -> - []. - -%% should never happen if the input is type-checked by hocon schema -type(Unknown) -> throw({unknown_authz_source_type, Unknown}). - -desc(_) -> - undefined. --endif. diff --git a/apps/emqx_authz/src/emqx_authz_schema.erl b/apps/emqx_authz/src/emqx_authz_schema.erl deleted file mode 100644 index 6aa04cdc1..000000000 --- a/apps/emqx_authz/src/emqx_authz_schema.erl +++ /dev/null @@ -1,531 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --module(emqx_authz_schema). - --include("emqx_authz.hrl"). --include_lib("hocon/include/hoconsc.hrl"). --include_lib("emqx_connector/include/emqx_connector.hrl"). - --reflect_type([ - permission/0, - action/0 -]). - --type action() :: publish | subscribe | all. --type permission() :: allow | deny. - --import(emqx_schema, [mk_duration/2]). - --export([ - namespace/0, - roots/0, - tags/0, - fields/1, - validations/0, - desc/1, - authz_fields/0 -]). - --export([ - headers_no_content_type/1, - headers/1, - default_authz/0, - authz_common_fields/1 -]). - -%%-------------------------------------------------------------------- -%% Hocon Schema -%%-------------------------------------------------------------------- - -type_names() -> - [ - file, - http_get, - http_post, - builtin_db, - mongo_single, - mongo_rs, - mongo_sharded, - mysql, - postgresql, - redis_single, - redis_sentinel, - redis_cluster - ] ++ - emqx_authz_enterprise:type_names(). - -namespace() -> authz. - -tags() -> - [<<"Authorization">>]. - -%% @doc authorization schema is not exported -%% but directly used by emqx_schema -roots() -> []. - -fields("authorization") -> - authz_fields(); -fields(file) -> - authz_common_fields(file) ++ - [ - {path, - ?HOCON( - string(), - #{ - required => true, - validator => fun(Path) -> element(1, emqx_authz_file:validate(Path)) end, - desc => ?DESC(path) - } - )} - ]; -fields(http_get) -> - authz_common_fields(http) ++ - http_common_fields() ++ - [ - {method, method(get)}, - {headers, fun headers_no_content_type/1} - ]; -fields(http_post) -> - authz_common_fields(http) ++ - http_common_fields() ++ - [ - {method, method(post)}, - {headers, fun headers/1} - ]; -fields(builtin_db) -> - authz_common_fields(built_in_database); -fields(mongo_single) -> - authz_common_fields(mongodb) ++ - mongo_common_fields() ++ - emqx_mongodb:fields(single); -fields(mongo_rs) -> - authz_common_fields(mongodb) ++ - mongo_common_fields() ++ - emqx_mongodb:fields(rs); -fields(mongo_sharded) -> - authz_common_fields(mongodb) ++ - mongo_common_fields() ++ - emqx_mongodb:fields(sharded); -fields(mysql) -> - authz_common_fields(mysql) ++ - connector_fields(mysql) ++ - [{query, query()}]; -fields(postgresql) -> - authz_common_fields(postgresql) ++ - emqx_connector_pgsql:fields(config) ++ - [{query, query()}]; -fields(redis_single) -> - authz_common_fields(redis) ++ - connector_fields(redis, single) ++ - [{cmd, cmd()}]; -fields(redis_sentinel) -> - authz_common_fields(redis) ++ - connector_fields(redis, sentinel) ++ - [{cmd, cmd()}]; -fields(redis_cluster) -> - authz_common_fields(redis) ++ - connector_fields(redis, cluster) ++ - [{cmd, cmd()}]; -fields("metrics_status_fields") -> - [ - {"resource_metrics", ?HOCON(?R_REF("resource_metrics"), #{desc => ?DESC("metrics")})}, - {"node_resource_metrics", array("node_resource_metrics", "node_metrics")}, - {"metrics", ?HOCON(?R_REF("metrics"), #{desc => ?DESC("metrics")})}, - {"node_metrics", array("node_metrics")}, - {"status", ?HOCON(cluster_status(), #{desc => ?DESC("status")})}, - {"node_status", array("node_status")}, - {"node_error", array("node_error")} - ]; -fields("metrics") -> - [ - {"total", ?HOCON(integer(), #{desc => ?DESC("metrics_total")})}, - {"allow", ?HOCON(integer(), #{desc => ?DESC("allow")})}, - {"deny", ?HOCON(integer(), #{desc => ?DESC("deny")})}, - {"nomatch", ?HOCON(float(), #{desc => ?DESC("nomatch")})} - ] ++ common_rate_field(); -fields("node_metrics") -> - [ - node_name(), - {"metrics", ?HOCON(?R_REF("metrics"), #{desc => ?DESC("metrics")})} - ]; -fields("resource_metrics") -> - common_field(); -fields("node_resource_metrics") -> - [ - node_name(), - {"metrics", ?HOCON(?R_REF("resource_metrics"), #{desc => ?DESC("metrics")})} - ]; -fields("node_status") -> - [ - node_name(), - {"status", ?HOCON(status(), #{desc => ?DESC("node_status")})} - ]; -fields("node_error") -> - [ - node_name(), - {"error", ?HOCON(string(), #{desc => ?DESC("node_error")})} - ]; -fields(MaybeEnterprise) -> - emqx_authz_enterprise:fields(MaybeEnterprise). - -common_field() -> - [ - {"matched", ?HOCON(integer(), #{desc => ?DESC("matched")})}, - {"success", ?HOCON(integer(), #{desc => ?DESC("success")})}, - {"failed", ?HOCON(integer(), #{desc => ?DESC("failed")})} - ] ++ common_rate_field(). - -status() -> - hoconsc:enum([connected, disconnected, connecting]). - -cluster_status() -> - hoconsc:enum([connected, disconnected, connecting, inconsistent]). - -node_name() -> - {"node", ?HOCON(binary(), #{desc => ?DESC("node"), example => "emqx@127.0.0.1"})}. - -desc(?CONF_NS) -> - ?DESC(?CONF_NS); -desc(file) -> - ?DESC(file); -desc(http_get) -> - ?DESC(http_get); -desc(http_post) -> - ?DESC(http_post); -desc(builtin_db) -> - ?DESC(builtin_db); -desc(mongo_single) -> - ?DESC(mongo_single); -desc(mongo_rs) -> - ?DESC(mongo_rs); -desc(mongo_sharded) -> - ?DESC(mongo_sharded); -desc(mysql) -> - ?DESC(mysql); -desc(postgresql) -> - ?DESC(postgresql); -desc(redis_single) -> - ?DESC(redis_single); -desc(redis_sentinel) -> - ?DESC(redis_sentinel); -desc(redis_cluster) -> - ?DESC(redis_cluster); -desc(MaybeEnterprise) -> - emqx_authz_enterprise:desc(MaybeEnterprise). - -authz_common_fields(Type) -> - [ - {type, ?HOCON(Type, #{required => true, desc => ?DESC(type)})}, - {enable, ?HOCON(boolean(), #{default => true, desc => ?DESC(enable)})} - ]. - -http_common_fields() -> - [ - {url, fun url/1}, - {request_timeout, - mk_duration("Request timeout", #{ - required => false, default => <<"30s">>, desc => ?DESC(request_timeout) - })}, - {body, ?HOCON(map(), #{required => false, desc => ?DESC(body)})} - ] ++ - maps:to_list( - maps:without( - [ - pool_type - ], - maps:from_list(connector_fields(http)) - ) - ). - -mongo_common_fields() -> - [ - {collection, - ?HOCON(binary(), #{ - required => true, - desc => ?DESC(collection) - })}, - {filter, - ?HOCON(map(), #{ - required => false, - default => #{}, - desc => ?DESC(filter) - })} - ]. - -validations() -> - [ - {check_ssl_opts, fun check_ssl_opts/1} - ]. - -headers(type) -> - list({binary(), binary()}); -headers(desc) -> - ?DESC(?FUNCTION_NAME); -headers(converter) -> - fun(Headers) -> - maps:to_list(maps:merge(default_headers(), transform_header_name(Headers))) - end; -headers(default) -> - default_headers(); -headers(_) -> - undefined. - -headers_no_content_type(type) -> - list({binary(), binary()}); -headers_no_content_type(desc) -> - ?DESC(?FUNCTION_NAME); -headers_no_content_type(converter) -> - fun(Headers) -> - maps:to_list( - maps:without( - [<<"content-type">>], - maps:merge(default_headers_no_content_type(), transform_header_name(Headers)) - ) - ) - end; -headers_no_content_type(default) -> - default_headers_no_content_type(); -headers_no_content_type(validator) -> - fun(Headers) -> - case lists:keyfind(<<"content-type">>, 1, Headers) of - false -> ok; - _ -> {error, do_not_include_content_type} - end - end; -headers_no_content_type(_) -> - undefined. - -url(type) -> binary(); -url(desc) -> ?DESC(?FUNCTION_NAME); -url(validator) -> [?NOT_EMPTY("the value of the field 'url' cannot be empty")]; -url(required) -> true; -url(_) -> undefined. - -%%-------------------------------------------------------------------- -%% Internal functions -%%-------------------------------------------------------------------- - -default_headers() -> - maps:put( - <<"content-type">>, - <<"application/json">>, - default_headers_no_content_type() - ). - -default_headers_no_content_type() -> - #{ - <<"accept">> => <<"application/json">>, - <<"cache-control">> => <<"no-cache">>, - <<"connection">> => <<"keep-alive">>, - <<"keep-alive">> => <<"timeout=30, max=1000">> - }. - -transform_header_name(Headers) -> - maps:fold( - fun(K0, V, Acc) -> - K = list_to_binary(string:to_lower(to_list(K0))), - maps:put(K, V, Acc) - end, - #{}, - Headers - ). - -check_ssl_opts(Conf) -> - Sources = hocon_maps:get("authorization.sources", Conf, []), - lists:foreach( - fun - (#{<<"url">> := Url} = Source) -> - case emqx_authz_http:parse_url(Url) of - {<<"https", _/binary>>, _, _} -> - case emqx_utils_maps:deep_find([<<"ssl">>, <<"enable">>], Source) of - {ok, true} -> true; - {ok, false} -> throw({ssl_not_enable, Url}); - _ -> throw({ssl_enable_not_found, Url}) - end; - {<<"http", _/binary>>, _, _} -> - ok; - Bad -> - throw({bad_scheme, Url, Bad}) - end; - (_Source) -> - ok - end, - Sources - ). - -query() -> - ?HOCON(binary(), #{ - desc => ?DESC(query), - required => true, - validator => fun(S) -> - case size(S) > 0 of - true -> ok; - _ -> {error, "Request query"} - end - end - }). - -cmd() -> - ?HOCON(binary(), #{ - desc => ?DESC(cmd), - required => true, - validator => fun(S) -> - case size(S) > 0 of - true -> ok; - _ -> {error, "Request query"} - end - end - }). - -connector_fields(DB) -> - connector_fields(DB, config). -connector_fields(DB, Fields) when DB =:= redis; DB =:= mysql -> - connector_fields(DB, Fields, emqx); -connector_fields(DB, Fields) when DB =:= http -> - connector_fields(bridge_http_connector, Fields, emqx); -connector_fields(DB, Fields) -> - connector_fields(DB, Fields, emqx_connector). - -connector_fields(DB, Fields, Prefix) -> - Mod0 = io_lib:format("~ts_~ts", [Prefix, DB]), - Mod = - try - list_to_existing_atom(Mod0) - catch - error:badarg -> - list_to_atom(Mod0); - error:Reason -> - erlang:error(Reason) - end, - erlang:apply(Mod, fields, [Fields]). - -to_list(A) when is_atom(A) -> - atom_to_list(A); -to_list(B) when is_binary(B) -> - binary_to_list(B). - -common_rate_field() -> - [ - {"rate", ?HOCON(float(), #{desc => ?DESC("rate")})}, - {"rate_max", ?HOCON(float(), #{desc => ?DESC("rate_max")})}, - {"rate_last5m", ?HOCON(float(), #{desc => ?DESC("rate_last5m")})} - ]. - -method(Method) -> - ?HOCON(Method, #{required => true, desc => ?DESC(method)}). - -array(Ref) -> array(Ref, Ref). - -array(Ref, DescId) -> - ?HOCON(?ARRAY(?R_REF(Ref)), #{desc => ?DESC(DescId)}). - -select_union_member(#{<<"type">> := <<"mongodb">>} = Value) -> - MongoType = maps:get(<<"mongo_type">>, Value, undefined), - case MongoType of - <<"single">> -> - ?R_REF(mongo_single); - <<"rs">> -> - ?R_REF(mongo_rs); - <<"sharded">> -> - ?R_REF(mongo_sharded); - Else -> - throw(#{ - reason => "unknown_mongo_type", - expected => "single | rs | sharded", - got => Else - }) - end; -select_union_member(#{<<"type">> := <<"redis">>} = Value) -> - RedisType = maps:get(<<"redis_type">>, Value, undefined), - case RedisType of - <<"single">> -> - ?R_REF(redis_single); - <<"cluster">> -> - ?R_REF(redis_cluster); - <<"sentinel">> -> - ?R_REF(redis_sentinel); - Else -> - throw(#{ - reason => "unknown_redis_type", - expected => "single | cluster | sentinel", - got => Else - }) - end; -select_union_member(#{<<"type">> := <<"http">>} = Value) -> - RedisType = maps:get(<<"method">>, Value, undefined), - case RedisType of - <<"get">> -> - ?R_REF(http_get); - <<"post">> -> - ?R_REF(http_post); - Else -> - throw(#{ - reason => "unknown_http_method", - expected => "get | post", - got => Else - }) - end; -select_union_member(#{<<"type">> := <<"built_in_database">>}) -> - ?R_REF(builtin_db); -select_union_member(#{<<"type">> := Type}) -> - select_union_member_loop(Type, type_names()); -select_union_member(_) -> - throw("missing_type_field"). - -select_union_member_loop(TypeValue, []) -> - throw(#{ - reason => "unknown_authz_type", - got => TypeValue - }); -select_union_member_loop(TypeValue, [Type | Types]) -> - case TypeValue =:= atom_to_binary(Type) of - true -> - ?R_REF(Type); - false -> - select_union_member_loop(TypeValue, Types) - end. - -authz_fields() -> - Types = [?R_REF(Type) || Type <- type_names()], - UnionMemberSelector = - fun - (all_union_members) -> Types; - %% must return list - ({value, Value}) -> [select_union_member(Value)] - end, - [ - {sources, - ?HOCON( - ?ARRAY(?UNION(UnionMemberSelector)), - #{ - default => [default_authz()], - desc => ?DESC(sources), - %% doc_lift is force a root level reference instead of nesting sub-structs - extra => #{doc_lift => true}, - %% it is recommended to configure authz sources from dashboard - %% hence the importance level for config is low - importance => ?IMPORTANCE_LOW - } - )} - ]. - -default_authz() -> - #{ - <<"type">> => <<"file">>, - <<"enable">> => true, - <<"path">> => <<"${EMQX_ETC_DIR}/acl.conf">> - }. diff --git a/apps/emqx_bridge/test/emqx_bridge_api_SUITE.erl b/apps/emqx_bridge/test/emqx_bridge_api_SUITE.erl index f6129c09d..8992c20d7 100644 --- a/apps/emqx_bridge/test/emqx_bridge_api_SUITE.erl +++ b/apps/emqx_bridge/test/emqx_bridge_api_SUITE.erl @@ -75,7 +75,7 @@ -define(APPSPECS, [ emqx_conf, emqx, - emqx_authn, + emqx_auth, emqx_management, {emqx_rule_engine, "rule_engine { rules {} }"}, {emqx_bridge, "bridges {}"} @@ -1436,11 +1436,11 @@ get_raw_config(Path, Config) -> add_user_auth(Chain, AuthenticatorID, User, Config) -> Node = ?config(node, Config), - erpc:call(Node, emqx_authentication, add_user, [Chain, AuthenticatorID, User]). + erpc:call(Node, emqx_authn_chains, add_user, [Chain, AuthenticatorID, User]). delete_user_auth(Chain, AuthenticatorID, User, Config) -> Node = ?config(node, Config), - erpc:call(Node, emqx_authentication, delete_user, [Chain, AuthenticatorID, User]). + erpc:call(Node, emqx_authn_chains, delete_user, [Chain, AuthenticatorID, User]). str(S) when is_list(S) -> S; str(S) when is_binary(S) -> binary_to_list(S). diff --git a/apps/emqx_conf/rebar.config b/apps/emqx_conf/rebar.config index 1d2f23bd0..f83bd42bb 100644 --- a/apps/emqx_conf/rebar.config +++ b/apps/emqx_conf/rebar.config @@ -3,7 +3,7 @@ {erl_opts, [debug_info]}. {deps, [ {emqx, {path, "../emqx"}}, - {emqx_authn, {path, "../emqx_authn"}} + {emqx_auth, {path, "../emqx_auth"}} ]}. {shell, [ diff --git a/apps/emqx_conf/src/emqx_conf_cli.erl b/apps/emqx_conf/src/emqx_conf_cli.erl index 6cb53f8f3..ddabdae95 100644 --- a/apps/emqx_conf/src/emqx_conf_cli.erl +++ b/apps/emqx_conf/src/emqx_conf_cli.erl @@ -16,7 +16,7 @@ -module(emqx_conf_cli). -include("emqx_conf.hrl"). --include_lib("emqx_authn/include/emqx_authentication.hrl"). +-include_lib("emqx_auth/include/emqx_authn_chains.hrl"). -include_lib("emqx/include/logger.hrl"). -export([ diff --git a/apps/emqx_conf/src/emqx_conf_schema.erl b/apps/emqx_conf/src/emqx_conf_schema.erl index e4fb06ef5..97e8d50b0 100644 --- a/apps/emqx_conf/src/emqx_conf_schema.erl +++ b/apps/emqx_conf/src/emqx_conf_schema.erl @@ -70,7 +70,8 @@ emqx_mgmt_api_key_schema ]). -define(INJECTING_CONFIGS, [ - emqx_authn_schema + emqx_authn_schema, + emqx_authz_schema ]). %% 1 million default ports counter diff --git a/apps/emqx_conf/test/emqx_conf_cli_SUITE.erl b/apps/emqx_conf/test/emqx_conf_cli_SUITE.erl index e5356c2ea..e0386dd4d 100644 --- a/apps/emqx_conf/test/emqx_conf_cli_SUITE.erl +++ b/apps/emqx_conf/test/emqx_conf_cli_SUITE.erl @@ -27,11 +27,11 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_authz, emqx_authn]), + emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_auth]), Config. end_per_suite(_Config) -> - emqx_mgmt_api_test_util:end_suite([emqx_conf, emqx_authz, emqx_authn]). + emqx_mgmt_api_test_util:end_suite([emqx_conf, emqx_auth]). t_load_config(Config) -> Authz = authorization, diff --git a/apps/emqx_gateway/README.md b/apps/emqx_gateway/README.md index c71737639..40a25e83c 100644 --- a/apps/emqx_gateway/README.md +++ b/apps/emqx_gateway/README.md @@ -11,7 +11,7 @@ protocol access on EMQX. For example: - Configuration & Schema - HTTP/CLI management interfaces -The emqx_gateway application depends on `emqx`, `emqx_authn`, `emqx_authz`, `emqx_ctl` that +The emqx_gateway application depends on `emqx`, `emqx_auth`, ``emqx_auth_*`, `emqx_ctl` that provide the foundation for protocol access. More introduction: [Extended Protocol Gateway](https://www.emqx.io/docs/en/v5.0/gateway/gateway.html) diff --git a/apps/emqx_gateway/rebar.config b/apps/emqx_gateway/rebar.config index e78c8a44b..dc803f9b5 100644 --- a/apps/emqx_gateway/rebar.config +++ b/apps/emqx_gateway/rebar.config @@ -3,5 +3,5 @@ {deps, [ {emqx, {path, "../emqx"}}, {emqx_utils, {path, "../emqx_utils"}}, - {emqx_authn, {path, "../emqx_authn"}} + {emqx_auth, {path, "../emqx_auth"}} ]}. diff --git a/apps/emqx_gateway/src/emqx_gateway.app.src b/apps/emqx_gateway/src/emqx_gateway.app.src index 3fd769234..cca49a0ad 100644 --- a/apps/emqx_gateway/src/emqx_gateway.app.src +++ b/apps/emqx_gateway/src/emqx_gateway.app.src @@ -4,7 +4,7 @@ {vsn, "0.1.25"}, {registered, []}, {mod, {emqx_gateway_app, []}}, - {applications, [kernel, stdlib, emqx, emqx_authn, emqx_ctl]}, + {applications, [kernel, stdlib, emqx, emqx_auth, emqx_ctl]}, {env, []}, {modules, []}, {licenses, ["Apache 2.0"]}, diff --git a/apps/emqx_gateway/src/emqx_gateway.erl b/apps/emqx_gateway/src/emqx_gateway.erl index 0d5ea79b1..58b49be2f 100644 --- a/apps/emqx_gateway/src/emqx_gateway.erl +++ b/apps/emqx_gateway/src/emqx_gateway.erl @@ -126,7 +126,7 @@ get_basic_usage_info() -> get_authn_type(#{authentication := Authn = #{mechanism := Mechanism, backend := Backend}}) when is_atom(Mechanism), is_atom(Backend) -> - emqx_authentication_config:authenticator_id(Authn); + emqx_authn_config:authenticator_id(Authn); get_authn_type(_) -> <<"undefined">>. diff --git a/apps/emqx_gateway/src/emqx_gateway_api_authn_user_import.erl b/apps/emqx_gateway/src/emqx_gateway_api_authn_user_import.erl index 5848e3d55..30aeaf8fe 100644 --- a/apps/emqx_gateway/src/emqx_gateway_api_authn_user_import.erl +++ b/apps/emqx_gateway/src/emqx_gateway_api_authn_user_import.erl @@ -80,7 +80,7 @@ import_users(post, #{ File -> [{FileName, FileData}] = maps:to_list(maps:without([type], File)), case - emqx_authentication:import_users( + emqx_authn_chains:import_users( ChainName, AuthId, {FileName, FileData} ) of @@ -104,7 +104,7 @@ import_listener_users(post, #{ File -> [{FileName, FileData}] = maps:to_list(maps:without([type], File)), case - emqx_authentication:import_users( + emqx_authn_chains:import_users( ChainName, AuthId, {FileName, FileData} ) of diff --git a/apps/emqx_gateway/src/emqx_gateway_conf.erl b/apps/emqx_gateway/src/emqx_gateway_conf.erl index 376e63a5e..30c386f44 100644 --- a/apps/emqx_gateway/src/emqx_gateway_conf.erl +++ b/apps/emqx_gateway/src/emqx_gateway_conf.erl @@ -71,7 +71,7 @@ ]). -include_lib("emqx/include/logger.hrl"). --include_lib("emqx_authn/include/emqx_authentication.hrl"). +-include_lib("emqx_auth/include/emqx_authn_chains.hrl"). -define(AUTHN_BIN, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY). -type atom_or_bin() :: atom() | binary(). @@ -456,7 +456,7 @@ pre_config_update(?GATEWAY, {add_authn, GwName, Conf}, RawConf) -> case get_authn(GwName, RawConf) of undefined -> CertsDir = authn_certs_dir(GwName, Conf), - Conf1 = emqx_authentication_config:convert_certs(CertsDir, Conf), + Conf1 = emqx_authn_config:convert_certs(CertsDir, Conf), {ok, emqx_utils_maps:deep_merge( RawConf, @@ -473,7 +473,7 @@ pre_config_update(?GATEWAY, {add_authn, GwName, {LType, LName}, Conf}, RawConf) case maps:get(?AUTHN_BIN, Listener, undefined) of undefined -> CertsDir = authn_certs_dir(GwName, LType, LName, Conf), - Conf1 = emqx_authentication_config:convert_certs(CertsDir, Conf), + Conf1 = emqx_authn_config:convert_certs(CertsDir, Conf), NListener = maps:put(?AUTHN_BIN, Conf1, Listener), NGateway = #{ GwName => @@ -494,7 +494,7 @@ pre_config_update(?GATEWAY, {update_authn, GwName, Conf}, RawConf) -> badres_authn(not_found, GwName); _OldConf -> CertsDir = authn_certs_dir(GwName, Conf), - Conf1 = emqx_authentication_config:convert_certs(CertsDir, Conf), + Conf1 = emqx_authn_config:convert_certs(CertsDir, Conf), {ok, emqx_utils_maps:deep_put(Path, RawConf, Conf1)} end; pre_config_update(?GATEWAY, {update_authn, GwName, {LType, LName}, Conf}, RawConf) -> @@ -508,7 +508,7 @@ pre_config_update(?GATEWAY, {update_authn, GwName, {LType, LName}, Conf}, RawCon badres_listener_authn(not_found, GwName, LType, LName); OldAuthnConf -> CertsDir = authn_certs_dir(GwName, LType, LName, OldAuthnConf), - Conf1 = emqx_authentication_config:convert_certs(CertsDir, Conf), + Conf1 = emqx_authn_config:convert_certs(CertsDir, Conf), NListener = maps:put( ?AUTHN_BIN, Conf1, @@ -872,12 +872,12 @@ certs_dir(GwName) when is_binary(GwName) -> authn_certs_dir(GwName, ListenerType, ListenerName, AuthnConf) -> ChainName = emqx_gateway_utils:listener_chain(GwName, ListenerType, ListenerName), - emqx_authentication_config:certs_dir(ChainName, AuthnConf). + emqx_authn_config:certs_dir(ChainName, AuthnConf). authn_certs_dir(GwName, AuthnConf) when is_binary(GwName) -> authn_certs_dir(binary_to_existing_atom(GwName), AuthnConf); authn_certs_dir(GwName, AuthnConf) -> - emqx_authentication_config:certs_dir( + emqx_authn_config:certs_dir( emqx_gateway_utils:global_chain(GwName), AuthnConf ). diff --git a/apps/emqx_gateway/src/emqx_gateway_http.erl b/apps/emqx_gateway/src/emqx_gateway_http.erl index 997539e7d..e8f0e034f 100644 --- a/apps/emqx_gateway/src/emqx_gateway_http.erl +++ b/apps/emqx_gateway/src/emqx_gateway_http.erl @@ -19,7 +19,7 @@ -include("include/emqx_gateway.hrl"). -include_lib("emqx/include/logger.hrl"). --include_lib("emqx_authn/include/emqx_authentication.hrl"). +-include_lib("emqx_auth/include/emqx_authn_chains.hrl"). -define(AUTHN, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM). @@ -257,7 +257,7 @@ authn(GwName, ListenerId) -> ). wrap_chain_name(ChainName, Conf) -> - case emqx_authentication:list_authenticators(ChainName) of + case emqx_authn_chains:list_authenticators(ChainName) of {ok, [#{id := Id} | _]} -> Conf#{chain_name => ChainName, id => Id}; _ -> diff --git a/apps/emqx_gateway/src/emqx_gateway_insta_sup.erl b/apps/emqx_gateway/src/emqx_gateway_insta_sup.erl index b40c2cab8..8dce8582d 100644 --- a/apps/emqx_gateway/src/emqx_gateway_insta_sup.erl +++ b/apps/emqx_gateway/src/emqx_gateway_insta_sup.erl @@ -280,7 +280,7 @@ disable_authns(State) -> remove_all_authns(#state{name = GwName, config = Config}) -> lists:foreach( fun({ChainName, _}) -> - case emqx_authentication:delete_chain(ChainName) of + case emqx_authn_chains:delete_chain(ChainName) of ok -> ok; {error, {not_found, _}} -> @@ -297,9 +297,9 @@ remove_all_authns(#state{name = GwName, config = Config}) -> ). ensure_authenticator_created(ChainName, Confs) -> - case emqx_authentication:list_authenticators(ChainName) of + case emqx_authn_chains:list_authenticators(ChainName) of {ok, [#{id := AuthenticatorId}]} -> - case emqx_authentication:update_authenticator(ChainName, AuthenticatorId, Confs) of + case emqx_authn_chains:update_authenticator(ChainName, AuthenticatorId, Confs) of {ok, _} -> ok; {error, Reason} -> {error, {badauth, Reason}} end; @@ -334,7 +334,7 @@ authn_conf(Conf) -> maps:get(authentication, Conf, undefined). do_create_authenticator(ChainName, AuthConf) -> - case emqx_authentication:create_authenticator(ChainName, AuthConf) of + case emqx_authn_chains:create_authenticator(ChainName, AuthConf) of {ok, _} -> ok; {error, Reason} -> @@ -403,7 +403,7 @@ remove_deleted_authns(NAuthns, OAuthns) -> DeletedNames = ONames -- NNames, lists:foreach( fun(ChainName) -> - _ = emqx_authentication:delete_chain(ChainName) + _ = emqx_authn_chains:delete_chain(ChainName) end, DeletedNames ). diff --git a/apps/emqx_gateway/src/emqx_gateway_schema.erl b/apps/emqx_gateway/src/emqx_gateway_schema.erl index 8d9cc5a11..e58e552e2 100644 --- a/apps/emqx_gateway/src/emqx_gateway_schema.erl +++ b/apps/emqx_gateway/src/emqx_gateway_schema.erl @@ -26,7 +26,7 @@ -include_lib("hocon/include/hoconsc.hrl"). -include_lib("typerefl/include/types.hrl"). --include_lib("emqx_authn/include/emqx_authentication.hrl"). +-include_lib("emqx_auth/include/emqx_authn_chains.hrl"). -type ip_port() :: tuple() | integer(). -type duration() :: non_neg_integer(). diff --git a/apps/emqx_gateway/src/emqx_gateway_utils.erl b/apps/emqx_gateway/src/emqx_gateway_utils.erl index 10c71e3a7..72751297b 100644 --- a/apps/emqx_gateway/src/emqx_gateway_utils.erl +++ b/apps/emqx_gateway/src/emqx_gateway_utils.erl @@ -334,7 +334,7 @@ is_running(ListenerId, ListenOn0) -> false end. -%% same with emqx_authentication:global_chain/1 +%% same with emqx_authn_chains:global_chain/1 -spec global_chain(GatewayName :: atom()) -> atom(). global_chain('mqttsn') -> 'mqtt-sn:global'; diff --git a/apps/emqx_gateway/test/emqx_gateway_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_SUITE.erl index 9f8c7911c..8c43c6721 100644 --- a/apps/emqx_gateway/test/emqx_gateway_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_gateway_SUITE.erl @@ -35,11 +35,11 @@ init_per_suite(Conf) -> emqx_config:erase(gateway), emqx_gateway_test_utils:load_all_gateway_apps(), emqx_common_test_helpers:load_config(emqx_gateway_schema, ?CONF_DEFAULT), - emqx_common_test_helpers:start_apps([emqx_authn, emqx_gateway]), + emqx_common_test_helpers:start_apps([emqx_auth, emqx_auth_redis, emqx_auth_mnesia, emqx_gateway]), Conf. end_per_suite(_Conf) -> - emqx_common_test_helpers:stop_apps([emqx_gateway, emqx_authn]), + emqx_common_test_helpers:stop_apps([emqx_gateway, emqx_auth_mnesia, emqx_auth_redis, emqx_auth]), emqx_config:delete_override_conf_files(), ok. diff --git a/apps/emqx_gateway/test/emqx_gateway_api_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_api_SUITE.erl index a3fe39852..9cda5bc23 100644 --- a/apps/emqx_gateway/test/emqx_gateway_api_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_gateway_api_SUITE.erl @@ -50,11 +50,11 @@ init_per_suite(Conf) -> emqx_config:delete_override_conf_files(), emqx_config:erase(gateway), emqx_common_test_helpers:load_config(emqx_gateway_schema, ?CONF_DEFAULT), - emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_authn, emqx_gateway]), + emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_auth, emqx_auth_mnesia, emqx_gateway]), Conf. end_per_suite(Conf) -> - emqx_mgmt_api_test_util:end_suite([emqx_gateway, emqx_authn, emqx_conf]), + emqx_mgmt_api_test_util:end_suite([emqx_gateway, emqx_auth_mnesia, emqx_auth, emqx_conf]), Conf. init_per_testcase(t_gateway_fail, Config) -> @@ -366,7 +366,7 @@ t_authn_data_mgmt(_) -> ["gateways", "stomp", "authentication", "import_users"] ), - Dir = code:lib_dir(emqx_authn, test), + Dir = code:lib_dir(emqx_auth, test), JSONFileName = filename:join([Dir, <<"data/user-credentials.json">>]), {ok, JSONData} = file:read_file(JSONFileName), {ok, 204, _} = emqx_dashboard_api_test_helpers:multipart_formdata_request(ImportUri, [], [ @@ -571,7 +571,7 @@ t_listeners_authn_data_mgmt(_) -> ["gateways", "stomp", "listeners", "stomp:tcp:def", "authentication", "import_users"] ), - Dir = code:lib_dir(emqx_authn, test), + Dir = code:lib_dir(emqx_auth, test), JSONFileName = filename:join([Dir, <<"data/user-credentials.json">>]), {ok, JSONData} = file:read_file(JSONFileName), {ok, 204, _} = emqx_dashboard_api_test_helpers:multipart_formdata_request(ImportUri, [], [ diff --git a/apps/emqx_gateway/test/emqx_gateway_auth_ct.erl b/apps/emqx_gateway/test/emqx_gateway_auth_ct.erl index 0ed66a38d..92bf95a69 100644 --- a/apps/emqx_gateway/test/emqx_gateway_auth_ct.erl +++ b/apps/emqx_gateway/test/emqx_gateway_auth_ct.erl @@ -40,7 +40,7 @@ ] ). --include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl"). diff --git a/apps/emqx_gateway/test/emqx_gateway_authn_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_authn_SUITE.erl index bb687ae58..f5e98bb37 100644 --- a/apps/emqx_gateway/test/emqx_gateway_authn_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_gateway_authn_SUITE.erl @@ -69,7 +69,7 @@ init_per_suite(Config) -> emqx_gateway_test_utils:load_all_gateway_apps(), emqx_config:erase(gateway), init_gateway_conf(), - emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_authn, emqx_gateway]), + emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_auth, emqx_auth_http, emqx_gateway]), application:ensure_all_started(cowboy), emqx_gateway_auth_ct:start(), timer:sleep(500), @@ -78,7 +78,7 @@ init_per_suite(Config) -> end_per_suite(Config) -> emqx_gateway_auth_ct:stop(), emqx_config:erase(gateway), - emqx_mgmt_api_test_util:end_suite([cowboy, emqx_conf, emqx_authn, emqx_gateway]), + emqx_mgmt_api_test_util:end_suite([cowboy, emqx_conf, emqx_auth, emqx_auth_http, emqx_gateway]), Config. init_per_testcase(_Case, Config) -> diff --git a/apps/emqx_gateway/test/emqx_gateway_authz_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_authz_SUITE.erl index 1934ce4ad..fed926bca 100644 --- a/apps/emqx_gateway/test/emqx_gateway_authz_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_gateway_authz_SUITE.erl @@ -70,7 +70,9 @@ init_per_suite(Config) -> init_gateway_conf(), meck:new(emqx_authz_file, [non_strict, passthrough, no_history, no_link]), meck:expect(emqx_authz_file, create, fun(S) -> S end), - emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_authz, emqx_authn, emqx_gateway]), + emqx_mgmt_api_test_util:init_suite([ + emqx_conf, emqx_auth, emqx_auth_file, emqx_auth_http, emqx_gateway + ]), application:ensure_all_started(cowboy), emqx_gateway_auth_ct:start(), Config. @@ -80,7 +82,9 @@ end_per_suite(Config) -> emqx_gateway_auth_ct:stop(), ok = emqx_authz_test_lib:restore_authorizers(), emqx_config:erase(gateway), - emqx_mgmt_api_test_util:end_suite([emqx_gateway, emqx_authn, emqx_authz, emqx_conf]), + emqx_mgmt_api_test_util:end_suite([ + emqx_gateway, emqx_auth_http, emqx_auth_file, emqx_auth, emqx_conf + ]), Config. init_per_testcase(_Case, Config) -> diff --git a/apps/emqx_gateway/test/emqx_gateway_cli_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_cli_SUITE.erl index fdaa55ab8..b2280bb20 100644 --- a/apps/emqx_gateway/test/emqx_gateway_cli_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_gateway_cli_SUITE.erl @@ -66,11 +66,11 @@ init_per_suite(Conf) -> emqx_config:erase(gateway), emqx_gateway_test_utils:load_all_gateway_apps(), emqx_common_test_helpers:load_config(emqx_gateway_schema, ?CONF_DEFAULT), - emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_authn, emqx_gateway]), + emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_auth, emqx_gateway]), Conf. end_per_suite(Conf) -> - emqx_mgmt_api_test_util:end_suite([emqx_gateway, emqx_authn, emqx_conf]), + emqx_mgmt_api_test_util:end_suite([emqx_gateway, emqx_auth, emqx_conf]), Conf. init_per_testcase(_, Conf) -> diff --git a/apps/emqx_gateway/test/emqx_gateway_conf_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_conf_SUITE.erl index 5deadaef1..216d08d5d 100644 --- a/apps/emqx_gateway/test/emqx_gateway_conf_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_gateway_conf_SUITE.erl @@ -39,11 +39,11 @@ all() -> init_per_suite(Conf) -> emqx_gateway_test_utils:load_all_gateway_apps(), emqx_common_test_helpers:load_config(emqx_gateway_schema, <<"gateway {}">>), - emqx_common_test_helpers:start_apps([emqx_conf, emqx_authn, emqx_gateway]), + emqx_common_test_helpers:start_apps([emqx_conf, emqx_auth, emqx_auth_mnesia, emqx_gateway]), Conf. end_per_suite(_Conf) -> - emqx_common_test_helpers:stop_apps([emqx_gateway, emqx_authn, emqx_conf]), + emqx_common_test_helpers:stop_apps([emqx_gateway, emqx_auth, emqx_auth_mnesia, emqx_conf]), emqx_config:delete_override_conf_files(). init_per_testcase(_CaseName, Conf) -> diff --git a/apps/emqx_gateway/test/emqx_gateway_registry_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_registry_SUITE.erl index a51621688..16e4d5e7d 100644 --- a/apps/emqx_gateway/test/emqx_gateway_registry_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_gateway_registry_SUITE.erl @@ -39,11 +39,11 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Cfg) -> emqx_gateway_test_utils:load_all_gateway_apps(), ok = emqx_common_test_helpers:load_config(emqx_gateway_schema, ?CONF_DEFAULT), - emqx_common_test_helpers:start_apps([emqx_authn, emqx_gateway]), + emqx_common_test_helpers:start_apps([emqx_auth, emqx_gateway]), Cfg. end_per_suite(_Cfg) -> - emqx_common_test_helpers:stop_apps([emqx_gateway, emqx_authn]), + emqx_common_test_helpers:stop_apps([emqx_gateway, emqx_auth]), ok. %%-------------------------------------------------------------------- diff --git a/apps/emqx_gateway_coap/test/emqx_coap_SUITE.erl b/apps/emqx_gateway_coap/test/emqx_coap_SUITE.erl index 091978172..39a73f201 100644 --- a/apps/emqx_gateway_coap/test/emqx_coap_SUITE.erl +++ b/apps/emqx_gateway_coap/test/emqx_coap_SUITE.erl @@ -58,12 +58,12 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> application:load(emqx_gateway_coap), ok = emqx_common_test_helpers:load_config(emqx_gateway_schema, ?CONF_DEFAULT), - emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_authn, emqx_gateway]), + emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_auth, emqx_gateway]), Config. end_per_suite(_) -> {ok, _} = emqx:remove_config([<<"gateway">>, <<"coap">>]), - emqx_mgmt_api_test_util:end_suite([emqx_gateway, emqx_authn, emqx_conf]). + emqx_mgmt_api_test_util:end_suite([emqx_gateway, emqx_auth, emqx_conf]). init_per_testcase(t_connection_with_authn_failed, Config) -> ok = meck:new(emqx_access_control, [passthrough]), diff --git a/apps/emqx_gateway_coap/test/emqx_coap_api_SUITE.erl b/apps/emqx_gateway_coap/test/emqx_coap_api_SUITE.erl index 6b7038eb8..71209e97d 100644 --- a/apps/emqx_gateway_coap/test/emqx_coap_api_SUITE.erl +++ b/apps/emqx_gateway_coap/test/emqx_coap_api_SUITE.erl @@ -58,12 +58,12 @@ all() -> init_per_suite(Config) -> application:load(emqx_gateway_coap), ok = emqx_common_test_helpers:load_config(emqx_gateway_schema, ?CONF_DEFAULT), - emqx_mgmt_api_test_util:init_suite([emqx_authn, emqx_gateway]), + emqx_mgmt_api_test_util:init_suite([emqx_auth, emqx_gateway]), Config. end_per_suite(Config) -> {ok, _} = emqx:remove_config([<<"gateway">>, <<"coap">>]), - emqx_mgmt_api_test_util:end_suite([emqx_gateway, emqx_authn]), + emqx_mgmt_api_test_util:end_suite([emqx_gateway, emqx_auth]), Config. %%-------------------------------------------------------------------- diff --git a/apps/emqx_gateway_exproto/test/emqx_exproto_SUITE.erl b/apps/emqx_gateway_exproto/test/emqx_exproto_SUITE.erl index 7b1ff5127..1c4c7ba08 100644 --- a/apps/emqx_gateway_exproto/test/emqx_exproto_SUITE.erl +++ b/apps/emqx_gateway_exproto/test/emqx_exproto_SUITE.erl @@ -128,7 +128,7 @@ init_per_group(LisType, ServiceName, Scheme, Cfg) -> Svrs = emqx_exproto_echo_svr:start(Scheme), application:load(emqx_gateway_exproto), emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authn, emqx_gateway], + [emqx_conf, emqx_auth, emqx_gateway], fun(App) -> set_special_cfg(App, LisType, ServiceName, Scheme) end @@ -143,7 +143,7 @@ init_per_group(LisType, ServiceName, Scheme, Cfg) -> end_per_group(_, Cfg) -> emqx_config:erase(gateway), - emqx_common_test_helpers:stop_apps([emqx_gateway, emqx_authn, emqx_conf]), + emqx_common_test_helpers:stop_apps([emqx_gateway, emqx_auth, emqx_conf]), emqx_exproto_echo_svr:stop(proplists:get_value(servers, Cfg)). init_per_testcase(TestCase, Cfg) when diff --git a/apps/emqx_gateway_lwm2m/test/emqx_lwm2m_api_SUITE.erl b/apps/emqx_gateway_lwm2m/test/emqx_lwm2m_api_SUITE.erl index 14fe7f0ae..2df3411d7 100644 --- a/apps/emqx_gateway_lwm2m/test/emqx_lwm2m_api_SUITE.erl +++ b/apps/emqx_gateway_lwm2m/test/emqx_lwm2m_api_SUITE.erl @@ -62,13 +62,13 @@ init_per_suite(Config) -> application:load(emqx_gateway_lwm2m), DefaultConfig = emqx_lwm2m_SUITE:default_config(), ok = emqx_common_test_helpers:load_config(emqx_gateway_schema, DefaultConfig), - emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_authn]), + emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_auth]), Config. end_per_suite(Config) -> timer:sleep(300), {ok, _} = emqx_conf:remove([<<"gateway">>, <<"lwm2m">>], #{}), - emqx_mgmt_api_test_util:end_suite([emqx_authn, emqx_conf]), + emqx_mgmt_api_test_util:end_suite([emqx_auth, emqx_conf]), Config. init_per_testcase(_AllTestCase, Config) -> diff --git a/apps/emqx_gateway_mqttsn/test/emqx_sn_protocol_SUITE.erl b/apps/emqx_gateway_mqttsn/test/emqx_sn_protocol_SUITE.erl index 1b5443451..5a82966c8 100644 --- a/apps/emqx_gateway_mqttsn/test/emqx_sn_protocol_SUITE.erl +++ b/apps/emqx_gateway_mqttsn/test/emqx_sn_protocol_SUITE.erl @@ -99,12 +99,12 @@ all() -> init_per_suite(Config) -> application:load(emqx_gateway_mqttsn), ok = emqx_common_test_helpers:load_config(emqx_gateway_schema, ?CONF_DEFAULT), - emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_authn, emqx_gateway]), + emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_auth, emqx_gateway]), Config. end_per_suite(_) -> {ok, _} = emqx:remove_config([gateway, mqttsn]), - emqx_mgmt_api_test_util:end_suite([emqx_gateway, emqx_authn, emqx_conf]). + emqx_mgmt_api_test_util:end_suite([emqx_gateway, emqx_auth, emqx_conf]). restart_mqttsn_with_subs_resume_on() -> Conf = emqx:get_raw_config([gateway, mqttsn]), diff --git a/apps/emqx_gateway_stomp/test/emqx_stomp_SUITE.erl b/apps/emqx_gateway_stomp/test/emqx_stomp_SUITE.erl index d9f0f4ce2..44d07d084 100644 --- a/apps/emqx_gateway_stomp/test/emqx_stomp_SUITE.erl +++ b/apps/emqx_gateway_stomp/test/emqx_stomp_SUITE.erl @@ -60,11 +60,11 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Cfg) -> application:load(emqx_gateway_stomp), ok = emqx_common_test_helpers:load_config(emqx_gateway_schema, ?CONF_DEFAULT), - emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_authn, emqx_gateway]), + emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_auth, emqx_gateway]), Cfg. end_per_suite(_Cfg) -> - emqx_mgmt_api_test_util:end_suite([emqx_gateway, emqx_authn, emqx_conf]), + emqx_mgmt_api_test_util:end_suite([emqx_gateway, emqx_auth, emqx_conf]), ok. default_config() -> diff --git a/apps/emqx_gcp_device/include/emqx_gcp_device.hrl b/apps/emqx_gcp_device/include/emqx_gcp_device.hrl new file mode 100644 index 000000000..adf14af0c --- /dev/null +++ b/apps/emqx_gcp_device/include/emqx_gcp_device.hrl @@ -0,0 +1,25 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-ifndef(EMQX_GCP_DEVICE_HRL). +-define(EMQX_GCP_DEVICE_HRL, true). + +-define(AUTHN_MECHANISM, gcp_device). +-define(AUTHN_MECHANISM_BIN, <<"gcp_device">>). + +-define(AUTHN_TYPE, ?AUTHN_MECHANISM). + +-endif. diff --git a/apps/emqx_gcp_device/rebar.config b/apps/emqx_gcp_device/rebar.config index 575a874ea..cb594b6f5 100644 --- a/apps/emqx_gcp_device/rebar.config +++ b/apps/emqx_gcp_device/rebar.config @@ -2,5 +2,5 @@ {deps, [ {emqx, {path, "../emqx"}}, {emqx_utils, {path, "../emqx_utils"}}, - {emqx_authn, {path, "../emqx_authn"}} + {emqx_auth, {path, "../emqx_auth"}} ]}. diff --git a/apps/emqx_gcp_device/src/emqx_gcp_device.app.src b/apps/emqx_gcp_device/src/emqx_gcp_device.app.src index 44f55314e..db1604e9e 100644 --- a/apps/emqx_gcp_device/src/emqx_gcp_device.app.src +++ b/apps/emqx_gcp_device/src/emqx_gcp_device.app.src @@ -6,7 +6,7 @@ {applications, [ kernel, stdlib, - emqx_authn + emqx_auth ]}, {env, []}, {modules, []}, diff --git a/apps/emqx_gcp_device/src/emqx_gcp_device.erl b/apps/emqx_gcp_device/src/emqx_gcp_device.erl index 84a2616af..ae976ce20 100644 --- a/apps/emqx_gcp_device/src/emqx_gcp_device.erl +++ b/apps/emqx_gcp_device/src/emqx_gcp_device.erl @@ -4,11 +4,12 @@ -module(emqx_gcp_device). --include_lib("emqx_authn/include/emqx_authn.hrl"). -include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/logger.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). +-define(AUTHN_SHARD, ?MODULE). + %% Management -export([put_device/1, get_device/1, remove_device/1]). %% Management: import @@ -126,7 +127,7 @@ import_devices(FormattedDevices) when is_list(FormattedDevices) -> -spec create_table() -> ok. create_table() -> ok = mria:create_table(?TAB, [ - {rlog_shard, ?AUTH_SHARD}, + {rlog_shard, ?AUTHN_SHARD}, {type, ordered_set}, {storage, disc_copies}, {record_name, emqx_gcp_device}, @@ -150,9 +151,9 @@ perform_dirty(Fun) -> -spec perform_dirty(async_dirty | sync_dirty, function()) -> term(). perform_dirty(async_dirty, Fun) -> - mria:async_dirty(?AUTH_SHARD, Fun); + mria:async_dirty(?AUTHN_SHARD, Fun); perform_dirty(sync_dirty, Fun) -> - mria:sync_dirty(?AUTH_SHARD, Fun). + mria:sync_dirty(?AUTHN_SHARD, Fun). -spec put_device_no_transaction(formatted_device()) -> ok. put_device_no_transaction( diff --git a/apps/emqx_gcp_device/src/emqx_gcp_device_app.erl b/apps/emqx_gcp_device/src/emqx_gcp_device_app.erl index a3d80b0a8..619f7c903 100644 --- a/apps/emqx_gcp_device/src/emqx_gcp_device_app.erl +++ b/apps/emqx_gcp_device/src/emqx_gcp_device_app.erl @@ -4,9 +4,9 @@ -module(emqx_gcp_device_app). --behaviour(application). +-include("emqx_gcp_device.hrl"). --emqx_plugin(?MODULE). +-behaviour(application). -export([ start/2, @@ -15,6 +15,7 @@ start(_StartType, _StartArgs) -> emqx_gcp_device:create_table(), + emqx_authn:register_provider(?AUTHN_TYPE, emqx_gcp_device_authn), emqx_gcp_device_sup:start_link(). stop(_State) -> diff --git a/apps/emqx_gcp_device/src/emqx_gcp_device_authn.erl b/apps/emqx_gcp_device/src/emqx_gcp_device_authn.erl index 956545c95..efbcd7ae5 100644 --- a/apps/emqx_gcp_device/src/emqx_gcp_device_authn.erl +++ b/apps/emqx_gcp_device/src/emqx_gcp_device_authn.erl @@ -4,60 +4,22 @@ -module(emqx_gcp_device_authn). --include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("emqx/include/logger.hrl"). --include_lib("hocon/include/hoconsc.hrl"). -include_lib("jose/include/jose_jwt.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl"). --behaviour(hocon_schema). - -export([ - namespace/0, - tags/0, - roots/0, - fields/1, - desc/1 -]). - --export([ - refs/0, create/2, update/2, authenticate/2, destroy/1 ]). -%%------------------------------------------------------------------------------ -%% Hocon Schema -%%------------------------------------------------------------------------------ - -namespace() -> "authn". - -tags() -> - [<<"Authentication">>]. - -%% used for config check when the schema module is resolved -roots() -> - [{?CONF_NS, hoconsc:mk(hoconsc:ref(gcp_device))}]. - -fields(gcp_device) -> - common_fields(). - -desc(gcp_device) -> - ?DESC(emqx_gcp_device_api, gcp_device); -desc(_) -> - undefined. - %%------------------------------------------------------------------------------ %% APIs %%------------------------------------------------------------------------------ -refs() -> - [ - hoconsc:ref(?MODULE, gcp_device) - ]. - create(_AuthenticatorID, _Config) -> {ok, #{}}. @@ -80,11 +42,6 @@ destroy(_State) -> %% Internal functions %%-------------------------------------------------------------------- -common_fields() -> - [ - {mechanism, emqx_authn_schema:mechanism('gcp_device')} - ] ++ emqx_authn_schema:common_fields(). - % The check logic is the following: %% 1. If clientid is not GCP-like or password is not a JWT, the result is ignore %% 2. If clientid is GCP-like and password is a JWT, but expired, the result is password_error diff --git a/apps/emqx_gcp_device/src/emqx_gcp_device_authn_schema.erl b/apps/emqx_gcp_device/src/emqx_gcp_device_authn_schema.erl new file mode 100644 index 000000000..43af4e4c2 --- /dev/null +++ b/apps/emqx_gcp_device/src/emqx_gcp_device_authn_schema.erl @@ -0,0 +1,45 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_gcp_device_authn_schema). + +-include("emqx_gcp_device.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). + +-behaviour(emqx_authn_schema). + +-export([ + fields/1, + refs/0, + select_union_member/1 +]). + +refs() -> [?R_REF(gcp_device)]. + +select_union_member(#{<<"mechanism">> := ?AUTHN_MECHANISM_BIN}) -> + [refs()]; +select_union_member(_Value) -> + undefined. + +fields(gcp_device) -> + [ + {mechanism, emqx_authn_schema:mechanism('gcp_device')} + ] ++ emqx_authn_schema:common_fields(). + +% desc(gcp_device) -> +% ?DESC(emqx_gcp_device_api, gcp_device); +% desc(_) -> +% undefined. diff --git a/apps/emqx_gcp_device/test/emqx_gcp_device_SUITE.erl b/apps/emqx_gcp_device/test/emqx_gcp_device_SUITE.erl index 4c8e89551..2f0b4d7b2 100644 --- a/apps/emqx_gcp_device/test/emqx_gcp_device_SUITE.erl +++ b/apps/emqx_gcp_device/test/emqx_gcp_device_SUITE.erl @@ -10,7 +10,7 @@ -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl"). --include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("emqx/include/emqx.hrl"). all() -> @@ -18,7 +18,7 @@ all() -> init_per_suite(Config) -> Apps = emqx_cth_suite:start( - [emqx, emqx_conf, emqx_authn, emqx_gcp_device, {emqx_retainer, "retainer {enable = true}"}], + [emqx, emqx_conf, emqx_auth, emqx_gcp_device, {emqx_retainer, "retainer {enable = true}"}], #{ work_dir => ?config(priv_dir, Config) } diff --git a/apps/emqx_gcp_device/test/emqx_gcp_device_api_SUITE.erl b/apps/emqx_gcp_device/test/emqx_gcp_device_api_SUITE.erl index 4ed34344e..1a728597e 100644 --- a/apps/emqx_gcp_device/test/emqx_gcp_device_api_SUITE.erl +++ b/apps/emqx_gcp_device/test/emqx_gcp_device_api_SUITE.erl @@ -10,7 +10,7 @@ -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl"). --include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("emqx/include/emqx.hrl"). -define(PATH, [authentication]). @@ -23,7 +23,7 @@ init_per_suite(Config) -> [ emqx, emqx_conf, - emqx_authn, + emqx_auth, {emqx_retainer, "retainer {enable = true}"}, emqx_management, {emqx_dashboard, "dashboard.listeners.http { enable = true, bind = 18083 }"}, diff --git a/apps/emqx_gcp_device/test/emqx_gcp_device_authn_SUITE.erl b/apps/emqx_gcp_device/test/emqx_gcp_device_authn_SUITE.erl index 23e69f4c5..a7a0e6d4d 100644 --- a/apps/emqx_gcp_device/test/emqx_gcp_device_authn_SUITE.erl +++ b/apps/emqx_gcp_device/test/emqx_gcp_device_authn_SUITE.erl @@ -10,7 +10,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl"). --include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("emqx_auth/include/emqx_authn.hrl"). -define(PATH, [authentication]). -define(DEVICE_ID, <<"test-device">>). @@ -23,7 +23,7 @@ all() -> init_per_suite(Config0) -> ok = snabbkaffe:start_trace(), - Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_authn, emqx_gcp_device], #{ + Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_auth, emqx_gcp_device], #{ work_dir => ?config(priv_dir, Config0) }), ValidExpirationTime = erlang:system_time(second) + 3600, @@ -65,7 +65,7 @@ t_create(_Config) -> {ok, _} = emqx:update_config(?PATH, {create_authenticator, ?GLOBAL, AuthConfig}), ?assertMatch( {ok, [#{provider := emqx_gcp_device_authn}]}, - emqx_authentication:list_authenticators(?GLOBAL) + emqx_authn_chains:list_authenticators(?GLOBAL) ). t_destroy(Config) -> @@ -95,7 +95,7 @@ t_expired_client(Config) -> {ok, _} = emqx:update_config(?PATH, {create_authenticator, ?GLOBAL, AuthConfig}), ?assertMatch( {ok, [#{provider := emqx_gcp_device_authn}]}, - emqx_authentication:list_authenticators(?GLOBAL) + emqx_authn_chains:list_authenticators(?GLOBAL) ), ok = emqx_gcp_device:put_device(Client), ?assertMatch( @@ -162,7 +162,7 @@ test_rsa_key(private) -> data_file("private_key.pem"). data_file(Name) -> - Dir = code:lib_dir(emqx_authn, test), + Dir = code:lib_dir(emqx_auth, test), list_to_binary(filename:join([Dir, "data", Name])). credential(ClientId, JWT) -> diff --git a/apps/emqx_ldap/README.md b/apps/emqx_ldap/README.md index 5923a10d7..64d6a4a0d 100644 --- a/apps/emqx_ldap/README.md +++ b/apps/emqx_ldap/README.md @@ -3,7 +3,7 @@ This application houses the LDAP connector. It provides the APIs to connect to the LDAP service. -It is used by the emqx_authz and emqx_authn applications to check user permissions. +It is used by the emqx_auth application to check user permissions. ## Contributing diff --git a/apps/emqx_ldap/rebar.config b/apps/emqx_ldap/rebar.config index abf0d192f..e6e2db243 100644 --- a/apps/emqx_ldap/rebar.config +++ b/apps/emqx_ldap/rebar.config @@ -3,7 +3,5 @@ {erl_opts, [debug_info]}. {deps, [ {emqx_connector, {path, "../../apps/emqx_connector"}}, - {emqx_resource, {path, "../../apps/emqx_resource"}}, - {emqx_authn, {path, "../../apps/emqx_authn"}}, - {emqx_authz, {path, "../../apps/emqx_authz"}} + {emqx_resource, {path, "../../apps/emqx_resource"}} ]}. diff --git a/apps/emqx_ldap/src/emqx_ldap.app.src b/apps/emqx_ldap/src/emqx_ldap.app.src index e6abb2363..5671aa07b 100644 --- a/apps/emqx_ldap/src/emqx_ldap.app.src +++ b/apps/emqx_ldap/src/emqx_ldap.app.src @@ -6,8 +6,8 @@ kernel, stdlib, eldap, - emqx_authn, - emqx_authz + emqx_connector, + emqx_resource ]}, {env, []}, {modules, []}, diff --git a/apps/emqx_machine/priv/reboot_lists.eterm b/apps/emqx_machine/priv/reboot_lists.eterm index b90c52d36..99209dae1 100644 --- a/apps/emqx_machine/priv/reboot_lists.eterm +++ b/apps/emqx_machine/priv/reboot_lists.eterm @@ -48,8 +48,15 @@ emqx_http_lib, emqx_resource, emqx_connector, - emqx_authn, - emqx_authz, + emqx_auth, + emqx_auth_file, + emqx_auth_http, + emqx_auth_jwt, + emqx_auth_mnesia, + emqx_auth_mongodb, + emqx_auth_mysql, + emqx_auth_postgresql, + emqx_auth_redis, emqx_auto_subscribe, emqx_gateway, emqx_gateway_stomp, @@ -114,6 +121,7 @@ emqx_node_rebalance, emqx_ft, emqx_ldap, + emqx_auth_ldap, emqx_gcp_device, emqx_dashboard_rbac, emqx_dashboard_sso diff --git a/apps/emqx_machine/test/emqx_machine_SUITE.erl b/apps/emqx_machine/test/emqx_machine_SUITE.erl index 2e3b85442..78e95d083 100644 --- a/apps/emqx_machine/test/emqx_machine_SUITE.erl +++ b/apps/emqx_machine/test/emqx_machine_SUITE.erl @@ -27,22 +27,6 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - %% CASE-SIDE-EFFICT: - %% - %% Running-Seq: - %% emqx_authz_api_mnesia_SUITE.erl - %% emqx_gateway_api_SUITE.erl - %% emqx_machine_SUITE.erl - %% - %% Reason: - %% the `emqx_machine_boot:ensure_apps_started()` will crash - %% on starting `emqx_authz` with dirty confs, which caused the file - %% `.._build/test/lib/emqx_conf/etc/acl.conf` could not be found - %% - %% Workaround: - %% Unload emqx_authz to avoid reboot this application - %% - application:unload(emqx_authz), emqx_common_test_helpers:start_apps([emqx_conf, emqx_opentelemetry]), application:set_env(emqx_machine, applications, [ emqx_prometheus, @@ -55,8 +39,7 @@ init_per_suite(Config) -> emqx_management, emqx_retainer, emqx_exhook, - emqx_authn, - emqx_authz, + emqx_auth, emqx_plugin, emqx_opentelemetry ]), diff --git a/apps/emqx_management/src/emqx_mgmt_data_backup.erl b/apps/emqx_management/src/emqx_mgmt_data_backup.erl index b677da2b2..0717e8285 100644 --- a/apps/emqx_management/src/emqx_mgmt_data_backup.erl +++ b/apps/emqx_management/src/emqx_mgmt_data_backup.erl @@ -480,7 +480,7 @@ read_data_files(RawConf) -> DataDir = bin(emqx:data_dir()), {ok, Cwd} = file:get_cwd(), AbsDataDir = bin(filename:join(Cwd, DataDir)), - RawConf1 = emqx_authz:maybe_read_acl_file(RawConf), + RawConf1 = emqx_authz:maybe_read_files(RawConf), emqx_utils_maps:deep_convert(RawConf1, fun read_data_file/4, [DataDir, AbsDataDir]). -define(dir_pattern(_Dir_), <<_Dir_:(byte_size(_Dir_))/binary, _/binary>>). @@ -512,7 +512,7 @@ do_read_file(FileName) -> validate_cluster_hocon(RawConf) -> %% write ACL file to comply with the schema... - RawConf1 = emqx_authz:maybe_write_acl_file(RawConf), + RawConf1 = emqx_authz:maybe_write_files(RawConf), emqx_hocon:check( emqx_conf:schema_module(), maps:merge(emqx:get_raw_config([]), RawConf1), diff --git a/apps/emqx_management/test/emqx_mgmt_data_backup_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_data_backup_SUITE.erl index e737c12b9..a9b3e36c6 100644 --- a/apps/emqx_management/test/emqx_mgmt_data_backup_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_data_backup_SUITE.erl @@ -22,7 +22,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl"). --define(ROLE_SUPERUSER, <<"superuser">>). +-define(ROLE_SUPERUSER, <<"administrator">>). -define(BOOTSTRAP_BACKUP, "emqx-export-test-bootstrap-ce.tar.gz"). all() -> @@ -397,7 +397,7 @@ users(Prefix) -> ]. authn_ids(AuthnConf) -> - lists:sort([emqx_authentication:authenticator_id(Conf) || Conf <- AuthnConf]). + lists:sort([emqx_authn_chains:authenticator_id(Conf) || Conf <- AuthnConf]). recompose_version(MajorInt, MinorInt, Patch) -> unicode:characters_to_list( @@ -438,8 +438,15 @@ apps_to_start() -> emqx_psk, emqx_management, emqx_dashboard, - emqx_authz, - emqx_authn, + emqx_auth, + emqx_auth_file, + emqx_auth_http, + emqx_auth_jwt, + emqx_auth_mnesia, + emqx_auth_mongodb, + emqx_auth_mysql, + emqx_auth_postgresql, + emqx_auth_redis, emqx_rule_engine, emqx_retainer, emqx_prometheus, diff --git a/apps/emqx_mongodb/README.md b/apps/emqx_mongodb/README.md index 7141a929e..4eee50958 100644 --- a/apps/emqx_mongodb/README.md +++ b/apps/emqx_mongodb/README.md @@ -1,7 +1,7 @@ # MongoDB Connector This application houses the MongoDB connector. The MongoDB connector is used by -emqx_authz, emqx_authn and emqx_bridge_mongodb applications to connect to +emqx_auth and emqx_bridge_mongodb applications to connect to MongoDB. ## Contributing diff --git a/apps/emqx_mysql/README.md b/apps/emqx_mysql/README.md index 04db049ed..75cc99779 100644 --- a/apps/emqx_mysql/README.md +++ b/apps/emqx_mysql/README.md @@ -3,8 +3,7 @@ This application houses the MySQL Database connector. It provides the APIs to connect to MySQL Databases. -It is used by the MySQL bridge to insert messages and by the emqx_authz and -emqx_authn applications to check user permissions. +It is used by the MySQL bridge to insert messages and by the `emqx_auth` application to check user permissions. ## Contributing diff --git a/apps/emqx_mysql/src/emqx_mysql.app.src b/apps/emqx_mysql/src/emqx_mysql.app.src index 04bc6aa53..a8b03df03 100644 --- a/apps/emqx_mysql/src/emqx_mysql.app.src +++ b/apps/emqx_mysql/src/emqx_mysql.app.src @@ -5,7 +5,9 @@ {applications, [ kernel, stdlib, - mysql + mysql, + emqx_connector, + emqx_resource ]}, {env, []}, {modules, []}, diff --git a/apps/emqx_redis/README.md b/apps/emqx_redis/README.md index 443b3f170..f6c141021 100644 --- a/apps/emqx_redis/README.md +++ b/apps/emqx_redis/README.md @@ -3,8 +3,7 @@ This application houses the Redis Database connector. It provides the APIs to connect to Redis Database. -It is used by the Redis bridge to insert messages and by the emqx_authz and -emqx_authn applications to check user permissions. +It is used by the Redis bridge to insert messages and by the `emqx_auth`application to check user permissions. ## Contributing diff --git a/apps/emqx_redis/src/emqx_redis.app.src b/apps/emqx_redis/src/emqx_redis.app.src index 23e13bb72..36f4b0cab 100644 --- a/apps/emqx_redis/src/emqx_redis.app.src +++ b/apps/emqx_redis/src/emqx_redis.app.src @@ -1,11 +1,13 @@ {application, emqx_redis, [ {description, "EMQX Redis Database Connector"}, - {vsn, "0.1.1"}, + {vsn, "0.1.2"}, {registered, []}, {applications, [ kernel, stdlib, - eredis_cluster + eredis_cluster, + emqx_connector, + emqx_resource ]}, {env, []}, {modules, []}, diff --git a/apps/emqx_redis/src/emqx_redis.erl b/apps/emqx_redis/src/emqx_redis.erl index 2779620bf..d07501bcd 100644 --- a/apps/emqx_redis/src/emqx_redis.erl +++ b/apps/emqx_redis/src/emqx_redis.erl @@ -175,8 +175,10 @@ on_start( end; _ -> case emqx_resource_pool:start(InstId, ?MODULE, Opts ++ [{options, Options}]) of - ok -> {ok, State}; - {error, Reason} -> {error, Reason} + ok -> + {ok, State}; + {error, Reason} -> + {error, Reason} end end. diff --git a/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl b/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl index f45cf2002..31b0e83fa 100644 --- a/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl +++ b/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl @@ -151,7 +151,7 @@ init_per_suite(Config) -> emqx_rule_funcs_demo:module_info(), application:load(emqx_conf), ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_rule_engine, emqx_authz, emqx_bridge], + [emqx_conf, emqx_rule_engine, emqx_auth, emqx_bridge], fun set_special_configs/1 ), Config. @@ -160,7 +160,7 @@ end_per_suite(_Config) -> emqx_common_test_helpers:stop_apps([emqx_conf, emqx_rule_engine]), ok. -set_special_configs(emqx_authz) -> +set_special_configs(emqx_auth) -> {ok, _} = emqx:update_config( [authorization], #{ diff --git a/apps/emqx_telemetry/test/emqx_telemetry_SUITE.erl b/apps/emqx_telemetry/test/emqx_telemetry_SUITE.erl index d8a50c528..31c9d25bd 100644 --- a/apps/emqx_telemetry/test/emqx_telemetry_SUITE.erl +++ b/apps/emqx_telemetry/test/emqx_telemetry_SUITE.erl @@ -43,20 +43,22 @@ apps() -> emqx_conf, emqx_management, emqx_retainer, - emqx_authn, - emqx_authz, + emqx_auth, + emqx_auth_redis, + emqx_auth_mnesia, + emqx_auth_postgresql, emqx_modules, emqx_telemetry ]. init_per_suite(Config) -> net_kernel:start(['master@127.0.0.1', longnames]), - ok = meck:new(emqx_authz, [non_strict, passthrough, no_history, no_link]), + ok = meck:new(emqx_authz_file, [non_strict, passthrough, no_history, no_link]), meck:expect( - emqx_authz, + emqx_authz_file, acl_conf_file, fun() -> - emqx_common_test_helpers:deps_path(emqx_authz, "etc/acl.conf") + emqx_common_test_helpers:deps_path(emqx_auth_file, "etc/acl.conf") end ), ok = emqx_common_test_helpers:load_config(emqx_modules_schema, ?MODULES_CONF), @@ -76,7 +78,7 @@ end_per_suite(_Config) -> mnesia:clear_table(cluster_rpc_commit), mnesia:clear_table(cluster_rpc_mfa), stop_apps(), - meck:unload(emqx_authz), + meck:unload(emqx_authz_file), ok. init_per_testcase(t_get_telemetry_without_memsup, Config) -> @@ -715,37 +717,33 @@ mock_advanced_mqtt_features() -> ok. create_authn(ChainName, built_in_database) -> - emqx_authentication:initialize_authentication( + emqx_authn_chains:create_authenticator( ChainName, - [ - #{ - mechanism => password_based, - backend => built_in_database, - enable => true, - user_id_type => username, - password_hash_algorithm => #{ - name => plain, - salt_position => suffix - } + #{ + mechanism => password_based, + backend => built_in_database, + enable => true, + user_id_type => username, + password_hash_algorithm => #{ + name => plain, + salt_position => suffix } - ] + } ); create_authn(ChainName, redis) -> - emqx_authentication:initialize_authentication( + emqx_authn_chains:create_authenticator( ChainName, - [ - #{ - mechanism => password_based, - backend => redis, - enable => true, - user_id_type => username, - cmd => "HMGET mqtt_user:${username} password_hash salt is_superuser", - password_hash_algorithm => #{ - name => plain, - salt_position => suffix - } + #{ + mechanism => password_based, + backend => redis, + enable => true, + user_id_type => username, + cmd => "HMGET mqtt_user:${username} password_hash salt is_superuser", + password_hash_algorithm => #{ + name => plain, + salt_position => suffix } - ] + } ). create_authz(postgresql) -> @@ -813,7 +811,7 @@ setup_fake_rule_engine_data() -> ), ok. -set_special_configs(emqx_authz) -> +set_special_configs(emqx_auth) -> {ok, _} = emqx:update_config([authorization, cache, enable], false), {ok, _} = emqx:update_config([authorization, no_match], deny), {ok, _} = emqx:update_config([authorization, sources], []), diff --git a/apps/emqx_telemetry/test/emqx_telemetry_api_SUITE.erl b/apps/emqx_telemetry/test/emqx_telemetry_api_SUITE.erl index 55e16bc3c..6ff573b4d 100644 --- a/apps/emqx_telemetry/test/emqx_telemetry_api_SUITE.erl +++ b/apps/emqx_telemetry/test/emqx_telemetry_api_SUITE.erl @@ -32,7 +32,7 @@ init_per_suite(Config) -> ok = emqx_common_test_helpers:load_config(emqx_modules_schema, ?BASE_CONF), ok = emqx_common_test_helpers:load_config(emqx_telemetry_schema, ?BASE_CONF), ok = emqx_mgmt_api_test_util:init_suite( - [emqx_conf, emqx_authn, emqx_management, emqx_authz, emqx_telemetry], + [emqx_conf, emqx_auth, emqx_auth_file, emqx_management, emqx_telemetry], fun set_special_configs/1 ), @@ -48,7 +48,7 @@ end_per_suite(_Config) -> } ), emqx_mgmt_api_test_util:end_suite([ - emqx_conf, emqx_authn, emqx_management, emqx_authz, emqx_telemetry + emqx_conf, emqx_auth, emqx_auth_file, emqx_management, emqx_telemetry ]), ok. diff --git a/changes/v5.0.15-en.md b/changes/v5.0.15-en.md index f77753c25..9987ab099 100644 --- a/changes/v5.0.15-en.md +++ b/changes/v5.0.15-en.md @@ -16,12 +16,12 @@ - [#9725](https://github.com/emqx/emqx/pull/9725) Remove the config `auto_reconnect` from the emqx_authz, emqx_authn and data-bridge componets. This is because we have another config with similar functions: `resource_opts.auto_restart_interval`。 - + The functions of these two config are difficult to distinguish, which will lead to confusion. After this change, `auto_reconnect` will not be configurable (always be true), and the underlying drivers that support this config will automatically reconnect the abnormally disconnected connection every `2s`. - + And the config `resource_opts.auto_restart_interval` is still available for user. It is the time interval that emqx restarts the resource when the connection cannot be established for some reason. @@ -55,7 +55,7 @@ Prior to this change, two configs were allow to change from dashboard, but will not take effect after reboot: * Logging (such as level) * Prometheus configs - + - [#9751](https://github.com/emqx/emqx/pull/9751) Fix that obsoleted cert file will not be deleted after the listener is updated/deleted @@ -74,6 +74,6 @@ - [#9781](https://github.com/emqx/emqx/pull/9781) Trace files were left on a node when creating a zip file for download. They are now removed when the file is sent. Also, concurrent downloads will no longer interfere with each other. -- [#9785](https://github.com/emqx/emqx/pull/9785) Stop authentication hook chain if `emqx_authentication` provides a definitive result. +- [#9785](https://github.com/emqx/emqx/pull/9785) Stop authentication hook chain if `emqx_authn_chains` provides a definitive result. - [#9787](https://github.com/emqx/emqx/pull/9787) Fix a compatible problem for the `webhook` bridge configuration which was created before the v5.0.12. diff --git a/changes/v5.0.15-zh.md b/changes/v5.0.15-zh.md index 679298613..6a4739a43 100644 --- a/changes/v5.0.15-zh.md +++ b/changes/v5.0.15-zh.md @@ -16,10 +16,10 @@ - [#9725](https://github.com/emqx/emqx/pull/9725) 从认证、鉴权和数据桥接功能中,删除 `auto_reconnect` 配置项,因为我们还有另一个功能类似的配置项: `resource_opts.auto_restart_interval`。 - + 这两个配置项的功能难以区分,会导致困惑。此修改之后,`auto_reconnect` 将不可配置(永远为 true), 支持此配置的底层驱动将以 `2s` 为周期自动重连异常断开的连接。 - + 而 `resource_opts.auto_restart_interval` 配置项仍然开放给用户配置,它是资源因为某些原因 无法建立连接的时候,emqx 重新启动该资源的时间间隔。 @@ -63,7 +63,7 @@ 必须用引把值括起来才行: `EMQX_FOOBAR__PASSWORD='"12344"'` 或 `emqx.foobar.password="1234"`。 修复后可以不使用引号。在环境变量重载中使用更加方便。 - + - [#9769](https://github.com/emqx/emqx/pull/9769) 修复 Eralng 控制台版本号前缀的打印错误 e5.0.15 -> v5.0.15 @@ -71,6 +71,6 @@ - [#9781](https://github.com/emqx/emqx/pull/9781) 当下载 日志追踪 的日志时,一些中间文件将存留在处理节点上,现在这个问题得到了修复。同时,并发下载日志将不再相互干扰。 -- [#9785](https://github.com/emqx/emqx/pull/9785) 如果 `emqx_authentication` 提供了确定的结果,则停止认证钩子链。 +- [#9785](https://github.com/emqx/emqx/pull/9785) 如果 `emqx_authn_chains` 提供了确定的结果,则停止认证钩子链。 - [#9787](https://github.com/emqx/emqx/pull/9787) 修复对在 v5.0.12 之前创建的 `webhook` 桥接配置的兼容问题。 diff --git a/dev b/dev index 2d204a0b4..3e6194aaf 100755 --- a/dev +++ b/dev @@ -330,7 +330,7 @@ EOF # copy cert files and acl.conf to etc copy_other_conf_files() { cp -r apps/emqx/etc/certs "$EMQX_ETC_DIR"/ - cp apps/emqx_authz/etc/acl.conf "$EMQX_ETC_DIR"/ + cp apps/emqx_auth_file/etc/acl.conf "$EMQX_ETC_DIR"/ } is_current_profile_app() { diff --git a/mix.exs b/mix.exs index 6b8db287c..dbe346a3d 100644 --- a/mix.exs +++ b/mix.exs @@ -226,6 +226,7 @@ defmodule EMQXUmbrella.MixProject do :emqx_bridge_kinesis, :emqx_bridge_azure_event_hub, :emqx_ldap, + :emqx_auth_ldap, :emqx_gcp_device, :emqx_dashboard_rbac, :emqx_dashboard_sso @@ -534,12 +535,12 @@ defmodule EMQXUmbrella.MixProject do ) Mix.Generator.copy_file( - "apps/emqx_authz/etc/acl.conf", + "apps/emqx_auth_file/etc/acl.conf", Path.join(etc, "acl.conf"), force: overwrite? ) - # required by emqx_authz + # required by emqx_auth File.cp_r!( "apps/emqx/etc/certs", Path.join(etc, "certs") diff --git a/rebar.config.erl b/rebar.config.erl index da30ec933..fede342bc 100644 --- a/rebar.config.erl +++ b/rebar.config.erl @@ -455,7 +455,7 @@ relx_overlay(ReleaseType, Edition) -> {copy, "bin/emqx_ctl", "bin/emqx_ctl-{{release_version}}"}, {copy, "bin/install_upgrade.escript", "bin/install_upgrade.escript-{{release_version}}"}, {copy, "apps/emqx_gateway_lwm2m/lwm2m_xml", "etc/lwm2m_xml"}, - {copy, "apps/emqx_authz/etc/acl.conf", "etc/acl.conf"}, + {copy, "apps/emqx_auth_file/etc/acl.conf", "etc/acl.conf"}, {template, "bin/emqx.cmd", "bin/emqx.cmd"}, {template, "bin/emqx_ctl.cmd", "bin/emqx_ctl.cmd"}, {copy, "bin/nodetool", "bin/nodetool"}, diff --git a/rel/i18n/emqx_authn_http.hocon b/rel/i18n/emqx_authn_http_schema.hocon similarity index 96% rename from rel/i18n/emqx_authn_http.hocon rename to rel/i18n/emqx_authn_http_schema.hocon index 582bacea8..def1f9dca 100644 --- a/rel/i18n/emqx_authn_http.hocon +++ b/rel/i18n/emqx_authn_http_schema.hocon @@ -1,4 +1,4 @@ -emqx_authn_http { +emqx_authn_http_schema { body.desc: """HTTP request body.""" diff --git a/rel/i18n/emqx_authn_jwt.hocon b/rel/i18n/emqx_authn_jwt_schema.hocon similarity index 99% rename from rel/i18n/emqx_authn_jwt.hocon rename to rel/i18n/emqx_authn_jwt_schema.hocon index f947dcf5d..cd0af6f42 100644 --- a/rel/i18n/emqx_authn_jwt.hocon +++ b/rel/i18n/emqx_authn_jwt_schema.hocon @@ -1,4 +1,4 @@ -emqx_authn_jwt { +emqx_authn_jwt_schema { acl_claim_name.desc: """JWT claim name to use for getting ACL rules.""" diff --git a/rel/i18n/emqx_ldap_authn_bind.hocon b/rel/i18n/emqx_authn_ldap_bind_schema.hocon similarity index 87% rename from rel/i18n/emqx_ldap_authn_bind.hocon rename to rel/i18n/emqx_authn_ldap_bind_schema.hocon index 66510c2a2..b0f20aa10 100644 --- a/rel/i18n/emqx_ldap_authn_bind.hocon +++ b/rel/i18n/emqx_authn_ldap_bind_schema.hocon @@ -1,4 +1,4 @@ -emqx_ldap_authn_bind { +emqx_authn_ldap_bind_schema { ldap_bind.desc: """Configuration of authenticator using the LDAP bind operation as the authentication method.""" diff --git a/rel/i18n/emqx_ldap_authn.hocon b/rel/i18n/emqx_authn_ldap_schema.hocon similarity index 95% rename from rel/i18n/emqx_ldap_authn.hocon rename to rel/i18n/emqx_authn_ldap_schema.hocon index 04dc88e83..41f57ffbc 100644 --- a/rel/i18n/emqx_ldap_authn.hocon +++ b/rel/i18n/emqx_authn_ldap_schema.hocon @@ -1,4 +1,4 @@ -emqx_ldap_authn { +emqx_authn_ldap_schema { ldap.desc: """Configuration of authenticator using LDAP as authentication data source.""" diff --git a/rel/i18n/emqx_authn_mnesia.hocon b/rel/i18n/emqx_authn_mnesia_schema.hocon similarity index 89% rename from rel/i18n/emqx_authn_mnesia.hocon rename to rel/i18n/emqx_authn_mnesia_schema.hocon index 6770df090..b0d1a8517 100644 --- a/rel/i18n/emqx_authn_mnesia.hocon +++ b/rel/i18n/emqx_authn_mnesia_schema.hocon @@ -1,4 +1,4 @@ -emqx_authn_mnesia { +emqx_authn_mnesia_schema { builtin_db.desc: """Configuration of authenticator using built-in database as data source.""" diff --git a/rel/i18n/emqx_authn_mongodb.hocon b/rel/i18n/emqx_authn_mongodb_schema.hocon similarity index 97% rename from rel/i18n/emqx_authn_mongodb.hocon rename to rel/i18n/emqx_authn_mongodb_schema.hocon index 6d851f58f..a544ec59b 100644 --- a/rel/i18n/emqx_authn_mongodb.hocon +++ b/rel/i18n/emqx_authn_mongodb_schema.hocon @@ -1,4 +1,4 @@ -emqx_authn_mongodb { +emqx_authn_mongodb_schema { collection.desc: """Collection used to store authentication data.""" diff --git a/rel/i18n/emqx_authn_mysql.hocon b/rel/i18n/emqx_authn_mysql_schema.hocon similarity index 92% rename from rel/i18n/emqx_authn_mysql.hocon rename to rel/i18n/emqx_authn_mysql_schema.hocon index 5eb2d23e9..28abad743 100644 --- a/rel/i18n/emqx_authn_mysql.hocon +++ b/rel/i18n/emqx_authn_mysql_schema.hocon @@ -1,4 +1,4 @@ -emqx_authn_mysql { +emqx_authn_mysql_schema { mysql.desc: """Configuration of authenticator using MySQL as authentication data source.""" diff --git a/rel/i18n/emqx_authn_pgsql.hocon b/rel/i18n/emqx_authn_postgresql_schema.hocon similarity index 87% rename from rel/i18n/emqx_authn_pgsql.hocon rename to rel/i18n/emqx_authn_postgresql_schema.hocon index 696a17861..fa26cfc5b 100644 --- a/rel/i18n/emqx_authn_pgsql.hocon +++ b/rel/i18n/emqx_authn_postgresql_schema.hocon @@ -1,4 +1,4 @@ -emqx_authn_pgsql { +emqx_authn_postgresql_schema { postgresql.desc: """Configuration of authenticator using PostgreSQL as authentication data source.""" diff --git a/rel/i18n/emqx_authn_redis.hocon b/rel/i18n/emqx_authn_redis_schema.hocon similarity index 95% rename from rel/i18n/emqx_authn_redis.hocon rename to rel/i18n/emqx_authn_redis_schema.hocon index e8d98b9a1..e51dbaeeb 100644 --- a/rel/i18n/emqx_authn_redis.hocon +++ b/rel/i18n/emqx_authn_redis_schema.hocon @@ -1,4 +1,4 @@ -emqx_authn_redis { +emqx_authn_redis_schema { cluster.desc: """Configuration of authenticator using Redis (Cluster) as authentication data source.""" diff --git a/rel/i18n/emqx_authz_api_schema.hocon b/rel/i18n/emqx_authz_api_schema.hocon deleted file mode 100644 index 2edfc8fcb..000000000 --- a/rel/i18n/emqx_authz_api_schema.hocon +++ /dev/null @@ -1,90 +0,0 @@ -emqx_authz_api_schema { - -body.desc: -"""HTTP request body.""" - -body.label: -"""body""" - -cmd.desc: -"""Database query used to retrieve authorization data.""" - -cmd.label: -"""cmd""" - -collection.desc: -"""`MongoDB` collection containing the authorization data.""" - -collection.label: -"""collection""" - -enable.desc: -"""Set to true or false to disable this ACL provider.""" - -enable.label: -"""enable""" - -filter.desc: -"""Conditional expression that defines the filter condition in the query. -Filter supports the following placeholders: - - ${username}: Will be replaced at runtime with Username used by the client when connecting; - - ${clientid}: Will be replaced at runtime with Client ID used by the client when connecting.""" - -filter.label: -"""Filter""" - -headers.desc: -"""List of HTTP Headers.""" - -headers.label: -"""Headers""" - -headers_no_content_type.desc: -"""List of HTTP headers (without content-type).""" - -headers_no_content_type.label: -"""headers_no_content_type""" - -method.desc: -"""HTTP method.""" - -method.label: -"""method""" - -position.desc: -"""Where to place the source.""" - -position.label: -"""position""" - -query.desc: -"""Database query used to retrieve authorization data.""" - -query.label: -"""query""" - -request_timeout.desc: -"""Request timeout.""" - -request_timeout.label: -"""request_timeout""" - -rules.desc: -"""Authorization static file rules.""" - -rules.label: -"""rules""" - -type.desc: -"""Backend type.""" - -type.label: -"""type""" - -url.desc: -"""URL of the auth server.""" - -url.label: -"""url""" - -} diff --git a/rel/i18n/emqx_authz_file_schema.hocon b/rel/i18n/emqx_authz_file_schema.hocon new file mode 100644 index 000000000..5226a4a37 --- /dev/null +++ b/rel/i18n/emqx_authz_file_schema.hocon @@ -0,0 +1,22 @@ +emqx_authz_file_schema { + +path.desc: +"""Path to the file which contains the ACL rules. +If the file provisioned before starting EMQX node, +it can be placed anywhere as long as EMQX has read access to it. +That is, EMQX will treat it as read only. + +In case the rule-set is created or updated from EMQX Dashboard or HTTP API, +a new file will be created and placed in `authz` subdirectory inside EMQX's `data_dir`, +and the old file will not be used anymore.""" + +path.label: +"""path""" + +file.desc: +"""Authorization using a static file.""" + +file.label: +"""file""" + +} diff --git a/rel/i18n/emqx_authz_http_schema.hocon b/rel/i18n/emqx_authz_http_schema.hocon new file mode 100644 index 000000000..fee29a126 --- /dev/null +++ b/rel/i18n/emqx_authz_http_schema.hocon @@ -0,0 +1,51 @@ +emqx_authz_http_schema { + +method.desc: +"""HTTP method.""" + +method.label: +"""method""" + +body.desc: +"""HTTP request body.""" + +body.label: +"""Request Body""" + +url.desc: +"""URL of the auth server.""" + +url.label: +"""URL""" + +headers.desc: +"""List of HTTP Headers.""" + +headers.label: +"""Headers""" + +headers_no_content_type.desc: +"""List of HTTP headers (without content-type).""" + +headers_no_content_type.label: +"""headers_no_content_type""" + +http_post.desc: +"""Authorization using an external HTTP server (via POST requests).""" + +http_post.label: +"""http_post""" + +request_timeout.desc: +"""HTTP request timeout.""" + +request_timeout.label: +"""Request Timeout""" + +http_get.desc: +"""Authorization using an external HTTP server (via GET requests).""" + +http_get.label: +"""http_get""" + +} diff --git a/rel/i18n/emqx_ldap_authz.hocon b/rel/i18n/emqx_authz_ldap_schema.hocon similarity index 96% rename from rel/i18n/emqx_ldap_authz.hocon rename to rel/i18n/emqx_authz_ldap_schema.hocon index 1ccb085f1..7f663412b 100644 --- a/rel/i18n/emqx_ldap_authz.hocon +++ b/rel/i18n/emqx_authz_ldap_schema.hocon @@ -1,4 +1,4 @@ -emqx_ldap_authz { +emqx_authz_ldap_schema { publish_attribute.desc: """Indicates which attribute is used to represent the allowed topics list of the `publish`.""" diff --git a/rel/i18n/emqx_authz_mnesia_schema.hocon b/rel/i18n/emqx_authz_mnesia_schema.hocon new file mode 100644 index 000000000..6face07b9 --- /dev/null +++ b/rel/i18n/emqx_authz_mnesia_schema.hocon @@ -0,0 +1,9 @@ +emqx_authz_mnesia_schema { + +builtin_db.desc: +"""Authorization using a built-in database (mnesia).""" + +builtin_db.label: +"""Builtin Database""" + +} diff --git a/rel/i18n/emqx_authz_mongodb_schema.hocon b/rel/i18n/emqx_authz_mongodb_schema.hocon new file mode 100644 index 000000000..c3064670e --- /dev/null +++ b/rel/i18n/emqx_authz_mongodb_schema.hocon @@ -0,0 +1,36 @@ +emqx_authz_mongodb_schema { + +mongo_rs.desc: +"""Authorization using a MongoDB replica set.""" + +mongo_rs.label: +"""mongo_rs""" + +mongo_sharded.desc: +"""Authorization using a sharded MongoDB cluster.""" + +mongo_sharded.label: +"""mongo_sharded""" + +mongo_single.desc: +"""Authorization using a single MongoDB instance.""" + +mongo_single.label: +"""mongo_single""" + +filter.desc: +"""Conditional expression that defines the filter condition in the query. +Filter supports the following placeholders
+ - ${username}: Will be replaced at runtime with Username used by the client when connecting
+ - ${clientid}: Will be replaced at runtime with Client ID used by the client when connecting""" + +filter.label: +"""Filter""" + +collection.desc: +"""`MongoDB` collection containing the authorization data.""" + +collection.label: +"""collection""" + +} diff --git a/rel/i18n/emqx_authz_mysql_schema.hocon b/rel/i18n/emqx_authz_mysql_schema.hocon new file mode 100644 index 000000000..225f2bf30 --- /dev/null +++ b/rel/i18n/emqx_authz_mysql_schema.hocon @@ -0,0 +1,15 @@ +emqx_authz_mysql_schema { + +query.desc: +"""Database query used to retrieve authorization data.""" + +query.label: +"""query""" + +mysql.desc: +"""Authorization using a MySQL database.""" + +mysql.label: +"""mysql""" + +} diff --git a/rel/i18n/emqx_authz_postgresql_schema.hocon b/rel/i18n/emqx_authz_postgresql_schema.hocon new file mode 100644 index 000000000..bac59bdbd --- /dev/null +++ b/rel/i18n/emqx_authz_postgresql_schema.hocon @@ -0,0 +1,15 @@ +emqx_authz_postgresql_schema { + +query.desc: +"""Database query used to retrieve authorization data.""" + +query.label: +"""query""" + +postgresql.desc: +"""Authorization using a PostgreSQL database.""" + +postgresql.label: +"""postgresql""" + +} diff --git a/rel/i18n/emqx_authz_redis_schema.hocon b/rel/i18n/emqx_authz_redis_schema.hocon new file mode 100644 index 000000000..38ba51f16 --- /dev/null +++ b/rel/i18n/emqx_authz_redis_schema.hocon @@ -0,0 +1,27 @@ +emqx_authz_redis_schema { + +redis_sentinel.desc: +"""Authorization using a Redis Sentinel.""" + +redis_sentinel.label: +"""redis_sentinel""" + +redis_cluster.desc: +"""Authorization using a Redis cluster.""" + +redis_cluster.label: +"""redis_cluster""" + +redis_single.desc: +"""Authorization using a single Redis instance.""" + +redis_single.label: +"""redis_single""" + +cmd.desc: +"""Database query used to retrieve authorization data.""" + +cmd.label: +"""cmd""" + +} diff --git a/rel/i18n/emqx_authz_schema.hocon b/rel/i18n/emqx_authz_schema.hocon index 5318eb769..5e11f26a6 100644 --- a/rel/i18n/emqx_authz_schema.hocon +++ b/rel/i18n/emqx_authz_schema.hocon @@ -6,12 +6,6 @@ deny.desc: deny.label: """The Number of Authentication Failures""" -redis_sentinel.desc: -"""Authorization using a Redis Sentinel.""" - -redis_sentinel.label: -"""redis_sentinel""" - rate.desc: """The rate of matched, times/second.""" @@ -24,108 +18,36 @@ status.desc: status.label: """Status""" -method.desc: -"""HTTP method.""" - -method.label: -"""method""" - -query.desc: -"""Database query used to retrieve authorization data.""" - -query.label: -"""query""" - metrics_total.desc: """The total number of times the authorization rule was triggered.""" metrics_total.label: """The Total Number of Times the Authorization Rule was Triggered""" -redis_cluster.desc: -"""Authorization using a Redis cluster.""" - -redis_cluster.label: -"""redis_cluster""" - -mysql.desc: -"""Authorization using a MySQL database.""" - -mysql.label: -"""mysql""" - -postgresql.desc: -"""Authorization using a PostgreSQL database.""" - -postgresql.label: -"""postgresql""" - -mongo_rs.desc: -"""Authorization using a MongoDB replica set.""" - -mongo_rs.label: -"""mongo_rs""" - type.desc: """Backend type.""" type.label: """type""" -mongo_sharded.desc: -"""Authorization using a sharded MongoDB cluster.""" - -mongo_sharded.label: -"""mongo_sharded""" - -body.desc: -"""HTTP request body.""" - -body.label: -"""Request Body""" - -url.desc: -"""URL of the auth server.""" - -url.label: -"""URL""" - node.desc: """Node name.""" node.label: """Node Name.""" -headers.desc: -"""List of HTTP Headers.""" - -headers.label: -"""Headers""" - rate_last5m.desc: """The average rate of matched in the last 5 minutes, times/second.""" rate_last5m.label: """Rate in Last 5min""" -headers_no_content_type.desc: -"""List of HTTP headers (without content-type).""" - -headers_no_content_type.label: -"""headers_no_content_type""" - node_error.desc: """The error of node.""" node_error.label: """Error in Node""" -builtin_db.desc: -"""Authorization using a built-in database (mnesia).""" - -builtin_db.label: -"""Builtin Database""" - enable.desc: """Set to true or false to disable this ACL provider""" @@ -150,34 +72,6 @@ rate_max.desc: rate_max.label: """Max Rate""" -filter.desc: -"""Conditional expression that defines the filter condition in the query. -Filter supports the following placeholders
- - ${username}: Will be replaced at runtime with Username used by the client when connecting
- - ${clientid}: Will be replaced at runtime with Client ID used by the client when connecting""" - -filter.label: -"""Filter""" - -path.desc: -"""Path to the file which contains the ACL rules. -If the file provisioned before starting EMQX node, -it can be placed anywhere as long as EMQX has read access to it. -That is, EMQX will treat it as read only. - -In case the rule-set is created or updated from EMQX Dashboard or HTTP API, -a new file will be created and placed in `authz` subdirectory inside EMQX's `data_dir`, -and the old file will not be used anymore.""" - -path.label: -"""path""" - -redis_single.desc: -"""Authorization using a single Redis instance.""" - -redis_single.label: -"""redis_single""" - failed.desc: """Count of query failed.""" @@ -196,48 +90,12 @@ authorization.desc: authorization.label: """authorization""" -collection.desc: -"""`MongoDB` collection containing the authorization data.""" - -collection.label: -"""collection""" - -mongo_single.desc: -"""Authorization using a single MongoDB instance.""" - -mongo_single.label: -"""mongo_single""" - -file.desc: -"""Authorization using a static file.""" - -file.label: -"""file""" - -http_post.desc: -"""Authorization using an external HTTP server (via POST requests).""" - -http_post.label: -"""http_post""" - -request_timeout.desc: -"""HTTP request timeout.""" - -request_timeout.label: -"""Request Timeout""" - allow.desc: """The number of times the authentication was successful.""" allow.label: """The Number of Times the Authentication was Successful""" -cmd.desc: -"""Database query used to retrieve authorization data.""" - -cmd.label: -"""cmd""" - nomatch.desc: """The number of times that no authorization rules were matched.""" @@ -278,10 +136,4 @@ success.desc: success.label: """Success""" -http_get.desc: -"""Authorization using an external HTTP server (via GET requests).""" - -http_get.label: -"""http_get""" - } diff --git a/scripts/merge-config.escript b/scripts/merge-config.escript index 2b621da6b..25593a323 100755 --- a/scripts/merge-config.escript +++ b/scripts/merge-config.escript @@ -9,7 +9,7 @@ %% edition due to backward-compatibility reasons. -mode(compile). --define(APPS, ["emqx", "emqx_dashboard", "emqx_authz"]). +-define(APPS, ["emqx", "emqx_dashboard", "emqx_auth"]). main(_) -> {ok, BaseConf} = file:read_file("apps/emqx_conf/etc/emqx_conf.conf"),