feat(ldap): set test env and add test suites

This commit is contained in:
firest 2023-08-01 18:48:43 +08:00
parent 8c9b136d15
commit f98f97f37e
11 changed files with 533 additions and 63 deletions

View File

@ -9,8 +9,8 @@ services:
args:
LDAP_TAG: ${LDAP_TAG}
image: openldap
ports:
- 389:389
#ports:
# - 389:389
restart: always
networks:
- emqx_bridge

View File

@ -1,18 +1,20 @@
FROM buildpack-deps:stretch
FROM buildpack-deps:bookworm
ARG LDAP_TAG=2.4.50
ARG LDAP_TAG=2.5.16
RUN apt-get update && apt-get install -y groff groff-base
RUN wget ftp://ftp.openldap.org/pub/OpenLDAP/openldap-release/openldap-${LDAP_TAG}.tgz \
&& gunzip -c openldap-${LDAP_TAG}.tgz | tar xvfB - \
RUN wget https://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-${LDAP_TAG}.tgz \
&& tar xvzf openldap-${LDAP_TAG}.tgz \
&& cd openldap-${LDAP_TAG} \
&& ./configure && make depend && make && make install \
&& cd .. && rm -rf openldap-${LDAP_TAG}
COPY .ci/docker-compose-file/openldap/slapd.conf /usr/local/etc/openldap/slapd.conf
COPY apps/emqx_authn/test/data/emqx.io.ldif /usr/local/etc/openldap/schema/emqx.io.ldif
COPY apps/emqx_authn/test/data/emqx.schema /usr/local/etc/openldap/schema/emqx.schema
COPY apps/emqx_authn/test/data/certs/*.pem /usr/local/etc/openldap/
COPY apps/emqx_ldap/test/data/emqx.io.ldif /usr/local/etc/openldap/schema/emqx.io.ldif
COPY apps/emqx_ldap/test/data/emqx.schema /usr/local/etc/openldap/schema/emqx.schema
COPY .ci/docker-compose-file/certs/ca.crt /usr/local/etc/openldap/cacert.pem
COPY .ci/docker-compose-file/certs/server.crt /usr/local/etc/openldap/cert.pem
COPY .ci/docker-compose-file/certs/server.key /usr/local/etc/openldap/key.pem
RUN mkdir -p /usr/local/etc/openldap/data \
&& slapadd -l /usr/local/etc/openldap/schema/emqx.io.ldif -f /usr/local/etc/openldap/slapd.conf

View File

@ -1 +1 @@
ldap

View File

@ -58,7 +58,8 @@ fields(config) ->
desc => ?DESC(base_object),
required => true
})},
{filter, ?HOCON(binary(), #{desc => ?DESC(filter), default => ""})},
{filter,
?HOCON(binary(), #{desc => ?DESC(filter), default => <<"(objectClass=mqttUser)">>})},
{auto_reconnect, fun ?ECS:auto_reconnect/1}
] ++ emqx_connector_schema_lib:ssl_fields().
@ -143,10 +144,10 @@ do_get_status(Conn) ->
%% ===================================================================
connect(Options) ->
#{host := Host, username := Username, password := Password} =
#{hostname := 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
case eldap:open([Host], [{log, fun log/3} | OpenOpts]) of
{ok, Handle} = Ret ->
case eldap:simple_bind(Handle, Username, Password) of
ok -> Ret;
@ -167,7 +168,7 @@ on_query(
[] ->
do_ldap_query(InstId, [{base, Base} | SearchOptions], State);
_ ->
FilterBin = emqx_placeholder:proc_tmpl(FilterTks, Data, #{return => rawlist}),
FilterBin = emqx_placeholder:proc_tmpl(FilterTks, Data),
case emqx_ldap_filter_parser:scan_and_parse(FilterBin) of
{ok, Filter} ->
do_ldap_query(
@ -195,7 +196,7 @@ do_ldap_query(
case
ecpool:pick_and_do(
PoolName,
{eldap, search, SearchOptions},
{eldap, search, [SearchOptions]},
handover
)
of
@ -204,14 +205,9 @@ do_ldap_query(
ldap_connector_query_return,
#{result => Result}
),
{ok,
case Result#eldap_search_result.entries of
[First | _] ->
%% TODO Support multi entries?
First;
_ ->
undefined
end};
{ok, Result#eldap_search_result.entries};
{error, noSuchObject} ->
{ok, []};
{error, Reason} ->
?SLOG(
error,
@ -235,4 +231,6 @@ prepare_template(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)}).
do_prepare_template(T, State#{filter_tokens => emqx_placeholder:preproc_tmpl(V)});
do_prepare_template([], State) ->
State.

View File

@ -1,9 +1,9 @@
Definitions.
Control = [()&|!=~><:*]
NonControl = [^()&|!=~><:*]
String = {NonControl}*
White = [\s\t\n\r]+
NonString = [^()&|!=~><:*\s\t\n\r]
String = {NonString}+
Rules.

View File

@ -16,12 +16,12 @@ filtercomp ->
filtercomp ->
'or' filterlist: 'or'('$2').
filtercomp ->
'not' filterlist: 'not'('$2').
'not' filter: 'not'('$2').
filtercomp ->
item: '$1'.
filterlist ->
filter: '$1'.
filter: ['$1'].
filterlist ->
filter filterlist: ['$1' | '$2'].
@ -71,7 +71,7 @@ extensible ->
extensible ->
type matchingrule colon equal value: extensible('$5', ['$1', '$2']).
extensible ->
type colon equal value: extensible('$4', []).
type colon equal value: extensible('$4', ['$1']).
extensible ->
dnattrs matchingrule colon equal value: extensible('$5', ['$1', '$2']).
@ -125,7 +125,7 @@ substrings(Attr, List) ->
eldap:substrings(Attr, flatten(List)).
'any'(List, Item) ->
[{any, Item} | List].
[List, {any, Item}].
extensible(Value, Opts) -> eldap:extensibleMatch(Value, Opts).

View File

@ -0,0 +1,135 @@
## 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=

View File

@ -0,0 +1,46 @@
#
# 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 ) )

View File

@ -22,18 +22,33 @@
-include_lib("eunit/include/eunit.hrl").
-include_lib("emqx/include/emqx.hrl").
-include_lib("stdlib/include/assert.hrl").
-include_lib("eldap/include/eldap.hrl").
-define(MYSQL_HOST, "ldap").
-define(MYSQL_RESOURCE_MOD, emqx_ldap).
-define(LDAP_HOST, "ldap").
-define(LDAP_RESOURCE_MOD, emqx_ldap).
all() ->
emqx_common_test_helpers:all(?MODULE).
[
{group, tcp},
{group, ssl}
].
groups() ->
[].
Cases = emqx_common_test_helpers:all(?MODULE),
[
{tcp, Cases},
{ssl, Cases}
].
init_per_group(Group, Config) ->
[{group, Group} | Config].
end_per_group(_, Config) ->
proplists:delete(group, Config).
init_per_suite(Config) ->
case emqx_common_test_helpers:is_tcp_server_available(?MYSQL_HOST, ?MYSQL_DEFAULT_PORT) of
Port = port(tcp),
case emqx_common_test_helpers:is_tcp_server_available(?LDAP_HOST, Port) of
true ->
ok = emqx_common_test_helpers:start_apps([emqx_conf]),
ok = emqx_connector_test_helpers:start_apps([emqx_resource]),
@ -58,22 +73,22 @@ end_per_testcase(_, _Config) ->
% %% Testcases
% %%------------------------------------------------------------------------------
t_lifecycle(_Config) ->
t_lifecycle(Config) ->
perform_lifecycle_check(
<<"emqx_ldap_SUITE">>,
ldap_config()
ldap_config(Config)
).
perform_lifecycle_check(ResourceId, InitialConfig) ->
{ok, #{config := CheckedConfig}} =
emqx_resource:check_config(?MYSQL_RESOURCE_MOD, InitialConfig),
emqx_resource:check_config(?LDAP_RESOURCE_MOD, InitialConfig),
{ok, #{
state := #{pool_name := PoolName} = State,
status := InitialStatus
}} = emqx_resource:create_local(
ResourceId,
?CONNECTOR_RESOURCE_GROUP,
?MYSQL_RESOURCE_MOD,
?LDAP_RESOURCE_MOD,
CheckedConfig,
#{}
),
@ -86,15 +101,22 @@ perform_lifecycle_check(ResourceId, InitialConfig) ->
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]]},
{ok, [#eldap_entry{attributes = [_, _ | _]}]},
emqx_resource:query(ResourceId, test_query_no_attr())
),
?assertMatch(
{ok, [#eldap_entry{attributes = [{"mqttAccountName", _}]}]},
emqx_resource:query(ResourceId, test_query_with_attr())
),
?assertMatch(
{ok, _},
emqx_resource:query(
ResourceId,
test_query_with_params_and_timeout()
test_query_with_attr_and_timeout()
)
),
?assertMatch({ok, []}, emqx_resource:query(ResourceId, test_query_not_exists())),
?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.
@ -116,13 +138,13 @@ perform_lifecycle_check(ResourceId, InitialConfig) ->
{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, _}, emqx_resource:query(ResourceId, test_query_no_attr())),
?assertMatch({ok, _}, emqx_resource:query(ResourceId, test_query_with_attr())),
?assertMatch(
{ok, _, [[1]]},
{ok, _},
emqx_resource:query(
ResourceId,
test_query_with_params_and_timeout()
test_query_with_attr_and_timeout()
)
),
% Stop and remove the resource in one go.
@ -134,32 +156,51 @@ perform_lifecycle_check(ResourceId, InitialConfig) ->
% %%------------------------------------------------------------------------------
% %% Helpers
% %%------------------------------------------------------------------------------
ldap_config() ->
ldap_config(Config) ->
RawConfig = list_to_binary(
io_lib:format(
""
"\n"
" auto_reconnect = true\n"
" database = mqtt\n"
" username= root\n"
" username= \"cn=root,dc=emqx,dc=io\"\n"
" password = public\n"
" pool_size = 8\n"
" server = \"~s:~b\"\n"
" "
" base_object=\"uid=${username},ou=testdevice,dc=emqx,dc=io\"\n"
" filter =\"(objectClass=mqttUser)\"\n"
" ~ts\n"
"",
[?MYSQL_HOST, ?MYSQL_DEFAULT_PORT]
[?LDAP_HOST, port(Config), ssl(Config)]
)
),
{ok, Config} = hocon:binary(RawConfig),
#{<<"config">> => Config}.
{ok, LDConfig} = hocon:binary(RawConfig),
#{<<"config">> => LDConfig}.
test_query_no_params() ->
{sql, <<"SELECT 1">>}.
test_query_no_attr() ->
{query, data()}.
test_query_with_params() ->
{sql, <<"SELECT ?">>, [1]}.
test_query_with_attr() ->
{query, data(), ["mqttAccountName"]}.
test_query_with_params_and_timeout() ->
{sql, <<"SELECT ?">>, [1], 1000}.
test_query_with_attr_and_timeout() ->
{query, data(), ["mqttAccountName"], 5000}.
test_query_not_exists() ->
{query, #{username => <<"not_exists">>}}.
data() ->
#{username => <<"mqttuser0001">>}.
port(tcp) -> 389;
port(ssl) -> 636;
port(Config) -> port(proplists:get_value(group, Config)).
ssl(Config) ->
case proplists:get_value(group, Config) of
tcp ->
"ssl.enable=false";
ssl ->
"ssl.enable=true\n"
"ssl.cacertfile=\"etc/openldap/cacert.pem\""
end.

View File

@ -0,0 +1,245 @@
% %%--------------------------------------------------------------------
% %% 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_filter_SUITE).
-compile(nowarn_export_all).
-compile(export_all).
-include_lib("eunit/include/eunit.hrl").
-include_lib("stdlib/include/assert.hrl").
-import(eldap, [
'and'/1,
'or'/1,
'not'/1,
equalityMatch/2,
substrings/2,
present/1,
greaterOrEqual/2,
lessOrEqual/2,
approxMatch/2,
extensibleMatch/2
]).
all() ->
emqx_common_test_helpers:all(?MODULE).
groups() ->
[].
init_per_suite(Config) ->
Config.
end_per_suite(_Config) ->
_ = application:stop(emqx_connector).
% %%------------------------------------------------------------------------------
% %% Testcases
% %%------------------------------------------------------------------------------
t_and(_Config) ->
?assertEqual('and'([equalityMatch("a", "1")]), parse("(&(a=1))")),
?assertEqual(
'and'([equalityMatch("a", "1"), (equalityMatch("b", "2"))]),
parse("(&(a=1)(b=2))")
),
?assertMatch({error, _}, scan_and_parse("(&)")).
t_or(_Config) ->
?assertEqual('or'([equalityMatch("a", "1")]), parse("(|(a=1))")),
?assertEqual(
'or'([equalityMatch("a", "1"), (equalityMatch("b", "2"))]),
parse("(|(a=1)(b=2))")
),
?assertMatch({error, _}, scan_and_parse("(|)")).
t_not(_Config) ->
?assertEqual('not'(equalityMatch("a", "1")), parse("(!(a=1))")),
?assertMatch({error, _}, scan_and_parse("(!)")),
?assertMatch({error, _}, scan_and_parse("(!(a=1)(b=1))")).
t_equalityMatch(_Config) ->
?assertEqual(equalityMatch("attr", "value"), parse("(attr=value)")),
?assertEqual(equalityMatch("attr", "value"), parse("(attr = value)")),
?assertMatch({error, _}, scan_and_parse("(attr=)")),
?assertMatch({error, _}, scan_and_parse("(=)")),
?assertMatch({error, _}, scan_and_parse("(=value)")).
t_substrings_initial(_Config) ->
?assertEqual(substrings("attr", [{initial, "initial"}]), parse("(attr=initial*)")),
?assertEqual(
substrings("attr", [{initial, "initial"}, {any, "a"}]),
parse("(attr=initial*a*)")
),
?assertEqual(
substrings("attr", [{initial, "initial"}, {any, "a"}, {any, "b"}]),
parse("(attr=initial*a*b*)")
).
t_substrings_final(_Config) ->
?assertEqual(substrings("attr", [{final, "final"}]), parse("(attr=*final)")),
?assertEqual(
substrings("attr", [{any, "a"}, {final, "final"}]),
parse("(attr=*a*final)")
),
?assertEqual(
substrings("attr", [{any, "a"}, {any, "b"}, {final, "final"}]),
parse("(attr=*a*b*final)")
).
t_substrings_initial_final(_Config) ->
?assertEqual(
substrings("attr", [{initial, "initial"}, {final, "final"}]),
parse("(attr=initial*final)")
),
?assertEqual(
substrings("attr", [{initial, "initial"}, {any, "a"}, {final, "final"}]),
parse("(attr=initial*a*final)")
),
?assertEqual(
substrings(
"attr",
[{initial, "initial"}, {any, "a"}, {any, "b"}, {final, "final"}]
),
parse("(attr=initial*a*b*final)")
).
t_substrings_only_any(_Config) ->
?assertEqual(present("attr"), parse("(attr=*)")),
?assertEqual(substrings("attr", [{any, "a"}]), parse("(attr=*a*)")),
?assertEqual(
substrings("attr", [{any, "a"}, {any, "b"}]),
parse("(attr=*a*b*)")
).
t_greaterOrEqual(_Config) ->
?assertEqual(greaterOrEqual("attr", "value"), parse("(attr>=value)")),
?assertEqual(greaterOrEqual("attr", "value"), parse("(attr >= value )")),
?assertMatch({error, _}, scan_and_parse("(attr>=)")),
?assertMatch({error, _}, scan_and_parse("(>=)")),
?assertMatch({error, _}, scan_and_parse("(>=value)")).
t_lessOrEqual(_Config) ->
?assertEqual(lessOrEqual("attr", "value"), parse("(attr<=value)")),
?assertEqual(lessOrEqual("attr", "value"), parse("( attr <= value )")),
?assertMatch({error, _}, scan_and_parse("(attr<=)")),
?assertMatch({error, _}, scan_and_parse("(<=)")),
?assertMatch({error, _}, scan_and_parse("(<=value)")).
t_present(_Config) ->
?assertEqual(present("attr"), parse("(attr=*)")),
?assertEqual(present("attr"), parse("( attr = * )")).
t_approxMatch(_Config) ->
?assertEqual(approxMatch("attr", "value"), parse("(attr~=value)")),
?assertEqual(approxMatch("attr", "value"), parse("( attr ~= value )")),
?assertMatch({error, _}, scan_and_parse("(attr~=)")),
?assertMatch({error, _}, scan_and_parse("(~=)")),
?assertMatch({error, _}, scan_and_parse("(~=value)")).
t_extensibleMatch_dn(_Config) ->
?assertEqual(
extensibleMatch("value", [{type, "attr"}, {dnAttributes, true}]), parse("(attr:dn:=value)")
),
?assertEqual(
extensibleMatch("value", [{type, "attr"}, {dnAttributes, true}]),
parse("( attr:dn := value )")
).
t_extensibleMatch_rule(_Config) ->
?assertEqual(
extensibleMatch("value", [{type, "attr"}, {matchingRule, "objectClass"}]),
parse("(attr:objectClass:=value)")
),
?assertEqual(
extensibleMatch("value", [{type, "attr"}, {matchingRule, "objectClass"}]),
parse("( attr:objectClass := value )")
).
t_extensibleMatch_dn_rule(_Config) ->
?assertEqual(
extensibleMatch(
"value",
[
{type, "attr"},
{dnAttributes, true},
{matchingRule, "objectClass"}
]
),
parse("(attr:dn:objectClass:=value)")
),
?assertEqual(
extensibleMatch(
"value",
[
{type, "attr"},
{dnAttributes, true},
{matchingRule, "objectClass"}
]
),
parse("( attr:dn:objectClass :=value)")
).
t_extensibleMatch_no_dn_rule(_Config) ->
?assertEqual(extensibleMatch("value", [{type, "attr"}]), parse("(attr:=value)")),
?assertEqual(extensibleMatch("value", [{type, "attr"}]), parse("( attr := value )")).
t_extensibleMatch_no_type_dn(_Config) ->
?assertEqual(
extensibleMatch("value", [{matchingRule, "objectClass"}]),
parse("(:objectClass:=value)")
),
?assertEqual(
extensibleMatch("value", [{matchingRule, "objectClass"}]),
parse("( :objectClass := value )")
).
t_extensibleMatch_no_type_no_dn(_Config) ->
?assertEqual(
extensibleMatch(
"value",
[{dnAttributes, true}, {matchingRule, "objectClass"}]
),
parse("(:dn:objectClass:=value)")
),
?assertEqual(
extensibleMatch(
"value",
[{dnAttributes, true}, {matchingRule, "objectClass"}]
),
parse("( :dn:objectClass :=value)")
).
t_extensibleMatch_error(_Config) ->
?assertMatch({error, _}, scan_and_parse("(:dn:=value)")),
?assertMatch({error, _}, scan_and_parse("(::=value)")),
?assertMatch({error, _}, scan_and_parse("(:=)")),
?assertMatch({error, _}, scan_and_parse("(attr:=)")).
t_error(_Config) ->
?assertMatch({error, _}, scan_and_parse("(attr=value")),
?assertMatch({error, _}, scan_and_parse("attr=value")),
?assertMatch({error, _}, scan_and_parse("(a=b)(c=d)")).
% %%------------------------------------------------------------------------------
% %% Helpers
% %%------------------------------------------------------------------------------
parse(Str) ->
{ok, Res} = scan_and_parse(Str),
Res.
scan_and_parse(Str) ->
emqx_ldap_filter_parser:scan_and_parse(Str).

View File

@ -225,6 +225,9 @@ for dep in ${CT_DEPS}; do
greptimedb)
FILES+=( '.ci/docker-compose-file/docker-compose-greptimedb.yaml' )
;;
ldap)
FILES+=( '.ci/docker-compose-file/docker-compose-ldap.yaml' )
;;
*)
echo "unknown_ct_dependency $dep"
exit 1