Просмотр исходного кода

Merge pull request #11392 from lafirest/feat/ldap_authz

feat(ldap-authz): integrate the LDAP authorization
lafirest 2 лет назад
Родитель
Сommit
2b03436552

+ 2 - 1
apps/emqx/src/emqx_passwd.erl

@@ -19,7 +19,8 @@
 -export([
     hash/2,
     hash_data/2,
-    check_pass/3
+    check_pass/3,
+    compare_secure/2
 ]).
 
 -export_type([

+ 1 - 1
apps/emqx_authn/src/emqx_authn_enterprise.erl

@@ -1,5 +1,5 @@
 %%--------------------------------------------------------------------
-%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
+%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
 %%--------------------------------------------------------------------
 
 -module(emqx_authn_enterprise).

+ 1 - 1
apps/emqx_authz/src/emqx_authz.app.src

@@ -1,7 +1,7 @@
 %% -*- mode: erlang -*-
 {application, emqx_authz, [
     {description, "An OTP application"},
-    {vsn, "0.1.24"},
+    {vsn, "0.1.25"},
     {registered, []},
     {mod, {emqx_authz_app, []}},
     {applications, [

+ 9 - 3
apps/emqx_authz/src/emqx_authz.erl

@@ -19,6 +19,8 @@
 -behaviour(emqx_config_handler).
 -behaviour(emqx_config_backup).
 
+-dialyzer({nowarn_function, [authz_module/1]}).
+
 -include("emqx_authz.hrl").
 -include_lib("emqx/include/logger.hrl").
 -include_lib("emqx/include/emqx_hooks.hrl").
@@ -571,7 +573,12 @@ find_action_in_hooks() ->
 authz_module(built_in_database) ->
     emqx_authz_mnesia;
 authz_module(Type) ->
-    list_to_existing_atom("emqx_authz_" ++ atom_to_list(Type)).
+    case emqx_authz_enterprise:is_enterprise_module(Type) of
+        {ok, Module} ->
+            Module;
+        _ ->
+            list_to_existing_atom("emqx_authz_" ++ atom_to_list(Type))
+    end.
 
 type(#{type := Type}) -> type(Type);
 type(#{<<"type">> := Type}) -> type(Type);
@@ -591,8 +598,7 @@ type(built_in_database) -> built_in_database;
 type(<<"built_in_database">>) -> built_in_database;
 type(client_info) -> client_info;
 type(<<"client_info">>) -> client_info;
-%% should never happen if the input is type-checked by hocon schema
-type(Unknown) -> throw({unknown_authz_source_type, Unknown}).
+type(MaybeEnterprise) -> emqx_authz_enterprise:type(MaybeEnterprise).
 
 maybe_write_files(#{<<"type">> := <<"file">>} = Source) ->
     write_acl_file(Source);

+ 4 - 2
apps/emqx_authz/src/emqx_authz_api_schema.erl

@@ -95,7 +95,9 @@ fields(position) ->
                     in => body
                 }
             )}
-    ].
+    ];
+fields(MaybeEnterprise) ->
+    emqx_authz_enterprise:fields(MaybeEnterprise).
 
 %%------------------------------------------------------------------------------
 %% http type funcs
@@ -283,7 +285,7 @@ authz_sources_types(Type) ->
             mysql,
             postgresql,
             file
-        ].
+        ] ++ emqx_authz_enterprise:authz_sources_types().
 
 to_list(A) when is_atom(A) ->
     atom_to_list(A);

+ 66 - 0
apps/emqx_authz/src/emqx_authz_enterprise.erl

@@ -0,0 +1,66 @@
+%%--------------------------------------------------------------------
+%% 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.

+ 9 - 5
apps/emqx_authz/src/emqx_authz_schema.erl

@@ -43,7 +43,8 @@
 -export([
     headers_no_content_type/1,
     headers/1,
-    default_authz/0
+    default_authz/0,
+    authz_common_fields/1
 ]).
 
 %%--------------------------------------------------------------------
@@ -64,7 +65,8 @@ type_names() ->
         redis_single,
         redis_sentinel,
         redis_cluster
-    ].
+    ] ++
+        emqx_authz_enterprise:type_names().
 
 namespace() -> authz.
 
@@ -176,7 +178,9 @@ fields("node_error") ->
     [
         node_name(),
         {"error", ?HOCON(string(), #{desc => ?DESC("node_error")})}
-    ].
+    ];
+fields(MaybeEnterprise) ->
+    emqx_authz_enterprise:fields(MaybeEnterprise).
 
 common_field() ->
     [
@@ -220,8 +224,8 @@ desc(redis_sentinel) ->
     ?DESC(redis_sentinel);
 desc(redis_cluster) ->
     ?DESC(redis_cluster);
-desc(_) ->
-    undefined.
+desc(MaybeEnterprise) ->
+    emqx_authz_enterprise:desc(MaybeEnterprise).
 
 authz_common_fields(Type) ->
     [

+ 2 - 1
apps/emqx_ldap/rebar.config

@@ -4,5 +4,6 @@
 {deps, [
         {emqx_connector, {path, "../../apps/emqx_connector"}},
         {emqx_resource, {path, "../../apps/emqx_resource"}},
-        {emqx_authn, {path, "../../apps/emqx_authn"}}
+        {emqx_authn, {path, "../../apps/emqx_authn"}},
+        {emqx_authz, {path, "../../apps/emqx_authz"}}
 ]}.

+ 2 - 1
apps/emqx_ldap/src/emqx_ldap.app.src

@@ -5,7 +5,8 @@
     {applications, [
         kernel,
         stdlib,
-        emqx_authn
+        emqx_authn,
+        emqx_authz
     ]},
     {env, []},
     {modules, []},

+ 8 - 10
apps/emqx_ldap/src/emqx_ldap_authn.erl

@@ -1,5 +1,5 @@
 %%--------------------------------------------------------------------
-%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
+%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
 %%--------------------------------------------------------------------
 
 -module(emqx_ldap_authn).
@@ -47,7 +47,7 @@ tags() ->
 
 %% used for config check when the schema module is resolved
 roots() ->
-    [{?CONF_NS, hoconsc:mk(hoconsc:ref(?MODULE, mysql))}].
+    [{?CONF_NS, hoconsc:mk(hoconsc:ref(?MODULE, ldap))}].
 
 fields(ldap) ->
     [
@@ -73,7 +73,7 @@ is_superuser_attribute(desc) -> ?DESC(?FUNCTION_NAME);
 is_superuser_attribute(default) -> <<"isSuperuser">>;
 is_superuser_attribute(_) -> undefined.
 
-query_timeout(type) -> emqx_schema:duration_ms();
+query_timeout(type) -> emqx_schema:timeout_duration_ms();
 query_timeout(desc) -> ?DESC(?FUNCTION_NAME);
 query_timeout(default) -> <<"5s">>;
 query_timeout(_) -> undefined.
@@ -173,7 +173,7 @@ ensure_password(
         undefined ->
             {error, no_password};
         [LDAPPassword | _] ->
-            extract_hash_algorithm(LDAPPassword, Password, fun try_decode_passowrd/4, Entry, State)
+            extract_hash_algorithm(LDAPPassword, Password, fun try_decode_password/4, Entry, State)
     end.
 
 %% RFC 2307 format password
@@ -207,7 +207,7 @@ is_valid_algorithm(HashType, PasswordHash, Password, Entry, State) ->
     end.
 
 %% this password is in LDIF format which is base64 encoding
-try_decode_passowrd(LDAPPassword, Password, Entry, State) ->
+try_decode_password(LDAPPassword, Password, Entry, State) ->
     case safe_base64_decode(LDAPPassword) of
         {ok, Decode} ->
             extract_hash_algorithm(
@@ -279,9 +279,7 @@ hash_password(Algorithm, Salt, suffix, Password) ->
 hash_password(Algorithm, Data) ->
     crypto:hash(Algorithm, Data).
 
-compare_password(hash, PasswordHash, PasswordHash) ->
-    true;
+compare_password(hash, LDAPPasswordHash, PasswordHash) ->
+    emqx_passwd:compare_secure(LDAPPasswordHash, PasswordHash);
 compare_password(base64, Base64HashData, PasswordHash) ->
-    Base64HashData =:= base64:encode(PasswordHash);
-compare_password(_, _, _) ->
-    false.
+    emqx_passwd:compare_secure(Base64HashData, base64:encode(PasswordHash)).

+ 164 - 0
apps/emqx_ldap/src/emqx_ldap_authz.erl

@@ -0,0 +1,164 @@
+%%--------------------------------------------------------------------
+%% 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_ldap_authz).
+
+-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).
+
+%% AuthZ Callbacks
+-export([
+    description/0,
+    create/1,
+    update/1,
+    destroy/1,
+    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
+%%------------------------------------------------------------------------------
+
+description() ->
+    "AuthZ with LDAP".
+
+create(Source) ->
+    ResourceId = emqx_authz_utils:make_resource_id(?MODULE),
+    {ok, _Data} = emqx_authz_utils:create_resource(ResourceId, emqx_ldap, Source),
+    Annotations = new_annotations(#{id => ResourceId}, Source),
+    Source#{annotations => Annotations}.
+
+update(Source) ->
+    case emqx_authz_utils:update_resource(emqx_ldap, Source) of
+        {error, Reason} ->
+            error({load_config_error, Reason});
+        {ok, Id} ->
+            Annotations = new_annotations(#{id => Id}, Source),
+            Source#{annotations => Annotations}
+    end.
+
+destroy(#{annotations := #{id := Id}}) ->
+    ok = emqx_resource:remove_local(Id).
+
+authorize(
+    Client,
+    Action,
+    Topic,
+    #{
+        query_timeout := QueryTimeout,
+        annotations := #{id := ResourceID} = Annotations
+    }
+) ->
+    Attrs = select_attrs(Action, Annotations),
+    case emqx_resource:simple_sync_query(ResourceID, {query, Client, Attrs, QueryTimeout}) of
+        {ok, []} ->
+            nomatch;
+        {ok, [Entry | _]} ->
+            do_authorize(Action, Topic, Attrs, Entry);
+        {error, Reason} ->
+            ?SLOG(error, #{
+                msg => "query_ldap_error",
+                reason => Reason,
+                resource_id => ResourceID
+            }),
+            nomatch
+    end.
+
+do_authorize(Action, Topic, [Attr | T], Entry) ->
+    Topics = proplists:get_value(Attr, Entry#eldap_entry.attributes, []),
+    case match_topic(Topic, Topics) of
+        true ->
+            {matched, allow};
+        false ->
+            do_authorize(Action, Topic, T, Entry)
+    end;
+do_authorize(_Action, _Topic, [], _Entry) ->
+    nomatch.
+
+new_annotations(Init, Source) ->
+    lists:foldl(
+        fun(Attr, Acc) ->
+            Acc#{
+                Attr =>
+                    case maps:get(Attr, Source) of
+                        Value when is_binary(Value) ->
+                            erlang:binary_to_list(Value);
+                        Value ->
+                            Value
+                    end
+            }
+        end,
+        Init,
+        [publish_attribute, subscribe_attribute, all_attribute]
+    ).
+
+select_attrs(#{action_type := publish}, #{publish_attribute := Pub, all_attribute := All}) ->
+    [Pub, All];
+select_attrs(_, #{subscribe_attribute := Sub, all_attribute := All}) ->
+    [Sub, All].
+
+match_topic(Target, Topics) ->
+    lists:any(
+        fun(Topic) ->
+            emqx_topic:match(Target, erlang:list_to_binary(Topic))
+        end,
+        Topics
+    ).

+ 1 - 1
apps/emqx_ldap/src/emqx_ldap_filter_lexer.xrl

@@ -27,5 +27,5 @@ dn : {token, {dn, TokenLine}}.
 Erlang code.
 
 %%--------------------------------------------------------------------
-%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
+%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
 %%--------------------------------------------------------------------

+ 1 - 1
apps/emqx_ldap/src/emqx_ldap_filter_parser.yrl

@@ -1,5 +1,5 @@
 Header "%%--------------------------------------------------------------------
-%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
+%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
 %%--------------------------------------------------------------------".
 
 Nonterminals

+ 1 - 1
apps/emqx_ldap/test/emqx_ldap_SUITE.erl

@@ -1,5 +1,5 @@
 %%--------------------------------------------------------------------
-%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
+%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
 %%--------------------------------------------------------------------
 
 -module(emqx_ldap_SUITE).

+ 1 - 2
apps/emqx_ldap/test/emqx_ldap_authn_SUITE.erl

@@ -1,7 +1,6 @@
 %%--------------------------------------------------------------------
-%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
+%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
 %%--------------------------------------------------------------------
-
 -module(emqx_ldap_authn_SUITE).
 
 -compile(nowarn_export_all).

+ 173 - 0
apps/emqx_ldap/test/emqx_ldap_authz_SUITE.erl

@@ -0,0 +1,173 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
+%%--------------------------------------------------------------------
+-module(emqx_ldap_authz_SUITE).
+
+-compile(nowarn_export_all).
+-compile(export_all).
+
+-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">>).
+
+all() ->
+    emqx_authz_test_lib:all_with_table_case(?MODULE, t_run_case, cases()).
+
+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(?LDAP_HOST, ?LDAP_DEFAULT_PORT) of
+        true ->
+            ok = emqx_common_test_helpers:start_apps(
+                [emqx_conf, emqx_authz],
+                fun set_special_configs/1
+            ),
+            ok = start_apps([emqx_resource]),
+            ok = create_ldap_resource(),
+            Config;
+        false ->
+            {skip, no_ldap}
+    end.
+
+end_per_suite(_Config) ->
+    ok = emqx_authz_test_lib:restore_authorizers(),
+    ok = emqx_resource:remove_local(?LDAP_RESOURCE),
+    ok = stop_apps([emqx_resource]),
+    ok = emqx_common_test_helpers:stop_apps([emqx_conf, emqx_authz]).
+
+init_per_group(Group, Config) ->
+    [{test_case, emqx_authz_test_lib:get_case(Group, cases())} | Config].
+end_per_group(_Group, _Config) ->
+    ok.
+
+init_per_testcase(_TestCase, Config) ->
+    ok = emqx_authz_test_lib:reset_authorizers(),
+    Config.
+end_per_testcase(_TestCase, _Config) ->
+    _ = emqx_authz:set_feature_available(rich_actions, true),
+    ok.
+
+set_special_configs(emqx_authz) ->
+    ok = emqx_authz_test_lib:reset_authorizers();
+set_special_configs(_) ->
+    ok.
+
+%%------------------------------------------------------------------------------
+%% Testcases
+%%------------------------------------------------------------------------------
+
+t_run_case(Config) ->
+    Case = ?config(test_case, Config),
+    ok = setup_authz_source(),
+    ok = emqx_authz_test_lib:run_checks(Case).
+
+t_create_invalid(_Config) ->
+    ok = setup_authz_source(),
+    BadConfig = maps:merge(
+        raw_ldap_authz_config(),
+        #{<<"server">> => <<"255.255.255.255:33333">>}
+    ),
+    {ok, _} = emqx_authz:update(?CMD_REPLACE, [BadConfig]),
+
+    [_] = emqx_authz:lookup().
+
+%%------------------------------------------------------------------------------
+%% Case
+%%------------------------------------------------------------------------------
+cases() ->
+    [
+        #{
+            name => simpe_publish,
+            client_info => #{username => <<"mqttuser0001">>},
+            checks => [
+                {allow, ?AUTHZ_PUBLISH, <<"mqttuser0001/pub/1">>},
+                {allow, ?AUTHZ_PUBLISH, <<"mqttuser0001/pub/+">>},
+                {allow, ?AUTHZ_PUBLISH, <<"mqttuser0001/pub/#">>}
+            ]
+        },
+        #{
+            name => simpe_subscribe,
+            client_info => #{username => <<"mqttuser0001">>},
+            checks => [
+                {allow, ?AUTHZ_SUBSCRIBE, <<"mqttuser0001/sub/1">>},
+                {allow, ?AUTHZ_SUBSCRIBE, <<"mqttuser0001/sub/+">>},
+                {allow, ?AUTHZ_SUBSCRIBE, <<"mqttuser0001/sub/#">>}
+            ]
+        },
+
+        #{
+            name => simpe_pubsub,
+            client_info => #{username => <<"mqttuser0001">>},
+            checks => [
+                {allow, ?AUTHZ_PUBLISH, <<"mqttuser0001/pubsub/1">>},
+                {allow, ?AUTHZ_PUBLISH, <<"mqttuser0001/pubsub/+">>},
+                {allow, ?AUTHZ_PUBLISH, <<"mqttuser0001/pubsub/#">>},
+
+                {allow, ?AUTHZ_SUBSCRIBE, <<"mqttuser0001/pubsub/1">>},
+                {allow, ?AUTHZ_SUBSCRIBE, <<"mqttuser0001/pubsub/+">>},
+                {allow, ?AUTHZ_SUBSCRIBE, <<"mqttuser0001/pubsub/#">>}
+            ]
+        },
+
+        #{
+            name => simpe_unmatched,
+            client_info => #{username => <<"mqttuser0001">>},
+            checks => [
+                {deny, ?AUTHZ_PUBLISH, <<"mqttuser0001/req/mqttuser0001/+">>},
+                {deny, ?AUTHZ_PUBLISH, <<"mqttuser0001/req/mqttuser0002/+">>},
+                {deny, ?AUTHZ_SUBSCRIBE, <<"mqttuser0001/req/+/mqttuser0002">>}
+            ]
+        }
+    ].
+
+%%------------------------------------------------------------------------------
+%% Helpers
+%%------------------------------------------------------------------------------
+
+setup_authz_source() ->
+    setup_config(#{}).
+
+raw_ldap_authz_config() ->
+    #{
+        <<"enable">> => <<"true">>,
+        <<"type">> => <<"ldap">>,
+        <<"server">> => ldap_server(),
+        <<"base_object">> => <<"uid=${username},ou=testdevice,dc=emqx,dc=io">>,
+        <<"username">> => <<"cn=root,dc=emqx,dc=io">>,
+        <<"password">> => <<"public">>,
+        <<"pool_size">> => 8
+    }.
+
+setup_config(SpecialParams) ->
+    emqx_authz_test_lib:setup_config(
+        raw_ldap_authz_config(),
+        SpecialParams
+    ).
+
+ldap_server() ->
+    iolist_to_binary(io_lib:format("~s:~B", [?LDAP_HOST, ?LDAP_DEFAULT_PORT])).
+
+ldap_config() ->
+    emqx_ldap_SUITE:ldap_config([]).
+
+start_apps(Apps) ->
+    lists:foreach(fun application:ensure_all_started/1, Apps).
+
+stop_apps(Apps) ->
+    lists:foreach(fun application:stop/1, Apps).
+
+create_ldap_resource() ->
+    {ok, _} = emqx_resource:create_local(
+        ?LDAP_RESOURCE,
+        ?RESOURCE_GROUP,
+        emqx_ldap,
+        ldap_config(),
+        #{}
+    ),
+    ok.

+ 1 - 1
apps/emqx_ldap/test/emqx_ldap_filter_SUITE.erl

@@ -1,5 +1,5 @@
 %%--------------------------------------------------------------------
-%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
+%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
 %%--------------------------------------------------------------------
 
 -module(emqx_ldap_filter_SUITE).

+ 1 - 1
changes/ee/feat-11386.en.md

@@ -1 +1 @@
-Integrated the LDAP as a new authenticator.
+Integrated LDAP as a new authenticator.

+ 1 - 0
changes/ee/feat-11392.en.md

@@ -0,0 +1 @@
+Integrated LDAP as a authorization source.

+ 1 - 1
rel/i18n/emqx_ldap_authn.hocon

@@ -10,7 +10,7 @@ password_attribute.label:
 """Password Attribute"""
 
 is_superuser_attribute.desc:
-"""Indicates which attribute is used to represent whether the user is a super user."""
+"""Indicates which attribute is used to represent whether the user is a superuser."""
 
 is_superuser_attribute.label:
 """IsSuperuser Attribute"""

+ 27 - 0
rel/i18n/emqx_ldap_authz.hocon

@@ -0,0 +1,27 @@
+emqx_ldap_authz {
+
+publish_attribute.desc:
+"""Indicates which attribute is used to represent the allowed topics list of the `publish`."""
+
+publish_attribute.label:
+"""Publish Attribute"""
+
+subscribe_attribute.desc:
+"""Indicates which attribute is used to represent the allowed topics list of the `subscribe`."""
+
+subscribe_attribute.label:
+"""Subscribe Attribute"""
+
+all_attribute.desc:
+"""Indicates which attribute is used to represent the both allowed topics list of  `publish` and `subscribe`."""
+
+all_attribute.label:
+"""All Attribute"""
+
+query_timeout.desc:
+"""Timeout for the LDAP query."""
+
+query_timeout.label:
+"""Query Timeout"""
+
+}