From fa6343cc80f971f74b499b299ef6267bade049b4 Mon Sep 17 00:00:00 2001 From: firest Date: Mon, 31 Jul 2023 19:30:24 +0800 Subject: [PATCH] feat(ldap): add LDAP connector --- apps/emqx_ldap/BSL.txt | 94 +++++++ apps/emqx_ldap/README.md | 14 ++ apps/emqx_ldap/docker-ct | 1 + apps/emqx_ldap/rebar.config | 7 + apps/emqx_ldap/src/emqx_ldap.app.src | 13 + apps/emqx_ldap/src/emqx_ldap.erl | 230 ++++++++++++++++++ apps/emqx_ldap/src/emqx_ldap_filter_lexer.xrl | 24 ++ .../emqx_ldap/src/emqx_ldap_filter_parser.yrl | 133 ++++++++++ apps/emqx_ldap/test/emqx_ldap_SUITE.erl | 165 +++++++++++++ rel/i18n/emqx_ldap.hocon | 25 ++ 10 files changed, 706 insertions(+) create mode 100644 apps/emqx_ldap/BSL.txt create mode 100644 apps/emqx_ldap/README.md create mode 100644 apps/emqx_ldap/docker-ct create mode 100644 apps/emqx_ldap/rebar.config create mode 100644 apps/emqx_ldap/src/emqx_ldap.app.src create mode 100644 apps/emqx_ldap/src/emqx_ldap.erl create mode 100644 apps/emqx_ldap/src/emqx_ldap_filter_lexer.xrl create mode 100644 apps/emqx_ldap/src/emqx_ldap_filter_parser.yrl create mode 100644 apps/emqx_ldap/test/emqx_ldap_SUITE.erl create mode 100644 rel/i18n/emqx_ldap.hocon diff --git a/apps/emqx_ldap/BSL.txt b/apps/emqx_ldap/BSL.txt new file mode 100644 index 000000000..0acc0e696 --- /dev/null +++ b/apps/emqx_ldap/BSL.txt @@ -0,0 +1,94 @@ +Business Source License 1.1 + +Licensor: Hangzhou EMQ Technologies Co., Ltd. +Licensed Work: EMQX Enterprise Edition + The Licensed Work is (c) 2023 + Hangzhou EMQ Technologies Co., Ltd. +Additional Use Grant: Students and educators are granted right to copy, + modify, and create derivative work for research + or education. +Change Date: 2027-02-01 +Change License: Apache License, Version 2.0 + +For information about alternative licensing arrangements for the Software, +please contact Licensor: https://www.emqx.com/en/contact + +Notice + +The Business Source License (this document, or the “License”) is not an Open +Source license. However, the Licensed Work will eventually be made available +under an Open Source License, as stated in this License. + +License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. +“Business Source License” is a trademark of MariaDB Corporation Ab. + +----------------------------------------------------------------------------- + +Business Source License 1.1 + +Terms + +The Licensor hereby grants you the right to copy, modify, create derivative +works, redistribute, and make non-production use of the Licensed Work. The +Licensor may make an Additional Use Grant, above, permitting limited +production use. + +Effective on the Change Date, or the fourth anniversary of the first publicly +available distribution of a specific version of the Licensed Work under this +License, whichever comes first, the Licensor hereby grants you rights under +the terms of the Change License, and the rights granted in the paragraph +above terminate. + +If your use of the Licensed Work does not comply with the requirements +currently in effect as described in this License, you must purchase a +commercial license from the Licensor, its affiliated entities, or authorized +resellers, or you must refrain from using the Licensed Work. + +All copies of the original and modified Licensed Work, and derivative works +of the Licensed Work, are subject to this License. This License applies +separately for each version of the Licensed Work and the Change Date may vary +for each version of the Licensed Work released by Licensor. + +You must conspicuously display this License on each original or modified copy +of the Licensed Work. If you receive the Licensed Work in original or +modified form from a third party, the terms and conditions set forth in this +License apply to your use of that work. + +Any use of the Licensed Work in violation of this License will automatically +terminate your rights under this License for the current and all other +versions of the Licensed Work. + +This License does not grant you any right in any trademark or logo of +Licensor or its affiliates (provided that you may use a trademark or logo of +Licensor as expressly required by this License). + +TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +TITLE. + +MariaDB hereby grants you permission to use this License’s text to license +your works, and to refer to it using the trademark “Business Source License”, +as long as you comply with the Covenants of Licensor below. + +Covenants of Licensor + +In consideration of the right to use this License’s text and the “Business +Source License” name and trademark, Licensor covenants to MariaDB, and to all +other recipients of the licensed work to be provided by Licensor: + +1. To specify as the Change License the GPL Version 2.0 or any later version, + or a license that is compatible with GPL Version 2.0 or a later version, + where “compatible” means that software provided under the Change License can + be included in a program with software provided under GPL Version 2.0 or a + later version. Licensor may specify additional Change Licenses without + limitation. + +2. To either: (a) specify an additional grant of rights to use that does not + impose any additional restriction on the right granted in this License, as + the Additional Use Grant; or (b) insert the text “None”. + +3. To specify a Change Date. + +4. Not to modify this License in any other way. diff --git a/apps/emqx_ldap/README.md b/apps/emqx_ldap/README.md new file mode 100644 index 000000000..5923a10d7 --- /dev/null +++ b/apps/emqx_ldap/README.md @@ -0,0 +1,14 @@ +# LDAP Connector + +This application houses the LDAP connector. +It provides the APIs to connect to the LDAP service. + +It is used by the emqx_authz and emqx_authn applications to check user permissions. + +## Contributing + +Please see our [contributing.md](../../CONTRIBUTING.md). + +## License + +See [APL](../../APL.txt). diff --git a/apps/emqx_ldap/docker-ct b/apps/emqx_ldap/docker-ct new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/emqx_ldap/docker-ct @@ -0,0 +1 @@ + diff --git a/apps/emqx_ldap/rebar.config b/apps/emqx_ldap/rebar.config new file mode 100644 index 000000000..e6e2db243 --- /dev/null +++ b/apps/emqx_ldap/rebar.config @@ -0,0 +1,7 @@ +%% -*- mode: erlang; -*- + +{erl_opts, [debug_info]}. +{deps, [ + {emqx_connector, {path, "../../apps/emqx_connector"}}, + {emqx_resource, {path, "../../apps/emqx_resource"}} +]}. diff --git a/apps/emqx_ldap/src/emqx_ldap.app.src b/apps/emqx_ldap/src/emqx_ldap.app.src new file mode 100644 index 000000000..976d85371 --- /dev/null +++ b/apps/emqx_ldap/src/emqx_ldap.app.src @@ -0,0 +1,13 @@ +{application, emqx_ldap, [ + {description, "EMQX LDAP Connector"}, + {vsn, "0.1.0"}, + {registered, []}, + {applications, [ + kernel, + stdlib + ]}, + {env, []}, + {modules, []}, + + {links, []} +]}. diff --git a/apps/emqx_ldap/src/emqx_ldap.erl b/apps/emqx_ldap/src/emqx_ldap.erl new file mode 100644 index 000000000..4b43bb6f6 --- /dev/null +++ b/apps/emqx_ldap/src/emqx_ldap.erl @@ -0,0 +1,230 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- +-module(emqx_ldap). + +-include_lib("emqx_connector/include/emqx_connector.hrl"). +-include_lib("typerefl/include/types.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). +-include_lib("emqx/include/logger.hrl"). +-include_lib("snabbkaffe/include/snabbkaffe.hrl"). +-include_lib("eldap/include/eldap.hrl"). + +-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 +]). + +%% ecpool connect & reconnect +-export([connect/1]). + +-export([roots/0, fields/1]). + +-export([do_get_status/1]). + +-define(LDAP_HOST_OPTIONS, #{ + default_port => 389 +}). + +-type params_tokens() :: #{atom() => list()}. +-type state() :: + #{ + pool_name := binary(), + base_tokens := params_tokens(), + filter_tokens := params_tokens() + }. + +-define(ECS, emqx_connector_schema_lib). + +%%===================================================================== +%% Hocon schema +roots() -> + [{config, #{type => hoconsc:ref(?MODULE, config)}}]. + +fields(config) -> + [ + {server, server()}, + {pool_size, fun ?ECS:pool_size/1}, + {username, fun ensure_username/1}, + {password, fun ?ECS:password/1}, + {base_object, + ?HOCON(binary(), #{ + desc => ?DESC(base_object), + required => true + })}, + {filter, ?HOCON(binary(), #{desc => ?DESC(filter), default => ""})}, + {auto_reconnect, fun ?ECS:auto_reconnect/1} + ] ++ emqx_connector_schema_lib:ssl_fields(). + +server() -> + Meta = #{desc => ?DESC("server")}, + emqx_schema:servers_sc(Meta, ?LDAP_HOST_OPTIONS). + +ensure_username(required) -> + true; +ensure_username(Field) -> + ?ECS:username(Field). + +%% =================================================================== +callback_mode() -> always_sync. + +-spec on_start(binary(), hoconsc:config()) -> {ok, state()} | {error, _}. +on_start( + InstId, + #{ + server := Server, + pool_size := PoolSize, + ssl := SSL + } = Config +) -> + HostPort = emqx_schema:parse_server(Server, ?LDAP_HOST_OPTIONS), + ?SLOG(info, #{ + msg => "starting_ldap_connector", + connector => InstId, + config => emqx_utils:redact(Config) + }), + + Config2 = maps:merge(Config, HostPort), + Config3 = + case maps:get(enable, SSL) of + true -> + Config2#{sslopts => emqx_tls_lib:to_client_opts(SSL)}; + false -> + Config2 + end, + Options = [ + {pool_size, PoolSize}, + {auto_reconnect, ?AUTO_RECONNECT_INTERVAL}, + {options, Config3} + ], + + case emqx_resource_pool:start(InstId, ?MODULE, Options) of + ok -> + {ok, prepare_template(Config, #{pool_name => InstId})}; + {error, Reason} -> + ?tp( + ldap_connector_start_failed, + #{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, {query, Data}, State) -> + on_query(InstId, {query, Data}, [], State); +on_query(InstId, {query, Data, Attrs}, State) -> + on_query(InstId, {query, Data}, [{attributes, Attrs}], State); +on_query(InstId, {query, Data, Attrs, Timeout}, State) -> + on_query(InstId, {query, Data}, [{attributes, Attrs}, {timeout, Timeout}], State). + +on_get_status(_InstId, #{pool_name := PoolName} = _State) -> + case emqx_resource_pool:health_check_workers(PoolName, fun ?MODULE:do_get_status/1) of + true -> + connected; + false -> + connecting + end. + +do_get_status(Conn) -> + erlang:is_process_alive(Conn). + +%% =================================================================== + +connect(Options) -> + #{host := Host, username := Username, password := Password} = + Conf = proplists:get_value(options, Options), + OpenOpts = maps:to_list(maps:with([port, sslopts], Conf)), + case eldap:open([Host], [{log, fun log/3}, OpenOpts]) of + {ok, Handle} = Ret -> + case eldap:simple_bind(Handle, Username, Password) of + ok -> Ret; + Error -> Error + end; + Error -> + Error + end. + +on_query( + InstId, + {query, Data}, + SearchOptions, + #{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), + %% TODO + Filter = FilterBin, + do_ldap_query( + InstId, + [{base, Base}, {filter, Filter} | SearchOptions], + State + ) + end. + +do_ldap_query( + InstId, + SearchOptions, + #{pool_name := PoolName} = State +) -> + LogMeta = #{connector => InstId, search => SearchOptions, state => State}, + ?TRACE("QUERY", "ldap_connector_received", LogMeta), + case + ecpool:pick_and_do( + PoolName, + {eldap, search, SearchOptions}, + handover + ) + of + {ok, Result} -> + ?tp( + ldap_connector_query_return, + #{result => Result} + ), + {ok, + case Result#eldap_search_result.entries of + [First | _] -> + %% TODO Support multi entries? + First; + _ -> + undefined + end}; + {error, Reason} -> + ?SLOG( + error, + LogMeta#{msg => "ldap_connector_do_sql_query_failed", reason => Reason} + ), + {error, {unrecoverable_error, Reason}} + end. + +log(Level, Format, Args) -> + ?SLOG( + Level, + #{ + msg => "ldap_log", + log => io_lib:format(Format, Args) + } + ). + +prepare_template(Config, State) -> + do_prepare_template(maps:to_list(maps:with([base_object, filter], Config)), State). + +do_prepare_template([{base_object, V} | T], State) -> + do_prepare_template(T, State#{base_tokens => emqx_placeholder:preproc_tmpl(V)}); +do_prepare_template([{filter, V} | T], State) -> + do_prepare_template(T, State#{filter_tokens => emqx_placeholder:preproc_tmpl(V)}). diff --git a/apps/emqx_ldap/src/emqx_ldap_filter_lexer.xrl b/apps/emqx_ldap/src/emqx_ldap_filter_lexer.xrl new file mode 100644 index 000000000..ae5c0cdbf --- /dev/null +++ b/apps/emqx_ldap/src/emqx_ldap_filter_lexer.xrl @@ -0,0 +1,24 @@ +Definitions. + +NonControl = [^()&|!=~><:*] +String = {NonControl}* +White = [\s\t\n\r]+ + +Rules. + +\( : {token, {lparen, TokenLine}}. +\) : {token, {rparen, TokenLine}}. +\& : {token, {'and', TokenLine}}. +\| : {token, {'or', TokenLine}}. +\! : {token, {'not', TokenLine}}. += : {token, {equal, TokenLine}}. +~= : {token, {approx, TokenLine}}. +>= : {token, {greaterOrEqual, TokenLine}}. +<= : {token, {lessOrEqual, TokenLine}}. +\* : {token, {asterisk, TokenLine}}. +\: : {token, {colon, TokenLine}}. +dn : {token, {dn, TokenLine}}. +{White} : skip_token. +{String} : {token, {string, TokenLine, TokenChars}}. + +Erlang code. diff --git a/apps/emqx_ldap/src/emqx_ldap_filter_parser.yrl b/apps/emqx_ldap/src/emqx_ldap_filter_parser.yrl new file mode 100644 index 000000000..2e9cf193d --- /dev/null +++ b/apps/emqx_ldap/src/emqx_ldap_filter_parser.yrl @@ -0,0 +1,133 @@ +Nonterminals +filter filtercomp filterlist item simple present substring initial any final extensible attr value type dnattrs matchingrule. + +Terminals +lparen rparen 'and' 'or' 'not' equal approx greaterOrEqual lessOrEqual asterisk colon dn string. + +Rootsymbol filter. +Left 100 present. +Left 500 substring. + +filter -> + lparen filtercomp rparen : '$2'. + +filtercomp -> + 'and' filterlist: 'and'('$2'). +filtercomp -> + 'or' filterlist: 'or'('$2'). +filtercomp -> + 'not' filterlist: 'not'('$2'). +filtercomp -> + item: '$1'. + +filterlist -> + filter: '$1'. +filterlist -> + filter filterlist: ['$1' | '$2']. + +item -> + simple: '$1'. +item -> + present: '$1'. +item -> + substring: '$1'. +item-> + extensible: '$1'. + +simple -> + attr equal value: equal('$1', '$3'). +simple -> + attr approx value: approx('$1', '$3'). +simple -> + attr greaterOrEqual value: greaterOrEqual('$1', '$3'). +simple -> + attr lessOrEqual value: lessOrEqual('$1', '$3'). + +present -> + attr equal asterisk: present('$1'). + +substring -> + attr equal initial asterisk any final: substrings('$1', ['$3', '$5', '$6']). +substring -> + attr equal asterisk any final: substrings('$1', ['$4', '$5']). +substring -> + attr equal initial asterisk any: substrings('$1', ['$3', '$5']). +substring -> + attr equal asterisk any: substrings('$1', ['$4']). + +initial -> + value: {initial, '$1'}. + +final -> + value: {final, '$1'}. + +any -> any value asterisk: 'any'('$1', '$2'). +any -> '$empty': []. + +extensible -> + type dnattrs matchingrule colon equal value : extensible('$6', ['$1', '$2', '$3']). +extensible -> + type dnattrs colon equal value: extensible('$5', ['$1', '$2']). +extensible -> + type matchingrule colon equal value: extensible('$5', ['$1', '$2']). +extensible -> + type colon equal value: extensible('$4', []). + +extensible -> + dnattrs matchingrule colon equal value: extensible('$5', ['$1', '$2']). +extensible -> + matchingrule colon equal value: extensible('$4', ['$1']). + +attr -> + string: get_value('$1'). + +value -> + string: get_value('$1'). + +type -> + value: {type, '$1'}. + +dnattrs -> + colon dn: {dnAttributes, true}. + +matchingrule -> + colon value: {matchingRule, '$2'}. + +Erlang code. + +'and'(Value) -> + eldap:'and'(Value). + +'or'(Value) -> + eldap:'or'(Value). + +'not'(Value) -> + eldap:'not'(Value). + +equal(Attr, Value) -> + eldap:equalityMatch(Attr, Value). + +approx(Attr, Value) -> + eldap:approxMatch(Attr, Value). + +greaterOrEqual(Attr, Value) -> + eldap:greaterOrEqual(Attr, Value). + +lessOrEqual(Attr, Value) -> + eldap:lessOrEqual(Attr, Value). + +present(Value) -> + eldap:present(Value). + +substrings(Attr, List) -> + eldap:substrings(Attr, flatten(List)). + +'any'(List, Item) -> + [{any, Item} | List]. + +extensible(Value, Opts) -> eldap:extensibleMatch(Value, Opts). + +flatten(List) -> lists:flatten(List). + +get_value({_Token, _Line, Value}) -> + Value. diff --git a/apps/emqx_ldap/test/emqx_ldap_SUITE.erl b/apps/emqx_ldap/test/emqx_ldap_SUITE.erl new file mode 100644 index 000000000..515f8b250 --- /dev/null +++ b/apps/emqx_ldap/test/emqx_ldap_SUITE.erl @@ -0,0 +1,165 @@ +% %%-------------------------------------------------------------------- +% %% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +% %% +% %% Licensed under the Apache License, Version 2.0 (the "License"); +% %% you may not use this file except in compliance with the License. +% %% You may obtain a copy of the License at +% %% http://www.apache.org/licenses/LICENSE-2.0 +% %% +% %% Unless required by applicable law or agreed to in writing, software +% %% distributed under the License is distributed on an "AS IS" BASIS, +% %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% %% See the License for the specific language governing permissions and +% %% limitations under the License. +% %%-------------------------------------------------------------------- + +-module(emqx_ldap_SUITE). + +-compile(nowarn_export_all). +-compile(export_all). + +-include_lib("emqx_connector/include/emqx_connector.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("emqx/include/emqx.hrl"). +-include_lib("stdlib/include/assert.hrl"). + +-define(MYSQL_HOST, "ldap"). +-define(MYSQL_RESOURCE_MOD, emqx_ldap). + +all() -> + emqx_common_test_helpers:all(?MODULE). + +groups() -> + []. + +init_per_suite(Config) -> + case emqx_common_test_helpers:is_tcp_server_available(?MYSQL_HOST, ?MYSQL_DEFAULT_PORT) of + true -> + ok = emqx_common_test_helpers:start_apps([emqx_conf]), + ok = emqx_connector_test_helpers:start_apps([emqx_resource]), + {ok, _} = application:ensure_all_started(emqx_connector), + Config; + false -> + {skip, no_ldap} + end. + +end_per_suite(_Config) -> + ok = emqx_common_test_helpers:stop_apps([emqx_conf]), + ok = emqx_connector_test_helpers:stop_apps([emqx_resource]), + _ = application:stop(emqx_connector). + +init_per_testcase(_, Config) -> + Config. + +end_per_testcase(_, _Config) -> + ok. + +% %%------------------------------------------------------------------------------ +% %% Testcases +% %%------------------------------------------------------------------------------ + +t_lifecycle(_Config) -> + perform_lifecycle_check( + <<"emqx_ldap_SUITE">>, + ldap_config() + ). + +perform_lifecycle_check(ResourceId, InitialConfig) -> + {ok, #{config := CheckedConfig}} = + emqx_resource:check_config(?MYSQL_RESOURCE_MOD, InitialConfig), + {ok, #{ + state := #{pool_name := PoolName} = State, + status := InitialStatus + }} = emqx_resource:create_local( + ResourceId, + ?CONNECTOR_RESOURCE_GROUP, + ?MYSQL_RESOURCE_MOD, + CheckedConfig, + #{} + ), + ?assertEqual(InitialStatus, connected), + % Instance should match the state and status of the just started resource + {ok, ?CONNECTOR_RESOURCE_GROUP, #{ + state := State, + status := InitialStatus + }} = + emqx_resource:get_instance(ResourceId), + ?assertEqual({ok, connected}, emqx_resource:health_check(ResourceId)), + % % Perform query as further check that the resource is working as expected + ?assertMatch({ok, _, [[1]]}, emqx_resource:query(ResourceId, test_query_no_params())), + ?assertMatch({ok, _, [[1]]}, emqx_resource:query(ResourceId, test_query_with_params())), + ?assertMatch( + {ok, _, [[1]]}, + emqx_resource:query( + ResourceId, + test_query_with_params_and_timeout() + ) + ), + ?assertEqual(ok, emqx_resource:stop(ResourceId)), + % Resource will be listed still, but state will be changed and healthcheck will fail + % as the worker no longer exists. + {ok, ?CONNECTOR_RESOURCE_GROUP, #{ + state := State, + status := StoppedStatus + }} = + emqx_resource:get_instance(ResourceId), + ?assertEqual(stopped, StoppedStatus), + ?assertEqual({error, resource_is_stopped}, emqx_resource:health_check(ResourceId)), + % Resource healthcheck shortcuts things by checking ets. Go deeper by checking pool itself. + ?assertEqual({error, not_found}, ecpool:stop_sup_pool(PoolName)), + % Can call stop/1 again on an already stopped instance + ?assertEqual(ok, emqx_resource:stop(ResourceId)), + % Make sure it can be restarted and the healthchecks and queries work properly + ?assertEqual(ok, emqx_resource:restart(ResourceId)), + % async restart, need to wait resource + timer:sleep(500), + {ok, ?CONNECTOR_RESOURCE_GROUP, #{status := InitialStatus}} = + emqx_resource:get_instance(ResourceId), + ?assertEqual({ok, connected}, emqx_resource:health_check(ResourceId)), + ?assertMatch({ok, _, [[1]]}, emqx_resource:query(ResourceId, test_query_no_params())), + ?assertMatch({ok, _, [[1]]}, emqx_resource:query(ResourceId, test_query_with_params())), + ?assertMatch( + {ok, _, [[1]]}, + emqx_resource:query( + ResourceId, + test_query_with_params_and_timeout() + ) + ), + % Stop and remove the resource in one go. + ?assertEqual(ok, emqx_resource:remove_local(ResourceId)), + ?assertEqual({error, not_found}, ecpool:stop_sup_pool(PoolName)), + % Should not even be able to get the resource data out of ets now unlike just stopping. + ?assertEqual({error, not_found}, emqx_resource:get_instance(ResourceId)). + +% %%------------------------------------------------------------------------------ +% %% Helpers +% %%------------------------------------------------------------------------------ + +ldap_config() -> + RawConfig = list_to_binary( + io_lib:format( + "" + "\n" + " auto_reconnect = true\n" + " database = mqtt\n" + " username= root\n" + " password = public\n" + " pool_size = 8\n" + " server = \"~s:~b\"\n" + " " + "", + [?MYSQL_HOST, ?MYSQL_DEFAULT_PORT] + ) + ), + + {ok, Config} = hocon:binary(RawConfig), + #{<<"config">> => Config}. + +test_query_no_params() -> + {sql, <<"SELECT 1">>}. + +test_query_with_params() -> + {sql, <<"SELECT ?">>, [1]}. + +test_query_with_params_and_timeout() -> + {sql, <<"SELECT ?">>, [1], 1000}. diff --git a/rel/i18n/emqx_ldap.hocon b/rel/i18n/emqx_ldap.hocon new file mode 100644 index 000000000..9f3d9216b --- /dev/null +++ b/rel/i18n/emqx_ldap.hocon @@ -0,0 +1,25 @@ +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.""" + +server.label: +"""Server Host""" + +base_object.desc: +"""The name of the base object entry (or possibly the root) relative to +which the Search is to be performed.""" + +base_object.label: +"""Base Object""" + +filter.desc: +"""The filter that defines the conditions that must be fulfilled in order +for the Search to match a given entry.""" + +filter.label: +"""Filter""" + +}