refactor(ldap): merge the `ldap-bind` backend as a type for the `ldap` backend
This commit is contained in:
parent
9dfffd90d0
commit
cbfd02d1b0
|
@ -26,10 +26,6 @@
|
|||
-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.
|
||||
|
|
|
@ -25,12 +25,10 @@
|
|||
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.
|
||||
|
|
|
@ -16,19 +16,10 @@
|
|||
|
||||
-module(emqx_authn_ldap).
|
||||
|
||||
-include_lib("emqx_auth/include/emqx_authn.hrl").
|
||||
-include_lib("emqx/include/logger.hrl").
|
||||
-include_lib("eldap/include/eldap.hrl").
|
||||
|
||||
-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([
|
||||
create/2,
|
||||
update/2,
|
||||
|
@ -69,163 +60,25 @@ authenticate(#{auth_method := _}, _) ->
|
|||
ignore;
|
||||
authenticate(#{password := undefined}, _) ->
|
||||
{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
|
||||
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.
|
||||
|
||||
%% it used the deprecated config form
|
||||
parse_config(
|
||||
#{password_attribute := PasswordAttr, is_superuser_attribute := IsSuperuserAttr} = Config0
|
||||
) ->
|
||||
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, 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}}
|
||||
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, #{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)).
|
||||
maps:with([query_timeout, method], Config).
|
||||
|
|
|
@ -20,32 +20,13 @@
|
|||
-include_lib("emqx/include/logger.hrl").
|
||||
-include_lib("eldap/include/eldap.hrl").
|
||||
|
||||
-behaviour(emqx_authn_provider).
|
||||
|
||||
-export([
|
||||
create/2,
|
||||
update/2,
|
||||
authenticate/2,
|
||||
destroy/1
|
||||
authenticate/2
|
||||
]).
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% 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(
|
||||
#{password := _Password} = Credential,
|
||||
#{
|
||||
|
|
|
@ -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.
|
|
@ -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)).
|
|
@ -29,7 +29,7 @@
|
|||
]).
|
||||
|
||||
refs() ->
|
||||
[?R_REF(ldap)].
|
||||
[?R_REF(ldap), ?R_REF(ldap_deprecated)].
|
||||
|
||||
select_union_member(#{<<"mechanism">> := ?AUTHN_MECHANISM_BIN, <<"backend">> := ?AUTHN_BACKEND_BIN}) ->
|
||||
refs();
|
||||
|
@ -41,12 +41,34 @@ select_union_member(#{<<"backend">> := ?AUTHN_BACKEND_BIN}) ->
|
|||
select_union_member(_) ->
|
||||
undefined.
|
||||
|
||||
fields(ldap_deprecated) ->
|
||||
common_fields() ++
|
||||
[
|
||||
{password_attribute, password_attribute()},
|
||||
{is_superuser_attribute, is_superuser_attribute()}
|
||||
];
|
||||
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)},
|
||||
{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() ++
|
||||
|
@ -54,18 +76,35 @@ fields(ldap) ->
|
|||
|
||||
desc(ldap) ->
|
||||
?DESC(ldap);
|
||||
desc(ldap_deprecated) ->
|
||||
?DESC(ldap_deprecated);
|
||||
desc(hash_method) ->
|
||||
?DESC(hash_method);
|
||||
desc(bind_method) ->
|
||||
?DESC(bind_method);
|
||||
desc(_) ->
|
||||
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}).
|
||||
|
||||
is_superuser_attribute(type) -> string();
|
||||
is_superuser_attribute(desc) -> ?DESC(?FUNCTION_NAME);
|
||||
is_superuser_attribute(default) -> <<"isSuperuser">>;
|
||||
is_superuser_attribute(_) -> undefined.
|
||||
password_attribute() ->
|
||||
?HOCON(
|
||||
string(),
|
||||
#{
|
||||
desc => ?DESC(?FUNCTION_NAME),
|
||||
default => <<"userPassword">>
|
||||
}
|
||||
).
|
||||
|
||||
is_superuser_attribute() ->
|
||||
?HOCON(
|
||||
string(),
|
||||
#{
|
||||
desc => ?DESC(?FUNCTION_NAME),
|
||||
default => <<"isSuperuser">>
|
||||
}
|
||||
).
|
||||
|
||||
query_timeout(type) -> emqx_schema:timeout_duration_ms();
|
||||
query_timeout(desc) -> ?DESC(?FUNCTION_NAME);
|
||||
|
|
|
@ -70,6 +70,29 @@ end_per_suite(Config) ->
|
|||
%% 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) ->
|
||||
AuthConfig = raw_ldap_auth_config(),
|
||||
|
||||
|
@ -225,6 +248,19 @@ raw_ldap_auth_config() ->
|
|||
<<"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() ->
|
||||
New = fun(Username, Password, Result) ->
|
||||
#{
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
-define(LDAP_RESOURCE, <<"emqx_authn_ldap_bind_SUITE">>).
|
||||
|
||||
-define(PATH, [authentication]).
|
||||
-define(ResourceID, <<"password_based:ldap_bind">>).
|
||||
-define(ResourceID, <<"password_based:ldap">>).
|
||||
|
||||
all() ->
|
||||
emqx_common_test_helpers:all(?MODULE).
|
||||
|
@ -78,7 +78,7 @@ t_create(_Config) ->
|
|||
{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).
|
||||
|
||||
t_create_invalid(_Config) ->
|
||||
|
@ -146,10 +146,10 @@ t_destroy(_Config) ->
|
|||
{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),
|
||||
|
||||
{ok, _} = emqx_authn_ldap_bind:authenticate(
|
||||
{ok, _} = emqx_authn_ldap:authenticate(
|
||||
#{
|
||||
username => <<"mqttuser0001">>,
|
||||
password => <<"mqttuser0001">>
|
||||
|
@ -165,7 +165,7 @@ t_destroy(_Config) ->
|
|||
% Authenticator should not be usable anymore
|
||||
?assertMatch(
|
||||
ignore,
|
||||
emqx_authn_ldap_bind:authenticate(
|
||||
emqx_authn_ldap:authenticate(
|
||||
#{
|
||||
username => <<"mqttuser0001">>,
|
||||
password => <<"mqttuser0001">>
|
||||
|
@ -199,7 +199,7 @@ t_update(_Config) ->
|
|||
% We update with config with correct query, provider should update and work properly
|
||||
{ok, _} = emqx:update_config(
|
||||
?PATH,
|
||||
{update_authenticator, ?GLOBAL, <<"password_based:ldap_bind">>, CorrectConfig}
|
||||
{update_authenticator, ?GLOBAL, <<"password_based:ldap">>, CorrectConfig}
|
||||
),
|
||||
|
||||
{ok, _} = emqx_access_control:authenticate(
|
||||
|
@ -218,14 +218,17 @@ t_update(_Config) ->
|
|||
raw_ldap_auth_config() ->
|
||||
#{
|
||||
<<"mechanism">> => <<"password_based">>,
|
||||
<<"backend">> => <<"ldap_bind">>,
|
||||
<<"backend">> => <<"ldap">>,
|
||||
<<"server">> => ldap_server(),
|
||||
<<"base_dn">> => <<"ou=testdevice,dc=emqx,dc=io">>,
|
||||
<<"filter">> => <<"(uid=${username})">>,
|
||||
<<"username">> => <<"cn=root,dc=emqx,dc=io">>,
|
||||
<<"password">> => <<"public">>,
|
||||
<<"pool_size">> => 8,
|
||||
<<"bind_password">> => <<"${password}">>
|
||||
<<"method">> => #{
|
||||
<<"type">> => <<"bind">>,
|
||||
<<"bind_password">> => <<"${password}">>
|
||||
}
|
||||
}.
|
||||
|
||||
user_seeds() ->
|
||||
|
|
|
@ -58,8 +58,7 @@
|
|||
emqx_authn_http_schema,
|
||||
emqx_authn_jwt_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, [
|
||||
|
|
|
@ -92,7 +92,7 @@ parse_config(Config0) ->
|
|||
%% 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
|
||||
ensure_bind_password(Config) ->
|
||||
Config#{bind_password => <<"${password}">>}.
|
||||
Config#{method => #{type => bind, bind_password => <<"${password}">>}}.
|
||||
|
||||
adjust_ldap_fields(Fields) ->
|
||||
lists:map(fun adjust_ldap_field/1, Fields).
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
%% ===================================================================
|
||||
-spec on_start(binary(), hoconsc:config(), proplists:proplist(), map()) ->
|
||||
{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),
|
||||
?SLOG(info, #{
|
||||
msg => "starting_ldap_bind_worker",
|
||||
|
@ -108,15 +108,10 @@ on_query(
|
|||
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
|
||||
%% 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) ->
|
||||
<<InstId/binary, "-", ?POOL_NAME_SUFFIX>>.
|
||||
|
|
|
@ -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"""
|
||||
}
|
|
@ -3,6 +3,9 @@ emqx_authn_ldap_schema {
|
|||
ldap.desc:
|
||||
"""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:
|
||||
"""Indicates which attribute is used to represent the user's password."""
|
||||
|
||||
|
@ -21,4 +24,16 @@ query_timeout.desc:
|
|||
query_timeout.label:
|
||||
"""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."""
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue