diff --git a/apps/emqx_authn/src/emqx_authn.app.src b/apps/emqx_authn/src/emqx_authn.app.src index c4cacca80..4ab86ef4a 100644 --- a/apps/emqx_authn/src/emqx_authn.app.src +++ b/apps/emqx_authn/src/emqx_authn.app.src @@ -1,7 +1,7 @@ %% -*- mode: erlang -*- {application, emqx_authn, [ {description, "EMQX Authentication"}, - {vsn, "0.1.23"}, + {vsn, "0.1.24"}, {modules, []}, {registered, [emqx_authn_sup, emqx_authn_registry]}, {applications, [ diff --git a/apps/emqx_authn/src/emqx_authn.erl b/apps/emqx_authn/src/emqx_authn.erl index 50287941e..ed6f0a095 100644 --- a/apps/emqx_authn/src/emqx_authn.erl +++ b/apps/emqx_authn/src/emqx_authn.erl @@ -40,7 +40,8 @@ providers() -> {{password_based, http}, emqx_authn_http}, {jwt, emqx_authn_jwt}, {{scram, built_in_database}, emqx_enhanced_authn_scram_mnesia} - ]. + ] ++ + emqx_authn_enterprise:providers(). check_config(Config) -> check_config(Config, #{}). diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl index 35d2caa96..fa9f6c820 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/apps/emqx_authn/src/emqx_authn_api.erl @@ -876,7 +876,8 @@ resource_provider() -> emqx_authn_mongodb, emqx_authn_redis, emqx_authn_http - ]. + ] ++ + emqx_authn_enterprise:resource_provider(). lookup_from_local_node(ChainName, AuthenticatorID) -> NodeId = node(self()), diff --git a/apps/emqx_authn/src/emqx_authn_enterprise.erl b/apps/emqx_authn/src/emqx_authn_enterprise.erl new file mode 100644 index 000000000..b50ec2c17 --- /dev/null +++ b/apps/emqx_authn/src/emqx_authn_enterprise.erl @@ -0,0 +1,24 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-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}]. + +resource_provider() -> + [emqx_ldap_authn]. + +-else. + +providers() -> + []. + +resource_provider() -> + []. +-endif. diff --git a/apps/emqx_authn/src/emqx_authn_utils.erl b/apps/emqx_authn/src/emqx_authn_utils.erl index 8e168bb5d..7fc20995a 100644 --- a/apps/emqx_authn/src/emqx_authn_utils.erl +++ b/apps/emqx_authn/src/emqx_authn_utils.erl @@ -35,7 +35,8 @@ ensure_apps_started/1, cleanup_resources/0, make_resource_id/1, - without_password/1 + without_password/1, + to_bool/1 ]). -define(AUTHN_PLACEHOLDERS, [ @@ -144,47 +145,8 @@ render_sql_params(ParamList, Credential) -> #{return => rawlist, var_trans => fun handle_sql_var/2} ). -%% true -is_superuser(#{<<"is_superuser">> := <<"true">>}) -> - #{is_superuser => true}; -is_superuser(#{<<"is_superuser">> := true}) -> - #{is_superuser => true}; -is_superuser(#{<<"is_superuser">> := <<"1">>}) -> - #{is_superuser => true}; -is_superuser(#{<<"is_superuser">> := I}) when - is_integer(I) andalso I >= 1 --> - #{is_superuser => true}; -%% false -is_superuser(#{<<"is_superuser">> := <<"">>}) -> - #{is_superuser => false}; -is_superuser(#{<<"is_superuser">> := <<"0">>}) -> - #{is_superuser => false}; -is_superuser(#{<<"is_superuser">> := 0}) -> - #{is_superuser => false}; -is_superuser(#{<<"is_superuser">> := null}) -> - #{is_superuser => false}; -is_superuser(#{<<"is_superuser">> := undefined}) -> - #{is_superuser => false}; -is_superuser(#{<<"is_superuser">> := <<"false">>}) -> - #{is_superuser => false}; -is_superuser(#{<<"is_superuser">> := false}) -> - #{is_superuser => false}; -is_superuser(#{<<"is_superuser">> := MaybeBinInt}) when - is_binary(MaybeBinInt) --> - try binary_to_integer(MaybeBinInt) of - Int when Int >= 1 -> - #{is_superuser => true}; - Int when Int =< 0 -> - #{is_superuser => false} - catch - error:badarg -> - #{is_superuser => false} - end; -%% fallback to default -is_superuser(#{<<"is_superuser">> := _}) -> - #{is_superuser => false}; +is_superuser(#{<<"is_superuser">> := Value}) -> + #{is_superuser => to_bool(Value)}; is_superuser(#{}) -> #{is_superuser => false}. @@ -211,6 +173,40 @@ make_resource_id(Name) -> without_password(Credential) -> without_password(Credential, [password, <<"password">>]). +to_bool(<<"true">>) -> + true; +to_bool(true) -> + true; +to_bool(<<"1">>) -> + true; +to_bool(I) when is_integer(I) andalso I >= 1 -> + true; +%% false +to_bool(<<"">>) -> + false; +to_bool(<<"0">>) -> + false; +to_bool(0) -> + false; +to_bool(null) -> + false; +to_bool(undefined) -> + false; +to_bool(<<"false">>) -> + false; +to_bool(false) -> + false; +to_bool(MaybeBinInt) when is_binary(MaybeBinInt) -> + try + binary_to_integer(MaybeBinInt) >= 1 + catch + error:badarg -> + false + end; +%% fallback to default +to_bool(_) -> + false. + %%-------------------------------------------------------------------- %% Internal functions %%-------------------------------------------------------------------- diff --git a/apps/emqx_authn/test/data/emqx.io.ldif b/apps/emqx_authn/test/data/emqx.io.ldif deleted file mode 100644 index 4675717ec..000000000 --- a/apps/emqx_authn/test/data/emqx.io.ldif +++ /dev/null @@ -1,134 +0,0 @@ -## create emqx.io - -dn:dc=emqx,dc=io -objectclass: top -objectclass: dcobject -objectclass: organization -dc:emqx -o:emqx,Inc. - -# create testdevice.emqx.io -dn:ou=testdevice,dc=emqx,dc=io -objectClass: top -objectclass:organizationalUnit -ou:testdevice - -# create user admin -dn:uid=admin,ou=testdevice,dc=emqx,dc=io -objectClass: top -objectClass: simpleSecurityObject -objectClass: account -userPassword:: e1NIQX1XNnBoNU1tNVB6OEdnaVVMYlBnekczN21qOWc9 -uid: admin - -## create user=mqttuser0001, -# password=mqttuser0001, -# passhash={SHA}mlb3fat40MKBTXUVZwCKmL73R/0= -# base64passhash=e1NIQX1tbGIzZmF0NDBNS0JUWFVWWndDS21MNzNSLzA9 -dn:uid=mqttuser0001,ou=testdevice,dc=emqx,dc=io -objectClass: top -objectClass: mqttUser -objectClass: mqttDevice -objectClass: mqttSecurity -uid: mqttuser0001 -isEnabled: TRUE -mqttAccountName: user1 -mqttPublishTopic: mqttuser0001/pub/1 -mqttPublishTopic: mqttuser0001/pub/+ -mqttPublishTopic: mqttuser0001/pub/# -mqttSubscriptionTopic: mqttuser0001/sub/1 -mqttSubscriptionTopic: mqttuser0001/sub/+ -mqttSubscriptionTopic: mqttuser0001/sub/# -mqttPubSubTopic: mqttuser0001/pubsub/1 -mqttPubSubTopic: mqttuser0001/pubsub/+ -mqttPubSubTopic: mqttuser0001/pubsub/# -userPassword:: e1NIQX1tbGIzZmF0NDBNS0JUWFVWWndDS21MNzNSLzA9 - -## create user=mqttuser0002 -# password=mqttuser0002, -# passhash={SSHA}n9XdtoG4Q/TQ3TQF4Y+khJbMBH4qXj4M -# base64passhash=e1NTSEF9bjlYZHRvRzRRL1RRM1RRRjRZK2toSmJNQkg0cVhqNE0= -dn:uid=mqttuser0002,ou=testdevice,dc=emqx,dc=io -objectClass: top -objectClass: mqttUser -objectClass: mqttDevice -objectClass: mqttSecurity -uid: mqttuser0002 -isEnabled: TRUE -mqttAccountName: user2 -mqttPublishTopic: mqttuser0002/pub/1 -mqttPublishTopic: mqttuser0002/pub/+ -mqttPublishTopic: mqttuser0002/pub/# -mqttSubscriptionTopic: mqttuser0002/sub/1 -mqttSubscriptionTopic: mqttuser0002/sub/+ -mqttSubscriptionTopic: mqttuser0002/sub/# -mqttPubSubTopic: mqttuser0002/pubsub/1 -mqttPubSubTopic: mqttuser0002/pubsub/+ -mqttPubSubTopic: mqttuser0002/pubsub/# -userPassword:: e1NTSEF9bjlYZHRvRzRRL1RRM1RRRjRZK2toSmJNQkg0cVhqNE0= - -## create user mqttuser0003 -# password=mqttuser0003, -# passhash={MD5}ybsPGoaK3nDyiQvveiCOIw== -# base64passhash=e01ENX15YnNQR29hSzNuRHlpUXZ2ZWlDT0l3PT0= -dn:uid=mqttuser0003,ou=testdevice,dc=emqx,dc=io -objectClass: top -objectClass: mqttUser -objectClass: mqttDevice -objectClass: mqttSecurity -uid: mqttuser0003 -isEnabled: TRUE -mqttPublishTopic: mqttuser0003/pub/1 -mqttPublishTopic: mqttuser0003/pub/+ -mqttPublishTopic: mqttuser0003/pub/# -mqttSubscriptionTopic: mqttuser0003/sub/1 -mqttSubscriptionTopic: mqttuser0003/sub/+ -mqttSubscriptionTopic: mqttuser0003/sub/# -mqttPubSubTopic: mqttuser0003/pubsub/1 -mqttPubSubTopic: mqttuser0003/pubsub/+ -mqttPubSubTopic: mqttuser0003/pubsub/# -userPassword:: e01ENX15YnNQR29hSzNuRHlpUXZ2ZWlDT0l3PT0= - -## create user mqttuser0004 -# password=mqttuser0004, -# passhash={MD5}2Br6pPDSEDIEvUlu9+s+MA== -# base64passhash=e01ENX0yQnI2cFBEU0VESUV2VWx1OStzK01BPT0= -dn:uid=mqttuser0004,ou=testdevice,dc=emqx,dc=io -objectClass: top -objectClass: mqttUser -objectClass: mqttDevice -objectClass: mqttSecurity -uid: mqttuser0004 -isEnabled: TRUE -mqttPublishTopic: mqttuser0004/pub/1 -mqttPublishTopic: mqttuser0004/pub/+ -mqttPublishTopic: mqttuser0004/pub/# -mqttSubscriptionTopic: mqttuser0004/sub/1 -mqttSubscriptionTopic: mqttuser0004/sub/+ -mqttSubscriptionTopic: mqttuser0004/sub/# -mqttPubSubTopic: mqttuser0004/pubsub/1 -mqttPubSubTopic: mqttuser0004/pubsub/+ -mqttPubSubTopic: mqttuser0004/pubsub/# -userPassword: {MD5}2Br6pPDSEDIEvUlu9+s+MA== - -## create user mqttuser0005 -# password=mqttuser0005, -# passhash={SHA}jKnxeEDGR14kE8AR7yuVFOelhz4= -# base64passhash=e1NIQX1qS254ZUVER1IxNGtFOEFSN3l1VkZPZWxoejQ9 -objectClass: top -dn:uid=mqttuser0005,ou=testdevice,dc=emqx,dc=io -objectClass: mqttUser -objectClass: mqttDevice -objectClass: mqttSecurity -uid: mqttuser0005 -isEnabled: TRUE -mqttPublishTopic: mqttuser0005/pub/1 -mqttPublishTopic: mqttuser0005/pub/+ -mqttPublishTopic: mqttuser0005/pub/# -mqttSubscriptionTopic: mqttuser0005/sub/1 -mqttSubscriptionTopic: mqttuser0005/sub/+ -mqttSubscriptionTopic: mqttuser0005/sub/# -mqttPubSubTopic: mqttuser0005/pubsub/1 -mqttPubSubTopic: mqttuser0005/pubsub/+ -mqttPubSubTopic: mqttuser0005/pubsub/# -userPassword: {SHA}jKnxeEDGR14kE8AR7yuVFOelhz4= diff --git a/apps/emqx_authn/test/data/emqx.schema b/apps/emqx_authn/test/data/emqx.schema deleted file mode 100644 index 55f92269b..000000000 --- a/apps/emqx_authn/test/data/emqx.schema +++ /dev/null @@ -1,46 +0,0 @@ -# -# Preliminary Apple OS X Native LDAP Schema -# This file is subject to change. -# -attributetype ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.1.3 NAME 'isEnabled' - EQUALITY booleanMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 - SINGLE-VALUE - USAGE userApplications ) - -attributetype ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.4.1 NAME ( 'mqttPublishTopic' 'mpt' ) - EQUALITY caseIgnoreMatch - SUBSTR caseIgnoreSubstringsMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 - USAGE userApplications ) -attributetype ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.4.2 NAME ( 'mqttSubscriptionTopic' 'mst' ) - EQUALITY caseIgnoreMatch - SUBSTR caseIgnoreSubstringsMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 - USAGE userApplications ) -attributetype ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.4.3 NAME ( 'mqttPubSubTopic' 'mpst' ) - EQUALITY caseIgnoreMatch - SUBSTR caseIgnoreSubstringsMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 - USAGE userApplications ) -attributetype ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.4.4 NAME ( 'mqttAccountName' 'man' ) - EQUALITY caseIgnoreMatch - SUBSTR caseIgnoreSubstringsMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 - USAGE userApplications ) - - -objectclass ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.4 NAME 'mqttUser' - AUXILIARY - MAY ( mqttPublishTopic $ mqttSubscriptionTopic $ mqttPubSubTopic $ mqttAccountName) ) - -objectclass ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.2 NAME 'mqttDevice' - SUP top - STRUCTURAL - MUST ( uid ) - MAY ( isEnabled ) ) - -objectclass ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.3 NAME 'mqttSecurity' - SUP top - AUXILIARY - MAY ( userPassword $ userPKCS12 $ pwdAttribute $ pwdLockout ) ) diff --git a/apps/emqx_connector/src/emqx_connector.app.src b/apps/emqx_connector/src/emqx_connector.app.src index 7614ddac3..cd8ce864c 100644 --- a/apps/emqx_connector/src/emqx_connector.app.src +++ b/apps/emqx_connector/src/emqx_connector.app.src @@ -1,7 +1,7 @@ %% -*- mode: erlang -*- {application, emqx_connector, [ {description, "EMQX Data Integration Connectors"}, - {vsn, "0.1.28"}, + {vsn, "0.1.29"}, {registered, []}, {mod, {emqx_connector_app, []}}, {applications, [ diff --git a/apps/emqx_connector/src/emqx_connector_ldap.erl b/apps/emqx_connector/src/emqx_connector_ldap.erl deleted file mode 100644 index c8c134f55..000000000 --- a/apps/emqx_connector/src/emqx_connector_ldap.erl +++ /dev/null @@ -1,199 +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_connector_ldap). - --include("emqx_connector.hrl"). --include_lib("typerefl/include/types.hrl"). --include_lib("emqx/include/logger.hrl"). - --export([roots/0, fields/1]). - --behaviour(emqx_resource). - -%% callbacks of behaviour emqx_resource --export([ - callback_mode/0, - on_start/2, - on_stop/2, - on_query/3, - on_get_status/2 -]). - --export([connect/1]). - --export([search/4]). - -%% port is not expected from configuration because -%% all servers expected to use the same port number --define(LDAP_HOST_OPTIONS, #{no_port => true}). - -%%===================================================================== -roots() -> - ldap_fields() ++ emqx_connector_schema_lib:ssl_fields(). - -%% this schema has no sub-structs -fields(_) -> []. - -%% =================================================================== -callback_mode() -> always_sync. - -on_start( - InstId, - #{ - servers := Servers0, - port := Port, - bind_dn := BindDn, - bind_password := BindPassword, - timeout := Timeout, - pool_size := PoolSize, - ssl := SSL - } = Config -) -> - ?SLOG(info, #{ - msg => "starting_ldap_connector", - connector => InstId, - config => emqx_utils:redact(Config) - }), - Servers1 = emqx_schema:parse_servers(Servers0, ?LDAP_HOST_OPTIONS), - Servers = - lists:map( - fun - (#{hostname := Host, port := Port0}) -> - {Host, Port0}; - (#{hostname := Host}) -> - Host - end, - Servers1 - ), - SslOpts = - case maps:get(enable, SSL) of - true -> - [ - {ssl, true}, - {sslopts, emqx_tls_lib:to_client_opts(SSL)} - ]; - false -> - [{ssl, false}] - end, - Opts = [ - {servers, Servers}, - {port, Port}, - {bind_dn, BindDn}, - {bind_password, BindPassword}, - {timeout, Timeout}, - {pool_size, PoolSize}, - {auto_reconnect, ?AUTO_RECONNECT_INTERVAL} - ], - case emqx_resource_pool:start(InstId, ?MODULE, Opts ++ SslOpts) of - ok -> {ok, #{pool_name => InstId}}; - {error, Reason} -> {error, Reason} - end. - -on_stop(InstId, _State) -> - ?SLOG(info, #{ - msg => "stopping_ldap_connector", - connector => InstId - }), - emqx_resource_pool:stop(InstId). - -on_query(InstId, {search, Base, Filter, Attributes}, #{pool_name := PoolName} = State) -> - Request = {Base, Filter, Attributes}, - ?TRACE( - "QUERY", - "ldap_connector_received", - #{request => Request, connector => InstId, state => State} - ), - case - Result = ecpool:pick_and_do( - PoolName, - {?MODULE, search, [Base, Filter, Attributes]}, - no_handover - ) - of - {error, Reason} -> - ?SLOG(error, #{ - msg => "ldap_connector_do_request_failed", - request => Request, - connector => InstId, - reason => Reason - }), - case Reason of - ecpool_empty -> - {error, {recoverable_error, Reason}}; - _ -> - Result - end; - _ -> - Result - end. - -on_get_status(_InstId, _State) -> connected. - -search(Conn, Base, Filter, Attributes) -> - eldap2:search(Conn, [ - {base, Base}, - {filter, Filter}, - {attributes, Attributes}, - {deref, eldap2:'derefFindingBaseObj'()} - ]). - -%% =================================================================== -connect(Opts) -> - Servers = proplists:get_value(servers, Opts, ["localhost"]), - Port = proplists:get_value(port, Opts, 389), - Timeout = proplists:get_value(timeout, Opts, 30), - BindDn = proplists:get_value(bind_dn, Opts), - BindPassword = proplists:get_value(bind_password, Opts), - SslOpts = - case proplists:get_value(ssl, Opts, false) of - true -> - [{sslopts, proplists:get_value(sslopts, Opts, [])}, {ssl, true}]; - false -> - [{ssl, false}] - end, - LdapOpts = - [ - {port, Port}, - {timeout, Timeout} - ] ++ SslOpts, - {ok, LDAP} = eldap2:open(Servers, LdapOpts), - ok = eldap2:simple_bind(LDAP, BindDn, BindPassword), - {ok, LDAP}. - -ldap_fields() -> - [ - {servers, servers()}, - {port, fun port/1}, - {pool_size, fun emqx_connector_schema_lib:pool_size/1}, - {bind_dn, fun bind_dn/1}, - {bind_password, fun emqx_connector_schema_lib:password/1}, - {timeout, fun duration/1}, - {auto_reconnect, fun emqx_connector_schema_lib:auto_reconnect/1} - ]. - -servers() -> - emqx_schema:servers_sc(#{}, ?LDAP_HOST_OPTIONS). - -bind_dn(type) -> binary(); -bind_dn(default) -> 0; -bind_dn(_) -> undefined. - -port(type) -> integer(); -port(default) -> 389; -port(_) -> undefined. - -duration(type) -> emqx_schema:timeout_duration_ms(); -duration(_) -> undefined. diff --git a/apps/emqx_ldap/rebar.config b/apps/emqx_ldap/rebar.config index e6e2db243..1e53ccafe 100644 --- a/apps/emqx_ldap/rebar.config +++ b/apps/emqx_ldap/rebar.config @@ -3,5 +3,6 @@ {erl_opts, [debug_info]}. {deps, [ {emqx_connector, {path, "../../apps/emqx_connector"}}, - {emqx_resource, {path, "../../apps/emqx_resource"}} + {emqx_resource, {path, "../../apps/emqx_resource"}}, + {emqx_authn, {path, "../../apps/emqx_authn"}} ]}. diff --git a/apps/emqx_ldap/src/emqx_ldap.app.src b/apps/emqx_ldap/src/emqx_ldap.app.src index 976d85371..32ffb4329 100644 --- a/apps/emqx_ldap/src/emqx_ldap.app.src +++ b/apps/emqx_ldap/src/emqx_ldap.app.src @@ -4,7 +4,8 @@ {registered, []}, {applications, [ kernel, - stdlib + stdlib, + emqx_authn ]}, {env, []}, {modules, []}, diff --git a/apps/emqx_ldap/src/emqx_ldap.erl b/apps/emqx_ldap/src/emqx_ldap.erl index 7cc9a6be0..d505f92d0 100644 --- a/apps/emqx_ldap/src/emqx_ldap.erl +++ b/apps/emqx_ldap/src/emqx_ldap.erl @@ -57,11 +57,18 @@ fields(config) -> {base_object, ?HOCON(binary(), #{ desc => ?DESC(base_object), - required => true + required => true, + validator => fun emqx_schema:non_empty_string/1 })}, {filter, - ?HOCON(binary(), #{desc => ?DESC(filter), default => <<"(objectClass=mqttUser)">>})}, - {auto_reconnect, fun ?ECS:auto_reconnect/1} + ?HOCON( + binary(), + #{ + desc => ?DESC(filter), + default => <<"(objectClass=mqttUser)">>, + validator => fun emqx_schema:non_empty_string/1 + } + )} ] ++ emqx_connector_schema_lib:ssl_fields(). server() -> @@ -165,26 +172,21 @@ on_query( #{base_tokens := BaseTks, filter_tokens := FilterTks} = State ) -> Base = emqx_placeholder:proc_tmpl(BaseTks, Data), - case FilterTks of - [] -> - do_ldap_query(InstId, [{base, Base} | SearchOptions], State); - _ -> - FilterBin = emqx_placeholder:proc_tmpl(FilterTks, Data), - case emqx_ldap_filter_parser:scan_and_parse(FilterBin) of - {ok, Filter} -> - do_ldap_query( - InstId, - [{base, Base}, {filter, Filter} | SearchOptions], - State - ); - {error, Reason} = Error -> - ?SLOG(error, #{ - msg => "filter_parse_failed", - filter => FilterBin, - reason => Reason - }), - Error - end + FilterBin = emqx_placeholder:proc_tmpl(FilterTks, Data), + case emqx_ldap_filter_parser:scan_and_parse(FilterBin) of + {ok, Filter} -> + do_ldap_query( + InstId, + [{base, Base}, {filter, Filter} | SearchOptions], + State + ); + {error, Reason} = Error -> + ?SLOG(error, #{ + msg => "filter_parse_failed", + filter => FilterBin, + reason => Reason + }), + Error end. do_ldap_query( diff --git a/apps/emqx_ldap/src/emqx_ldap_authn.erl b/apps/emqx_ldap/src/emqx_ldap_authn.erl new file mode 100644 index 000000000..c7bf61cd2 --- /dev/null +++ b/apps/emqx_ldap/src/emqx_ldap_authn.erl @@ -0,0 +1,287 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_ldap_authn). + +-include_lib("emqx_authn/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). + +%% 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([ + namespace/0, + tags/0, + roots/0, + fields/1, + desc/1 +]). + +-export([ + refs/0, + create/2, + update/2, + authenticate/2, + destroy/1 +]). + +-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, mysql))}]. + +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:duration_ms(); +query_timeout(desc) -> ?DESC(?FUNCTION_NAME); +query_timeout(default) -> <<"5s">>; +query_timeout(_) -> undefined. + +%%------------------------------------------------------------------------------ +%% APIs +%%------------------------------------------------------------------------------ + +refs() -> + [hoconsc:ref(?MODULE, ldap)]. + +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_ldap, Config), + {ok, State#{resource_id => ResourceId}}. + +update(Config0, #{resource_id := ResourceId} = _State) -> + {Config, NState} = parse_config(Config0), + case emqx_authn_utils:update_resource(emqx_ldap, Config, ResourceId) of + {error, Reason} -> + error({load_config_error, Reason}); + {ok, _} -> + {ok, NState#{resource_id => ResourceId}} + end. + +destroy(#{resource_id := ResourceId}) -> + _ = emqx_resource:remove_local(ResourceId), + ok. + +authenticate(#{auth_method := _}, _) -> + ignore; +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) -> + State = lists:foldl( + fun(Key, Acc) -> + Value = + case maps:get(Key, Config) of + Bin when is_binary(Bin) -> + erlang:binary_to_list(Bin); + Any -> + Any + end, + Acc#{Key => Value} + end, + #{}, + [password_attribute, is_superuser_attribute, query_timeout] + ), + {Config, State}. + +%% 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_passowrd/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_passowrd(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, <>} -> + 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, invalid_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, <>). + +hash_password(Algorithm, Data) -> + crypto:hash(Algorithm, Data). + +compare_password(hash, PasswordHash, PasswordHash) -> + true; +compare_password(base64, Base64HashData, PasswordHash) -> + Base64HashData =:= base64:encode(PasswordHash); +compare_password(_, _, _) -> + false. diff --git a/apps/emqx_ldap/test/data/emqx.io.ldif b/apps/emqx_ldap/test/data/emqx.io.ldif index f9833cd88..138651958 100644 --- a/apps/emqx_ldap/test/data/emqx.io.ldif +++ b/apps/emqx_ldap/test/data/emqx.io.ldif @@ -133,3 +133,20 @@ mqttPubSubTopic: mqttuser0005/pubsub/+ mqttPubSubTopic: mqttuser0005/pubsub/# userPassword: {SHA}jKnxeEDGR14kE8AR7yuVFOelhz4= +objectClass: top +dn:uid=mqttuser0006,ou=testdevice,dc=emqx,dc=io +objectClass: mqttUser +objectClass: mqttDevice +objectClass: mqttSecurity +uid: mqttuser0006 +isEnabled: FALSE +userPassword: {SHA}AlNm2FUO8G5BK5pCggfrPauRqN0= + +objectClass: top +dn:uid=mqttuser0007,ou=testdevice,dc=emqx,dc=io +objectClass: mqttUser +objectClass: mqttDevice +objectClass: mqttSecurity +uid: mqttuser0007 +isSuperuser: TRUE +userPassword: {SHA}axpQGbl00j3jvOG058y313ocnBk= diff --git a/apps/emqx_ldap/test/data/emqx.schema b/apps/emqx_ldap/test/data/emqx.schema index 55f92269b..d08548272 100644 --- a/apps/emqx_ldap/test/data/emqx.schema +++ b/apps/emqx_ldap/test/data/emqx.schema @@ -8,6 +8,12 @@ attributetype ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.1.3 NAME 'isEnabled' SINGLE-VALUE USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.1.4 NAME 'isSuperuser' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + SINGLE-VALUE + USAGE userApplications ) + attributetype ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.4.1 NAME ( 'mqttPublishTopic' 'mpt' ) EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch @@ -32,7 +38,7 @@ attributetype ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.4.4 NAME ( 'mqttAccountName' 'ma objectclass ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.4 NAME 'mqttUser' AUXILIARY - MAY ( mqttPublishTopic $ mqttSubscriptionTopic $ mqttPubSubTopic $ mqttAccountName) ) + MAY ( mqttPublishTopic $ mqttSubscriptionTopic $ mqttPubSubTopic $ mqttAccountName $ isSuperuser) ) objectclass ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.2 NAME 'mqttDevice' SUP top @@ -43,4 +49,4 @@ objectclass ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.2 NAME 'mqttDevice' objectclass ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.3 NAME 'mqttSecurity' SUP top AUXILIARY - MAY ( userPassword $ userPKCS12 $ pwdAttribute $ pwdLockout ) ) + MUST ( userPassword ) ) diff --git a/apps/emqx_ldap/test/emqx_ldap_SUITE.erl b/apps/emqx_ldap/test/emqx_ldap_SUITE.erl index 9386b1738..bf20629ec 100644 --- a/apps/emqx_ldap/test/emqx_ldap_SUITE.erl +++ b/apps/emqx_ldap/test/emqx_ldap_SUITE.erl @@ -150,7 +150,6 @@ ldap_config(Config) -> io_lib:format( "" "\n" - " auto_reconnect = true\n" " username= \"cn=root,dc=emqx,dc=io\"\n" " password = public\n" " pool_size = 8\n" @@ -183,10 +182,10 @@ data() -> port(tcp) -> 389; port(ssl) -> 636; -port(Config) -> port(proplists:get_value(group, Config)). +port(Config) -> port(proplists:get_value(group, Config, tcp)). ssl(Config) -> - case proplists:get_value(group, Config) of + case proplists:get_value(group, Config, tcp) of tcp -> "ssl.enable=false"; ssl -> diff --git a/apps/emqx_ldap/test/emqx_ldap_authn_SUITE.erl b/apps/emqx_ldap/test/emqx_ldap_authn_SUITE.erl new file mode 100644 index 000000000..ce7481ef8 --- /dev/null +++ b/apps/emqx_ldap/test/emqx_ldap_authn_SUITE.erl @@ -0,0 +1,259 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_ldap_authn_SUITE). + +-compile(nowarn_export_all). +-compile(export_all). + +-include_lib("emqx_authn/include/emqx_authn.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-define(LDAP_HOST, "ldap"). +-define(LDAP_DEFAULT_PORT, 389). +-define(LDAP_RESOURCE, <<"emqx_authn_ldap_SUITE">>). + +-define(PATH, [authentication]). +-define(ResourceID, <<"password_based:ldap">>). + +all() -> + emqx_common_test_helpers:all(?MODULE). + +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 + ), + Config. + +init_per_suite(Config) -> + _ = application:load(emqx_conf), + case emqx_common_test_helpers:is_tcp_server_available(?LDAP_HOST, ?LDAP_DEFAULT_PORT) of + true -> + ok = emqx_common_test_helpers:start_apps([emqx_authn]), + ok = start_apps([emqx_resource]), + {ok, _} = emqx_resource:create_local( + ?LDAP_RESOURCE, + ?RESOURCE_GROUP, + emqx_ldap, + ldap_config(), + #{} + ), + Config; + false -> + {skip, no_ldap} + end. + +end_per_suite(_Config) -> + emqx_authn_test_lib:delete_authenticators( + [authentication], + ?GLOBAL + ), + ok = emqx_resource:remove_local(?LDAP_RESOURCE), + ok = stop_apps([emqx_resource]), + ok = emqx_common_test_helpers:stop_apps([emqx_authn]). + +%%------------------------------------------------------------------------------ +%% Tests +%%------------------------------------------------------------------------------ + +t_create(_Config) -> + AuthConfig = raw_ldap_auth_config(), + + {ok, _} = emqx:update_config( + ?PATH, + {create_authenticator, ?GLOBAL, AuthConfig} + ), + + {ok, [#{provider := emqx_ldap_authn}]} = emqx_authentication:list_authenticators(?GLOBAL), + emqx_authn_test_lib:delete_config(?ResourceID). + +t_create_invalid(_Config) -> + AuthConfig = raw_ldap_auth_config(), + + InvalidConfigs = + [ + AuthConfig#{<<"server">> => <<"unknownhost:3333">>}, + AuthConfig#{<<"password">> => <<"wrongpass">>} + ], + + lists:foreach( + fun(Config) -> + {ok, _} = emqx:update_config( + ?PATH, + {create_authenticator, ?GLOBAL, Config} + ), + emqx_authn_test_lib:delete_config(?ResourceID), + ?assertEqual( + {error, {not_found, {chain, ?GLOBAL}}}, + emqx_authentication:list_authenticators(?GLOBAL) + ) + end, + InvalidConfigs + ). + +t_authenticate(_Config) -> + ok = lists:foreach( + fun(Sample) -> + ct:pal("test_user_auth sample: ~p", [Sample]), + test_user_auth(Sample) + end, + user_seeds() + ). + +test_user_auth(#{ + credentials := Credentials0, + config_params := SpecificConfigParams, + result := Result +}) -> + AuthConfig = maps:merge(raw_ldap_auth_config(), SpecificConfigParams), + + {ok, _} = emqx:update_config( + ?PATH, + {create_authenticator, ?GLOBAL, AuthConfig} + ), + + Credentials = Credentials0#{ + listener => 'tcp:default', + protocol => mqtt + }, + + ?assertEqual(Result, emqx_access_control:authenticate(Credentials)), + + emqx_authn_test_lib:delete_authenticators( + [authentication], + ?GLOBAL + ). + +t_destroy(_Config) -> + AuthConfig = raw_ldap_auth_config(), + + {ok, _} = emqx:update_config( + ?PATH, + {create_authenticator, ?GLOBAL, AuthConfig} + ), + + {ok, [#{provider := emqx_ldap_authn, state := State}]} = + emqx_authentication:list_authenticators(?GLOBAL), + + {ok, _} = emqx_ldap_authn:authenticate( + #{ + username => <<"mqttuser0001">>, + password => <<"mqttuser0001">> + }, + State + ), + + emqx_authn_test_lib:delete_authenticators( + [authentication], + ?GLOBAL + ), + + % Authenticator should not be usable anymore + ?assertMatch( + ignore, + emqx_ldap_authn:authenticate( + #{ + username => <<"mqttuser0001">>, + password => <<"mqttuser0001">> + }, + State + ) + ). + +t_update(_Config) -> + CorrectConfig = raw_ldap_auth_config(), + IncorrectConfig = + CorrectConfig#{ + <<"base_object">> => <<"ou=testdevice,dc=emqx,dc=io">> + }, + + {ok, _} = emqx:update_config( + ?PATH, + {create_authenticator, ?GLOBAL, IncorrectConfig} + ), + + {error, _} = emqx_access_control:authenticate( + #{ + username => <<"mqttuser0001">>, + password => <<"mqttuser0001">>, + listener => 'tcp:default', + protocol => mqtt + } + ), + + % We update with config with correct query, provider should update and work properly + {ok, _} = emqx:update_config( + ?PATH, + {update_authenticator, ?GLOBAL, <<"password_based:ldap">>, CorrectConfig} + ), + + {ok, _} = emqx_access_control:authenticate( + #{ + username => <<"mqttuser0001">>, + password => <<"mqttuser0001">>, + listener => 'tcp:default', + protocol => mqtt + } + ). + +%%------------------------------------------------------------------------------ +%% Helpers +%%------------------------------------------------------------------------------ + +raw_ldap_auth_config() -> + #{ + <<"mechanism">> => <<"password_based">>, + <<"backend">> => <<"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 + }. + +user_seeds() -> + New = fun(Username, Password, Result) -> + #{ + credentials => #{ + username => Username, + password => Password + }, + config_params => #{}, + result => Result + } + end, + Valid = + lists:map( + fun(Idx) -> + Username = erlang:iolist_to_binary(io_lib:format("mqttuser000~b", [Idx])), + New(Username, Username, {ok, #{is_superuser => false}}) + end, + lists:seq(1, 5) + ), + [ + %% Not exists + New(<<"notexists">>, <<"notexists">>, {error, not_authorized}), + %% Wrong Password + New(<<"mqttuser0001">>, <<"wrongpassword">>, {error, invalid_password}), + %% Disabled + New(<<"mqttuser0006">>, <<"mqttuser0006">>, {error, user_disabled}), + %% IsSuperuser + New(<<"mqttuser0007">>, <<"mqttuser0007">>, {ok, #{is_superuser => true}}) + | Valid + ]. + +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). diff --git a/apps/emqx_machine/priv/reboot_lists.eterm b/apps/emqx_machine/priv/reboot_lists.eterm index 03dc8618a..51c2d2274 100644 --- a/apps/emqx_machine/priv/reboot_lists.eterm +++ b/apps/emqx_machine/priv/reboot_lists.eterm @@ -106,7 +106,8 @@ emqx_schema_registry, emqx_eviction_agent, emqx_node_rebalance, - emqx_ft + emqx_ft, + emqx_ldap ], %% must always be of type `load' ce_business_apps => diff --git a/apps/emqx_machine/src/emqx_machine.app.src b/apps/emqx_machine/src/emqx_machine.app.src index 9a9dedc28..bdd1db76e 100644 --- a/apps/emqx_machine/src/emqx_machine.app.src +++ b/apps/emqx_machine/src/emqx_machine.app.src @@ -3,7 +3,7 @@ {id, "emqx_machine"}, {description, "The EMQX Machine"}, % strict semver, bump manually! - {vsn, "0.2.9"}, + {vsn, "0.2.10"}, {modules, []}, {registered, []}, {applications, [kernel, stdlib, emqx_ctl]}, diff --git a/changes/ee/feat-11386.en.md b/changes/ee/feat-11386.en.md new file mode 100644 index 000000000..cb527fa42 --- /dev/null +++ b/changes/ee/feat-11386.en.md @@ -0,0 +1 @@ +Integrated the LDAP as a new authenticator. diff --git a/mix.exs b/mix.exs index a39288409..00d190136 100644 --- a/mix.exs +++ b/mix.exs @@ -194,7 +194,8 @@ defmodule EMQXUmbrella.MixProject do :emqx_schema_registry, :emqx_enterprise, :emqx_bridge_kinesis, - :emqx_bridge_azure_event_hub + :emqx_bridge_azure_event_hub, + :emqx_ldap ]) end diff --git a/rebar.config.erl b/rebar.config.erl index f679bc2bb..b45516d2b 100644 --- a/rebar.config.erl +++ b/rebar.config.erl @@ -106,6 +106,7 @@ is_community_umbrella_app("apps/emqx_schema_registry") -> false; is_community_umbrella_app("apps/emqx_enterprise") -> false; is_community_umbrella_app("apps/emqx_bridge_kinesis") -> false; is_community_umbrella_app("apps/emqx_bridge_azure_event_hub") -> false; +is_community_umbrella_app("apps/emqx_ldap") -> false; is_community_umbrella_app(_) -> true. is_jq_supported() -> diff --git a/rel/i18n/emqx_connector_ldap.hocon b/rel/i18n/emqx_connector_ldap.hocon deleted file mode 100644 index 64a953816..000000000 --- a/rel/i18n/emqx_connector_ldap.hocon +++ /dev/null @@ -1,21 +0,0 @@ -emqx_connector_ldap { - -bind_dn.desc: -"""LDAP's Binding Distinguished Name (DN)""" - -bind_dn.label: -"""Bind DN""" - -port.desc: -"""LDAP Port""" - -port.label: -"""Port""" - -timeout.desc: -"""LDAP's query timeout""" - -timeout.label: -"""timeout""" - -} diff --git a/rel/i18n/emqx_ldap.hocon b/rel/i18n/emqx_ldap.hocon index 9f3d9216b..cd2865d85 100644 --- a/rel/i18n/emqx_ldap.hocon +++ b/rel/i18n/emqx_ldap.hocon @@ -3,7 +3,7 @@ emqx_ldap { server.desc: """The IPv4 or IPv6 address or the hostname to connect to.
A host entry has the following form: `Host[:Port]`.
-The MySQL default port 3306 is used if `[:Port]` is not specified.""" +The LDAP default port 389 is used if `[:Port]` is not specified.""" server.label: """Server Host""" diff --git a/rel/i18n/emqx_ldap_authn.hocon b/rel/i18n/emqx_ldap_authn.hocon new file mode 100644 index 000000000..7c59f2039 --- /dev/null +++ b/rel/i18n/emqx_ldap_authn.hocon @@ -0,0 +1,24 @@ +emqx_ldap_authn { + +ldap.desc: +"""Configuration of authenticator using LDAP as authentication data source.""" + +password_attribute.desc: +"""Indicates which attribute is used to represent the user's password.""" + +password_attribute.label: +"""Password Attribute""" + +is_superuser_attribute.desc: +"""Indicates which attribute is used to represent whether the user is a super user.""" + +is_superuser_attribute.label: +"""IsSuperuser Attribute""" + +query_timeout.desc: +"""Timeout for the LDAP query.""" + +query_timeout.label: +"""Query Timeout""" + +}