Sfoglia il codice sorgente

refactor(ldap): merge the `ldap-bind` backend as a type for the `ldap` backend

firest 2 anni fa
parent
commit
cbfd02d1b0

+ 0 - 4
apps/emqx_auth_ldap/include/emqx_auth_ldap.hrl

@@ -26,10 +26,6 @@
 -define(AUTHN_BACKEND, ldap).
 -define(AUTHN_BACKEND, ldap).
 -define(AUTHN_BACKEND_BIN, <<"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, {?AUTHN_MECHANISM, ?AUTHN_BACKEND}).
--define(AUTHN_TYPE_BIND, {?AUTHN_MECHANISM, ?AUTHN_BACKEND_BIND}).
 
 
 -endif.
 -endif.

+ 0 - 2
apps/emqx_auth_ldap/src/emqx_auth_ldap_app.erl

@@ -25,12 +25,10 @@
 start(_StartType, _StartArgs) ->
 start(_StartType, _StartArgs) ->
     ok = emqx_authz:register_source(?AUTHZ_TYPE, emqx_authz_ldap),
     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, 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} = emqx_auth_ldap_sup:start_link(),
     {ok, Sup}.
     {ok, Sup}.
 
 
 stop(_State) ->
 stop(_State) ->
     ok = emqx_authn:deregister_provider(?AUTHN_TYPE),
     ok = emqx_authn:deregister_provider(?AUTHN_TYPE),
-    ok = emqx_authn:deregister_provider(?AUTHN_TYPE_BIND),
     ok = emqx_authz:unregister_source(?AUTHZ_TYPE),
     ok = emqx_authz:unregister_source(?AUTHZ_TYPE),
     ok.
     ok.

+ 19 - 166
apps/emqx_auth_ldap/src/emqx_authn_ldap.erl

@@ -16,19 +16,10 @@
 
 
 -module(emqx_authn_ldap).
 -module(emqx_authn_ldap).
 
 
--include_lib("emqx_auth/include/emqx_authn.hrl").
 -include_lib("emqx/include/logger.hrl").
 -include_lib("emqx/include/logger.hrl").
--include_lib("eldap/include/eldap.hrl").
 
 
 -behaviour(emqx_authn_provider).
 -behaviour(emqx_authn_provider).
 
 
-%% a compatible attribute for version 4.x
--define(ISENABLED_ATTR, "isEnabled").
--define(VALID_ALGORITHMS, [md5, ssha, sha, sha256, sha384, sha512]).
-%% TODO
-%% 1. Supports more salt algorithms, SMD5 SSHA 256/384/512
-%% 2. Supports https://datatracker.ietf.org/doc/html/rfc3112
-
 -export([
 -export([
     create/2,
     create/2,
     update/2,
     update/2,
@@ -69,163 +60,25 @@ authenticate(#{auth_method := _}, _) ->
     ignore;
     ignore;
 authenticate(#{password := undefined}, _) ->
 authenticate(#{password := undefined}, _) ->
     {error, bad_username_or_password};
     {error, bad_username_or_password};
-authenticate(
-    #{password := Password} = Credential,
-    #{
-        password_attribute := PasswordAttr,
-        is_superuser_attribute := IsSuperuserAttr,
-        query_timeout := Timeout,
-        resource_id := ResourceId
-    } = State
-) ->
-    case
-        emqx_resource:simple_sync_query(
-            ResourceId,
-            {query, Credential, [PasswordAttr, IsSuperuserAttr, ?ISENABLED_ATTR], Timeout}
-        )
-    of
-        {ok, []} ->
-            ignore;
-        {ok, [Entry]} ->
-            is_enabled(Password, Entry, State);
-        {error, Reason} ->
-            ?TRACE_AUTHN_PROVIDER(error, "ldap_query_failed", #{
-                resource => ResourceId,
-                timeout => Timeout,
-                reason => Reason
-            }),
-            ignore
-    end.
-
-parse_config(Config) ->
-    maps:with([query_timeout, password_attribute, is_superuser_attribute], Config).
-
-%% To compatible v4.x
-is_enabled(Password, #eldap_entry{attributes = Attributes} = Entry, State) ->
-    IsEnabled = get_lower_bin_value(?ISENABLED_ATTR, Attributes, "true"),
-    case emqx_authn_utils:to_bool(IsEnabled) of
-        true ->
-            ensure_password(Password, Entry, State);
-        _ ->
-            {error, user_disabled}
-    end.
-
-ensure_password(
-    Password,
-    #eldap_entry{attributes = Attributes} = Entry,
-    #{password_attribute := PasswordAttr} = State
-) ->
-    case get_value(PasswordAttr, Attributes) of
-        undefined ->
-            {error, no_password};
-        [LDAPPassword | _] ->
-            extract_hash_algorithm(LDAPPassword, Password, fun try_decode_password/4, Entry, State)
-    end.
-
-%% RFC 2307 format password
-%% https://datatracker.ietf.org/doc/html/rfc2307
-extract_hash_algorithm(LDAPPassword, Password, OnFail, Entry, State) ->
-    case
-        re:run(
-            LDAPPassword,
-            "{([^{}]+)}(.+)",
-            [{capture, all_but_first, list}, global]
-        )
-    of
-        {match, [[HashTypeStr, PasswordHashStr]]} ->
-            case emqx_utils:safe_to_existing_atom(string:to_lower(HashTypeStr)) of
-                {ok, HashType} ->
-                    PasswordHash = to_binary(PasswordHashStr),
-                    is_valid_algorithm(HashType, PasswordHash, Password, Entry, State);
-                _Error ->
-                    {error, invalid_hash_type}
-            end;
-        _ ->
-            OnFail(LDAPPassword, Password, Entry, State)
-    end.
-
-is_valid_algorithm(HashType, PasswordHash, Password, Entry, State) ->
-    case lists:member(HashType, ?VALID_ALGORITHMS) of
-        true ->
-            verify_password(HashType, PasswordHash, Password, Entry, State);
-        _ ->
-            {error, {invalid_hash_type, HashType}}
-    end.
-
-%% this password is in LDIF format which is base64 encoding
-try_decode_password(LDAPPassword, Password, Entry, State) ->
-    case safe_base64_decode(LDAPPassword) of
-        {ok, Decode} ->
-            extract_hash_algorithm(
-                Decode,
-                Password,
-                fun(_, _, _, _) ->
-                    {error, invalid_password}
-                end,
-                Entry,
-                State
-            );
-        {error, Reason} ->
-            {error, {invalid_password, Reason}}
+authenticate(Credential, #{method := #{type := Type}} = State) ->
+    case Type of
+        hash ->
+            emqx_authn_ldap_hash:authenticate(Credential, State);
+        bind ->
+            emqx_authn_ldap_bind:authenticate(Credential, State)
     end.
     end.
 
 
-%% sha with salt
-%% https://www.openldap.org/faq/data/cache/347.html
-verify_password(ssha, PasswordData, Password, Entry, State) ->
-    case safe_base64_decode(PasswordData) of
-        {ok, <<PasswordHash:20/binary, Salt/binary>>} ->
-            verify_password(sha, hash, PasswordHash, Salt, suffix, Password, Entry, State);
-        {ok, _} ->
-            {error, invalid_ssha_password};
-        {error, Reason} ->
-            {error, {invalid_password, Reason}}
-    end;
-verify_password(
-    Algorithm,
-    Base64HashData,
-    Password,
-    Entry,
-    State
+%% it used the deprecated config form
+parse_config(
+    #{password_attribute := PasswordAttr, is_superuser_attribute := IsSuperuserAttr} = Config0
 ) ->
 ) ->
-    verify_password(Algorithm, base64, Base64HashData, <<>>, disable, Password, Entry, State).
-
-verify_password(Algorithm, LDAPPasswordType, LDAPPassword, Salt, Position, Password, Entry, State) ->
-    PasswordHash = hash_password(Algorithm, Salt, Position, Password),
-    case compare_password(LDAPPasswordType, LDAPPassword, PasswordHash) of
-        true ->
-            {ok, is_superuser(Entry, State)};
-        _ ->
-            {error, bad_username_or_password}
-    end.
-
-is_superuser(Entry, #{is_superuser_attribute := Attr} = _State) ->
-    Value = get_lower_bin_value(Attr, Entry#eldap_entry.attributes, "false"),
-    #{is_superuser => emqx_authn_utils:to_bool(Value)}.
-
-safe_base64_decode(Data) ->
-    try
-        {ok, base64:decode(Data)}
-    catch
-        _:Reason ->
-            {error, {invalid_base64_data, Reason}}
-    end.
-
-get_lower_bin_value(Key, Proplists, Default) ->
-    [Value | _] = get_value(Key, Proplists, [Default]),
-    to_binary(string:to_lower(Value)).
-
-to_binary(Value) ->
-    erlang:list_to_binary(Value).
-
-hash_password(Algorithm, _Salt, disable, Password) ->
-    hash_password(Algorithm, Password);
-hash_password(Algorithm, Salt, suffix, Password) ->
-    hash_password(Algorithm, <<Password/binary, Salt/binary>>).
-
-hash_password(Algorithm, Data) ->
-    crypto:hash(Algorithm, Data).
-
-compare_password(hash, LDAPPasswordHash, PasswordHash) ->
-    emqx_passwd:compare_secure(LDAPPasswordHash, PasswordHash);
-compare_password(base64, Base64HashData, PasswordHash) ->
-    emqx_passwd:compare_secure(Base64HashData, base64:encode(PasswordHash)).
+    Config = maps:without([password_attribute, is_superuser_attribute], Config0),
+    parse_config(Config#{
+        method => #{
+            type => hash,
+            password_attribute => PasswordAttr,
+            is_superuser_attribute => IsSuperuserAttr
+        }
+    });
+parse_config(Config) ->
+    maps:with([query_timeout, method], Config).

+ 1 - 20
apps/emqx_auth_ldap/src/emqx_authn_ldap_bind.erl

@@ -20,32 +20,13 @@
 -include_lib("emqx/include/logger.hrl").
 -include_lib("emqx/include/logger.hrl").
 -include_lib("eldap/include/eldap.hrl").
 -include_lib("eldap/include/eldap.hrl").
 
 
--behaviour(emqx_authn_provider).
-
 -export([
 -export([
-    create/2,
-    update/2,
-    authenticate/2,
-    destroy/1
+    authenticate/2
 ]).
 ]).
 
 
 %%------------------------------------------------------------------------------
 %%------------------------------------------------------------------------------
 %% APIs
 %% APIs
 %%------------------------------------------------------------------------------
 %%------------------------------------------------------------------------------
-
-create(_AuthenticatorID, Config) ->
-    emqx_authn_ldap:do_create(?MODULE, Config).
-
-update(Config, State) ->
-    emqx_authn_ldap:update(Config, State).
-
-destroy(State) ->
-    emqx_authn_ldap:destroy(State).
-
-authenticate(#{auth_method := _}, _) ->
-    ignore;
-authenticate(#{password := undefined}, _) ->
-    {error, bad_username_or_password};
 authenticate(
 authenticate(
     #{password := _Password} = Credential,
     #{password := _Password} = Credential,
     #{
     #{

+ 0 - 63
apps/emqx_auth_ldap/src/emqx_authn_ldap_bind_schema.erl

@@ -1,63 +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_authn_ldap_bind_schema).
-
--include("emqx_auth_ldap.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(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.

+ 197 - 0
apps/emqx_auth_ldap/src/emqx_authn_ldap_hash.erl

@@ -0,0 +1,197 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 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_hash).
+
+-include_lib("emqx_auth/include/emqx_authn.hrl").
+-include_lib("emqx/include/logger.hrl").
+-include_lib("eldap/include/eldap.hrl").
+
+%% a compatible attribute for version 4.x
+-define(ISENABLED_ATTR, "isEnabled").
+-define(VALID_ALGORITHMS, [md5, ssha, sha, sha256, sha384, sha512]).
+%% TODO
+%% 1. Supports more salt algorithms, SMD5 SSHA 256/384/512
+%% 2. Supports https://datatracker.ietf.org/doc/html/rfc3112
+
+-export([
+    authenticate/2
+]).
+
+-import(proplists, [get_value/2, get_value/3]).
+
+%%------------------------------------------------------------------------------
+%% APIs
+%%------------------------------------------------------------------------------
+authenticate(
+    #{password := Password} = Credential,
+    #{
+        method := #{
+            password_attribute := PasswordAttr,
+            is_superuser_attribute := IsSuperuserAttr
+        },
+        query_timeout := Timeout,
+        resource_id := ResourceId
+    } = State
+) ->
+    case
+        emqx_resource:simple_sync_query(
+            ResourceId,
+            {query, Credential, [PasswordAttr, IsSuperuserAttr, ?ISENABLED_ATTR], Timeout}
+        )
+    of
+        {ok, []} ->
+            ignore;
+        {ok, [Entry]} ->
+            is_enabled(Password, Entry, State);
+        {error, Reason} ->
+            ?TRACE_AUTHN_PROVIDER(error, "ldap_query_failed", #{
+                resource => ResourceId,
+                timeout => Timeout,
+                reason => Reason
+            }),
+            ignore
+    end.
+
+%% To compatible v4.x
+is_enabled(Password, #eldap_entry{attributes = Attributes} = Entry, State) ->
+    IsEnabled = get_lower_bin_value(?ISENABLED_ATTR, Attributes, "true"),
+    case emqx_authn_utils:to_bool(IsEnabled) of
+        true ->
+            ensure_password(Password, Entry, State);
+        _ ->
+            {error, user_disabled}
+    end.
+
+ensure_password(
+    Password,
+    #eldap_entry{attributes = Attributes} = Entry,
+    #{method := #{password_attribute := PasswordAttr}} = State
+) ->
+    case get_value(PasswordAttr, Attributes) of
+        undefined ->
+            {error, no_password};
+        [LDAPPassword | _] ->
+            extract_hash_algorithm(LDAPPassword, Password, fun try_decode_password/4, Entry, State)
+    end.
+
+%% RFC 2307 format password
+%% https://datatracker.ietf.org/doc/html/rfc2307
+extract_hash_algorithm(LDAPPassword, Password, OnFail, Entry, State) ->
+    case
+        re:run(
+            LDAPPassword,
+            "{([^{}]+)}(.+)",
+            [{capture, all_but_first, list}, global]
+        )
+    of
+        {match, [[HashTypeStr, PasswordHashStr]]} ->
+            case emqx_utils:safe_to_existing_atom(string:to_lower(HashTypeStr)) of
+                {ok, HashType} ->
+                    PasswordHash = to_binary(PasswordHashStr),
+                    is_valid_algorithm(HashType, PasswordHash, Password, Entry, State);
+                _Error ->
+                    {error, invalid_hash_type}
+            end;
+        _ ->
+            OnFail(LDAPPassword, Password, Entry, State)
+    end.
+
+is_valid_algorithm(HashType, PasswordHash, Password, Entry, State) ->
+    case lists:member(HashType, ?VALID_ALGORITHMS) of
+        true ->
+            verify_password(HashType, PasswordHash, Password, Entry, State);
+        _ ->
+            {error, {invalid_hash_type, HashType}}
+    end.
+
+%% this password is in LDIF format which is base64 encoding
+try_decode_password(LDAPPassword, Password, Entry, State) ->
+    case safe_base64_decode(LDAPPassword) of
+        {ok, Decode} ->
+            extract_hash_algorithm(
+                Decode,
+                Password,
+                fun(_, _, _, _) ->
+                    {error, invalid_password}
+                end,
+                Entry,
+                State
+            );
+        {error, Reason} ->
+            {error, {invalid_password, Reason}}
+    end.
+
+%% sha with salt
+%% https://www.openldap.org/faq/data/cache/347.html
+verify_password(ssha, PasswordData, Password, Entry, State) ->
+    case safe_base64_decode(PasswordData) of
+        {ok, <<PasswordHash:20/binary, Salt/binary>>} ->
+            verify_password(sha, hash, PasswordHash, Salt, suffix, Password, Entry, State);
+        {ok, _} ->
+            {error, invalid_ssha_password};
+        {error, Reason} ->
+            {error, {invalid_password, Reason}}
+    end;
+verify_password(
+    Algorithm,
+    Base64HashData,
+    Password,
+    Entry,
+    State
+) ->
+    verify_password(Algorithm, base64, Base64HashData, <<>>, disable, Password, Entry, State).
+
+verify_password(Algorithm, LDAPPasswordType, LDAPPassword, Salt, Position, Password, Entry, State) ->
+    PasswordHash = hash_password(Algorithm, Salt, Position, Password),
+    case compare_password(LDAPPasswordType, LDAPPassword, PasswordHash) of
+        true ->
+            {ok, is_superuser(Entry, State)};
+        _ ->
+            {error, bad_username_or_password}
+    end.
+
+is_superuser(Entry, #{method := #{is_superuser_attribute := Attr}} = _State) ->
+    Value = get_lower_bin_value(Attr, Entry#eldap_entry.attributes, "false"),
+    #{is_superuser => emqx_authn_utils:to_bool(Value)}.
+
+safe_base64_decode(Data) ->
+    try
+        {ok, base64:decode(Data)}
+    catch
+        _:Reason ->
+            {error, {invalid_base64_data, Reason}}
+    end.
+
+get_lower_bin_value(Key, Proplists, Default) ->
+    [Value | _] = get_value(Key, Proplists, [Default]),
+    to_binary(string:to_lower(Value)).
+
+to_binary(Value) ->
+    erlang:list_to_binary(Value).
+
+hash_password(Algorithm, _Salt, disable, Password) ->
+    hash_password(Algorithm, Password);
+hash_password(Algorithm, Salt, suffix, Password) ->
+    hash_password(Algorithm, <<Password/binary, Salt/binary>>).
+
+hash_password(Algorithm, Data) ->
+    crypto:hash(Algorithm, Data).
+
+compare_password(hash, LDAPPasswordHash, PasswordHash) ->
+    emqx_passwd:compare_secure(LDAPPasswordHash, PasswordHash);
+compare_password(base64, Base64HashData, PasswordHash) ->
+    emqx_passwd:compare_secure(Base64HashData, base64:encode(PasswordHash)).

+ 50 - 11
apps/emqx_auth_ldap/src/emqx_authn_ldap_schema.erl

@@ -29,7 +29,7 @@
 ]).
 ]).
 
 
 refs() ->
 refs() ->
-    [?R_REF(ldap)].
+    [?R_REF(ldap), ?R_REF(ldap_deprecated)].
 
 
 select_union_member(#{<<"mechanism">> := ?AUTHN_MECHANISM_BIN, <<"backend">> := ?AUTHN_BACKEND_BIN}) ->
 select_union_member(#{<<"mechanism">> := ?AUTHN_MECHANISM_BIN, <<"backend">> := ?AUTHN_BACKEND_BIN}) ->
     refs();
     refs();
@@ -41,12 +41,34 @@ select_union_member(#{<<"backend">> := ?AUTHN_BACKEND_BIN}) ->
 select_union_member(_) ->
 select_union_member(_) ->
     undefined.
     undefined.
 
 
+fields(ldap_deprecated) ->
+    common_fields() ++
+        [
+            {password_attribute, password_attribute()},
+            {is_superuser_attribute, is_superuser_attribute()}
+        ];
 fields(ldap) ->
 fields(ldap) ->
+    common_fields() ++
+        [
+            {method,
+                ?HOCON(
+                    ?UNION([?R_REF(hash_method), ?R_REF(bind_method)]),
+                    #{desc => ?DESC(method)}
+                )}
+        ];
+fields(hash_method) ->
+    [
+        {type, method_type(hash)},
+        {password_attribute, password_attribute()},
+        {is_superuser_attribute, is_superuser_attribute()}
+    ];
+fields(bind_method) ->
+    [{type, method_type(bind)}] ++ emqx_ldap:fields(bind_opts).
+
+common_fields() ->
     [
     [
         {mechanism, emqx_authn_schema:mechanism(?AUTHN_MECHANISM)},
         {mechanism, emqx_authn_schema:mechanism(?AUTHN_MECHANISM)},
         {backend, emqx_authn_schema:backend(?AUTHN_BACKEND)},
         {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}
         {query_timeout, fun query_timeout/1}
     ] ++
     ] ++
         emqx_authn_schema:common_fields() ++
         emqx_authn_schema:common_fields() ++
@@ -54,18 +76,35 @@ fields(ldap) ->
 
 
 desc(ldap) ->
 desc(ldap) ->
     ?DESC(ldap);
     ?DESC(ldap);
+desc(ldap_deprecated) ->
+    ?DESC(ldap_deprecated);
+desc(hash_method) ->
+    ?DESC(hash_method);
+desc(bind_method) ->
+    ?DESC(bind_method);
 desc(_) ->
 desc(_) ->
     undefined.
     undefined.
 
 
-password_attribute(type) -> string();
-password_attribute(desc) -> ?DESC(?FUNCTION_NAME);
-password_attribute(default) -> <<"userPassword">>;
-password_attribute(_) -> undefined.
+method_type(Type) ->
+    ?HOCON(?ENUM([Type]), #{desc => ?DESC(?FUNCTION_NAME), default => Type}).
+
+password_attribute() ->
+    ?HOCON(
+        string(),
+        #{
+            desc => ?DESC(?FUNCTION_NAME),
+            default => <<"userPassword">>
+        }
+    ).
 
 
-is_superuser_attribute(type) -> string();
-is_superuser_attribute(desc) -> ?DESC(?FUNCTION_NAME);
-is_superuser_attribute(default) -> <<"isSuperuser">>;
-is_superuser_attribute(_) -> undefined.
+is_superuser_attribute() ->
+    ?HOCON(
+        string(),
+        #{
+            desc => ?DESC(?FUNCTION_NAME),
+            default => <<"isSuperuser">>
+        }
+    ).
 
 
 query_timeout(type) -> emqx_schema:timeout_duration_ms();
 query_timeout(type) -> emqx_schema:timeout_duration_ms();
 query_timeout(desc) -> ?DESC(?FUNCTION_NAME);
 query_timeout(desc) -> ?DESC(?FUNCTION_NAME);

+ 36 - 0
apps/emqx_auth_ldap/test/emqx_authn_ldap_SUITE.erl

@@ -70,6 +70,29 @@ end_per_suite(Config) ->
 %% Tests
 %% Tests
 %%------------------------------------------------------------------------------
 %%------------------------------------------------------------------------------
 
 
+t_create_with_deprecated_cfg(_Config) ->
+    AuthConfig = deprecated_raw_ldap_auth_config(),
+
+    {ok, _} = emqx:update_config(
+        ?PATH,
+        {create_authenticator, ?GLOBAL, AuthConfig}
+    ),
+
+    {ok, [#{provider := emqx_authn_ldap, state := State}]} = emqx_authn_chains:list_authenticators(
+        ?GLOBAL
+    ),
+    ?assertMatch(
+        #{
+            method := #{
+                type := hash,
+                is_superuser_attribute := _,
+                password_attribute := "not_the_default_value"
+            }
+        },
+        State
+    ),
+    emqx_authn_test_lib:delete_config(?ResourceID).
+
 t_create(_Config) ->
 t_create(_Config) ->
     AuthConfig = raw_ldap_auth_config(),
     AuthConfig = raw_ldap_auth_config(),
 
 
@@ -225,6 +248,19 @@ raw_ldap_auth_config() ->
         <<"pool_size">> => 8
         <<"pool_size">> => 8
     }.
     }.
 
 
+deprecated_raw_ldap_auth_config() ->
+    #{
+        <<"mechanism">> => <<"password_based">>,
+        <<"backend">> => <<"ldap">>,
+        <<"server">> => ldap_server(),
+        <<"is_superuser_attribute">> => <<"isSuperuser">>,
+        <<"password_attribute">> => <<"not_the_default_value">>,
+        <<"base_dn">> => <<"uid=${username},ou=testdevice,dc=emqx,dc=io">>,
+        <<"username">> => <<"cn=root,dc=emqx,dc=io">>,
+        <<"password">> => <<"public">>,
+        <<"pool_size">> => 8
+    }.
+
 user_seeds() ->
 user_seeds() ->
     New = fun(Username, Password, Result) ->
     New = fun(Username, Password, Result) ->
         #{
         #{

+ 11 - 8
apps/emqx_auth_ldap/test/emqx_authn_ldap_bind_SUITE.erl

@@ -27,7 +27,7 @@
 -define(LDAP_RESOURCE, <<"emqx_authn_ldap_bind_SUITE">>).
 -define(LDAP_RESOURCE, <<"emqx_authn_ldap_bind_SUITE">>).
 
 
 -define(PATH, [authentication]).
 -define(PATH, [authentication]).
--define(ResourceID, <<"password_based:ldap_bind">>).
+-define(ResourceID, <<"password_based:ldap">>).
 
 
 all() ->
 all() ->
     emqx_common_test_helpers:all(?MODULE).
     emqx_common_test_helpers:all(?MODULE).
@@ -78,7 +78,7 @@ t_create(_Config) ->
         {create_authenticator, ?GLOBAL, AuthConfig}
         {create_authenticator, ?GLOBAL, AuthConfig}
     ),
     ),
 
 
-    {ok, [#{provider := emqx_authn_ldap_bind}]} = emqx_authn_chains:list_authenticators(?GLOBAL),
+    {ok, [#{provider := emqx_authn_ldap}]} = emqx_authn_chains:list_authenticators(?GLOBAL),
     emqx_authn_test_lib:delete_config(?ResourceID).
     emqx_authn_test_lib:delete_config(?ResourceID).
 
 
 t_create_invalid(_Config) ->
 t_create_invalid(_Config) ->
@@ -146,10 +146,10 @@ t_destroy(_Config) ->
         {create_authenticator, ?GLOBAL, AuthConfig}
         {create_authenticator, ?GLOBAL, AuthConfig}
     ),
     ),
 
 
-    {ok, [#{provider := emqx_authn_ldap_bind, state := State}]} =
+    {ok, [#{provider := emqx_authn_ldap, state := State}]} =
         emqx_authn_chains:list_authenticators(?GLOBAL),
         emqx_authn_chains:list_authenticators(?GLOBAL),
 
 
-    {ok, _} = emqx_authn_ldap_bind:authenticate(
+    {ok, _} = emqx_authn_ldap:authenticate(
         #{
         #{
             username => <<"mqttuser0001">>,
             username => <<"mqttuser0001">>,
             password => <<"mqttuser0001">>
             password => <<"mqttuser0001">>
@@ -165,7 +165,7 @@ t_destroy(_Config) ->
     % Authenticator should not be usable anymore
     % Authenticator should not be usable anymore
     ?assertMatch(
     ?assertMatch(
         ignore,
         ignore,
-        emqx_authn_ldap_bind:authenticate(
+        emqx_authn_ldap:authenticate(
             #{
             #{
                 username => <<"mqttuser0001">>,
                 username => <<"mqttuser0001">>,
                 password => <<"mqttuser0001">>
                 password => <<"mqttuser0001">>
@@ -199,7 +199,7 @@ t_update(_Config) ->
     % We update with config with correct query, provider should update and work properly
     % We update with config with correct query, provider should update and work properly
     {ok, _} = emqx:update_config(
     {ok, _} = emqx:update_config(
         ?PATH,
         ?PATH,
-        {update_authenticator, ?GLOBAL, <<"password_based:ldap_bind">>, CorrectConfig}
+        {update_authenticator, ?GLOBAL, <<"password_based:ldap">>, CorrectConfig}
     ),
     ),
 
 
     {ok, _} = emqx_access_control:authenticate(
     {ok, _} = emqx_access_control:authenticate(
@@ -218,14 +218,17 @@ t_update(_Config) ->
 raw_ldap_auth_config() ->
 raw_ldap_auth_config() ->
     #{
     #{
         <<"mechanism">> => <<"password_based">>,
         <<"mechanism">> => <<"password_based">>,
-        <<"backend">> => <<"ldap_bind">>,
+        <<"backend">> => <<"ldap">>,
         <<"server">> => ldap_server(),
         <<"server">> => ldap_server(),
         <<"base_dn">> => <<"ou=testdevice,dc=emqx,dc=io">>,
         <<"base_dn">> => <<"ou=testdevice,dc=emqx,dc=io">>,
         <<"filter">> => <<"(uid=${username})">>,
         <<"filter">> => <<"(uid=${username})">>,
         <<"username">> => <<"cn=root,dc=emqx,dc=io">>,
         <<"username">> => <<"cn=root,dc=emqx,dc=io">>,
         <<"password">> => <<"public">>,
         <<"password">> => <<"public">>,
         <<"pool_size">> => 8,
         <<"pool_size">> => 8,
-        <<"bind_password">> => <<"${password}">>
+        <<"method">> => #{
+            <<"type">> => <<"bind">>,
+            <<"bind_password">> => <<"${password}">>
+        }
     }.
     }.
 
 
 user_seeds() ->
 user_seeds() ->

+ 1 - 2
apps/emqx_conf/include/emqx_conf.hrl

@@ -58,8 +58,7 @@
     emqx_authn_http_schema,
     emqx_authn_http_schema,
     emqx_authn_jwt_schema,
     emqx_authn_jwt_schema,
     emqx_authn_scram_mnesia_schema,
     emqx_authn_scram_mnesia_schema,
-    emqx_authn_ldap_schema,
-    emqx_authn_ldap_bind_schema
+    emqx_authn_ldap_schema
 ]).
 ]).
 
 
 -define(EE_AUTHN_PROVIDER_SCHEMA_MODS, [
 -define(EE_AUTHN_PROVIDER_SCHEMA_MODS, [

+ 1 - 1
apps/emqx_dashboard_sso/src/emqx_dashboard_sso_ldap.erl

@@ -92,7 +92,7 @@ parse_config(Config0) ->
 %% In this feature, the `bind_password` is fixed, so it should conceal from the swagger,
 %% In this feature, the `bind_password` is fixed, so it should conceal from the swagger,
 %% but the connector still needs it, hence we should add it back here
 %% but the connector still needs it, hence we should add it back here
 ensure_bind_password(Config) ->
 ensure_bind_password(Config) ->
-    Config#{bind_password => <<"${password}">>}.
+    Config#{method => #{type => bind, bind_password => <<"${password}">>}}.
 
 
 adjust_ldap_fields(Fields) ->
 adjust_ldap_fields(Fields) ->
     lists:map(fun adjust_ldap_field/1, Fields).
     lists:map(fun adjust_ldap_field/1, Fields).

+ 3 - 8
apps/emqx_ldap/src/emqx_ldap_bind_worker.erl

@@ -35,7 +35,7 @@
 %% ===================================================================
 %% ===================================================================
 -spec on_start(binary(), hoconsc:config(), proplists:proplist(), map()) ->
 -spec on_start(binary(), hoconsc:config(), proplists:proplist(), map()) ->
     {ok, binary(), map()} | {error, _}.
     {ok, binary(), map()} | {error, _}.
-on_start(InstId, #{bind_password := _} = Config, Options, State) ->
+on_start(InstId, #{method := #{bind_password := _}} = Config, Options, State) ->
     PoolName = pool_name(InstId),
     PoolName = pool_name(InstId),
     ?SLOG(info, #{
     ?SLOG(info, #{
         msg => "starting_ldap_bind_worker",
         msg => "starting_ldap_bind_worker",
@@ -108,15 +108,10 @@ on_query(
 connect(Conf) ->
 connect(Conf) ->
     emqx_ldap:connect(Conf).
     emqx_ldap:connect(Conf).
 
 
-prepare_template(Config, State) ->
-    do_prepare_template(maps:to_list(maps:with([bind_password], Config)), State).
-
-do_prepare_template([{bind_password, V} | T], State) ->
+prepare_template(#{method := #{bind_password := V}}, State) ->
     %% This is sensitive data
     %% This is sensitive data
     %% to reduce match cases, here we reuse the existing sensitive filter key: bind_password
     %% to reduce match cases, here we reuse the existing sensitive filter key: bind_password
-    do_prepare_template(T, State#{bind_password => emqx_placeholder:preproc_tmpl(V)});
-do_prepare_template([], State) ->
-    State.
+    State#{bind_password => emqx_placeholder:preproc_tmpl(V)}.
 
 
 pool_name(InstId) ->
 pool_name(InstId) ->
     <<InstId/binary, "-", ?POOL_NAME_SUFFIX>>.
     <<InstId/binary, "-", ?POOL_NAME_SUFFIX>>.

+ 0 - 11
rel/i18n/emqx_authn_ldap_bind_schema.hocon

@@ -1,11 +0,0 @@
-emqx_authn_ldap_bind_schema {
-
-ldap_bind.desc:
-"""Configuration of authenticator using the LDAP bind operation as the authentication method."""
-
-query_timeout.desc:
-"""Timeout for the LDAP query."""
-
-query_timeout.label:
-"""Query Timeout"""
-}

+ 15 - 0
rel/i18n/emqx_authn_ldap_schema.hocon

@@ -3,6 +3,9 @@ emqx_authn_ldap_schema {
 ldap.desc:
 ldap.desc:
 """Configuration of authenticator using LDAP as authentication data source."""
 """Configuration of authenticator using LDAP as authentication data source."""
 
 
+ldap_deprecated.desc:
+"""This is a deprecated form, you should avoid using it."""
+
 password_attribute.desc:
 password_attribute.desc:
 """Indicates which attribute is used to represent the user's password."""
 """Indicates which attribute is used to represent the user's password."""
 
 
@@ -21,4 +24,16 @@ query_timeout.desc:
 query_timeout.label:
 query_timeout.label:
 """Query Timeout"""
 """Query Timeout"""
 
 
+hash_method.desc:
+"""Authenticate by comparing the hashed password which was provided by the `password attribute`."""
+
+bind_method.desc:
+"""Authenticate by the LDAP bind operation."""
+
+method.desc:
+"""Authentication method."""
+
+method_type.desc:
+"""Authentication method type."""
+
 }
 }