feat(ldap): set test env and add test suites
This commit is contained in:
parent
8c9b136d15
commit
f98f97f37e
|
@ -6,11 +6,11 @@ services:
|
||||||
build:
|
build:
|
||||||
context: ../..
|
context: ../..
|
||||||
dockerfile: .ci/docker-compose-file/openldap/Dockerfile
|
dockerfile: .ci/docker-compose-file/openldap/Dockerfile
|
||||||
args:
|
args:
|
||||||
LDAP_TAG: ${LDAP_TAG}
|
LDAP_TAG: ${LDAP_TAG}
|
||||||
image: openldap
|
image: openldap
|
||||||
ports:
|
#ports:
|
||||||
- 389:389
|
# - 389:389
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
- emqx_bridge
|
- emqx_bridge
|
|
@ -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 apt-get update && apt-get install -y groff groff-base
|
||||||
RUN wget ftp://ftp.openldap.org/pub/OpenLDAP/openldap-release/openldap-${LDAP_TAG}.tgz \
|
RUN wget https://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-${LDAP_TAG}.tgz \
|
||||||
&& gunzip -c openldap-${LDAP_TAG}.tgz | tar xvfB - \
|
&& tar xvzf openldap-${LDAP_TAG}.tgz \
|
||||||
&& cd openldap-${LDAP_TAG} \
|
&& cd openldap-${LDAP_TAG} \
|
||||||
&& ./configure && make depend && make && make install \
|
&& ./configure && make depend && make && make install \
|
||||||
&& cd .. && rm -rf openldap-${LDAP_TAG}
|
&& cd .. && rm -rf openldap-${LDAP_TAG}
|
||||||
|
|
||||||
COPY .ci/docker-compose-file/openldap/slapd.conf /usr/local/etc/openldap/slapd.conf
|
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_ldap/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_ldap/test/data/emqx.schema /usr/local/etc/openldap/schema/emqx.schema
|
||||||
COPY apps/emqx_authn/test/data/certs/*.pem /usr/local/etc/openldap/
|
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 \
|
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
|
&& slapadd -l /usr/local/etc/openldap/schema/emqx.io.ldif -f /usr/local/etc/openldap/slapd.conf
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
|
ldap
|
||||||
|
|
|
@ -58,7 +58,8 @@ fields(config) ->
|
||||||
desc => ?DESC(base_object),
|
desc => ?DESC(base_object),
|
||||||
required => true
|
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}
|
{auto_reconnect, fun ?ECS:auto_reconnect/1}
|
||||||
] ++ emqx_connector_schema_lib:ssl_fields().
|
] ++ emqx_connector_schema_lib:ssl_fields().
|
||||||
|
|
||||||
|
@ -143,10 +144,10 @@ do_get_status(Conn) ->
|
||||||
%% ===================================================================
|
%% ===================================================================
|
||||||
|
|
||||||
connect(Options) ->
|
connect(Options) ->
|
||||||
#{host := Host, username := Username, password := Password} =
|
#{hostname := Host, username := Username, password := Password} =
|
||||||
Conf = proplists:get_value(options, Options),
|
Conf = proplists:get_value(options, Options),
|
||||||
OpenOpts = maps:to_list(maps:with([port, sslopts], Conf)),
|
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 ->
|
{ok, Handle} = Ret ->
|
||||||
case eldap:simple_bind(Handle, Username, Password) of
|
case eldap:simple_bind(Handle, Username, Password) of
|
||||||
ok -> Ret;
|
ok -> Ret;
|
||||||
|
@ -167,7 +168,7 @@ on_query(
|
||||||
[] ->
|
[] ->
|
||||||
do_ldap_query(InstId, [{base, Base} | SearchOptions], State);
|
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
|
case emqx_ldap_filter_parser:scan_and_parse(FilterBin) of
|
||||||
{ok, Filter} ->
|
{ok, Filter} ->
|
||||||
do_ldap_query(
|
do_ldap_query(
|
||||||
|
@ -195,7 +196,7 @@ do_ldap_query(
|
||||||
case
|
case
|
||||||
ecpool:pick_and_do(
|
ecpool:pick_and_do(
|
||||||
PoolName,
|
PoolName,
|
||||||
{eldap, search, SearchOptions},
|
{eldap, search, [SearchOptions]},
|
||||||
handover
|
handover
|
||||||
)
|
)
|
||||||
of
|
of
|
||||||
|
@ -204,14 +205,9 @@ do_ldap_query(
|
||||||
ldap_connector_query_return,
|
ldap_connector_query_return,
|
||||||
#{result => Result}
|
#{result => Result}
|
||||||
),
|
),
|
||||||
{ok,
|
{ok, Result#eldap_search_result.entries};
|
||||||
case Result#eldap_search_result.entries of
|
{error, noSuchObject} ->
|
||||||
[First | _] ->
|
{ok, []};
|
||||||
%% TODO Support multi entries?
|
|
||||||
First;
|
|
||||||
_ ->
|
|
||||||
undefined
|
|
||||||
end};
|
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?SLOG(
|
?SLOG(
|
||||||
error,
|
error,
|
||||||
|
@ -235,4 +231,6 @@ prepare_template(Config, State) ->
|
||||||
do_prepare_template([{base_object, V} | T], State) ->
|
do_prepare_template([{base_object, V} | T], State) ->
|
||||||
do_prepare_template(T, State#{base_tokens => emqx_placeholder:preproc_tmpl(V)});
|
do_prepare_template(T, State#{base_tokens => emqx_placeholder:preproc_tmpl(V)});
|
||||||
do_prepare_template([{filter, V} | T], State) ->
|
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.
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
Definitions.
|
Definitions.
|
||||||
|
|
||||||
Control = [()&|!=~><:*]
|
Control = [()&|!=~><:*]
|
||||||
NonControl = [^()&|!=~><:*]
|
|
||||||
String = {NonControl}*
|
|
||||||
White = [\s\t\n\r]+
|
White = [\s\t\n\r]+
|
||||||
|
NonString = [^()&|!=~><:*\s\t\n\r]
|
||||||
|
String = {NonString}+
|
||||||
|
|
||||||
Rules.
|
Rules.
|
||||||
|
|
||||||
|
|
|
@ -16,12 +16,12 @@ filtercomp ->
|
||||||
filtercomp ->
|
filtercomp ->
|
||||||
'or' filterlist: 'or'('$2').
|
'or' filterlist: 'or'('$2').
|
||||||
filtercomp ->
|
filtercomp ->
|
||||||
'not' filterlist: 'not'('$2').
|
'not' filter: 'not'('$2').
|
||||||
filtercomp ->
|
filtercomp ->
|
||||||
item: '$1'.
|
item: '$1'.
|
||||||
|
|
||||||
filterlist ->
|
filterlist ->
|
||||||
filter: '$1'.
|
filter: ['$1'].
|
||||||
filterlist ->
|
filterlist ->
|
||||||
filter filterlist: ['$1' | '$2'].
|
filter filterlist: ['$1' | '$2'].
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ extensible ->
|
||||||
extensible ->
|
extensible ->
|
||||||
type matchingrule colon equal value: extensible('$5', ['$1', '$2']).
|
type matchingrule colon equal value: extensible('$5', ['$1', '$2']).
|
||||||
extensible ->
|
extensible ->
|
||||||
type colon equal value: extensible('$4', []).
|
type colon equal value: extensible('$4', ['$1']).
|
||||||
|
|
||||||
extensible ->
|
extensible ->
|
||||||
dnattrs matchingrule colon equal value: extensible('$5', ['$1', '$2']).
|
dnattrs matchingrule colon equal value: extensible('$5', ['$1', '$2']).
|
||||||
|
@ -125,7 +125,7 @@ substrings(Attr, List) ->
|
||||||
eldap:substrings(Attr, flatten(List)).
|
eldap:substrings(Attr, flatten(List)).
|
||||||
|
|
||||||
'any'(List, Item) ->
|
'any'(List, Item) ->
|
||||||
[{any, Item} | List].
|
[List, {any, Item}].
|
||||||
|
|
||||||
extensible(Value, Opts) -> eldap:extensibleMatch(Value, Opts).
|
extensible(Value, Opts) -> eldap:extensibleMatch(Value, Opts).
|
||||||
|
|
||||||
|
|
|
@ -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=
|
||||||
|
|
|
@ -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 ) )
|
|
@ -22,18 +22,33 @@
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
-include_lib("emqx/include/emqx.hrl").
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
-include_lib("stdlib/include/assert.hrl").
|
-include_lib("stdlib/include/assert.hrl").
|
||||||
|
-include_lib("eldap/include/eldap.hrl").
|
||||||
|
|
||||||
-define(MYSQL_HOST, "ldap").
|
-define(LDAP_HOST, "ldap").
|
||||||
-define(MYSQL_RESOURCE_MOD, emqx_ldap).
|
-define(LDAP_RESOURCE_MOD, emqx_ldap).
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
emqx_common_test_helpers:all(?MODULE).
|
[
|
||||||
|
{group, tcp},
|
||||||
|
{group, ssl}
|
||||||
|
].
|
||||||
|
|
||||||
groups() ->
|
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) ->
|
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 ->
|
true ->
|
||||||
ok = emqx_common_test_helpers:start_apps([emqx_conf]),
|
ok = emqx_common_test_helpers:start_apps([emqx_conf]),
|
||||||
ok = emqx_connector_test_helpers:start_apps([emqx_resource]),
|
ok = emqx_connector_test_helpers:start_apps([emqx_resource]),
|
||||||
|
@ -58,22 +73,22 @@ end_per_testcase(_, _Config) ->
|
||||||
% %% Testcases
|
% %% Testcases
|
||||||
% %%------------------------------------------------------------------------------
|
% %%------------------------------------------------------------------------------
|
||||||
|
|
||||||
t_lifecycle(_Config) ->
|
t_lifecycle(Config) ->
|
||||||
perform_lifecycle_check(
|
perform_lifecycle_check(
|
||||||
<<"emqx_ldap_SUITE">>,
|
<<"emqx_ldap_SUITE">>,
|
||||||
ldap_config()
|
ldap_config(Config)
|
||||||
).
|
).
|
||||||
|
|
||||||
perform_lifecycle_check(ResourceId, InitialConfig) ->
|
perform_lifecycle_check(ResourceId, InitialConfig) ->
|
||||||
{ok, #{config := CheckedConfig}} =
|
{ok, #{config := CheckedConfig}} =
|
||||||
emqx_resource:check_config(?MYSQL_RESOURCE_MOD, InitialConfig),
|
emqx_resource:check_config(?LDAP_RESOURCE_MOD, InitialConfig),
|
||||||
{ok, #{
|
{ok, #{
|
||||||
state := #{pool_name := PoolName} = State,
|
state := #{pool_name := PoolName} = State,
|
||||||
status := InitialStatus
|
status := InitialStatus
|
||||||
}} = emqx_resource:create_local(
|
}} = emqx_resource:create_local(
|
||||||
ResourceId,
|
ResourceId,
|
||||||
?CONNECTOR_RESOURCE_GROUP,
|
?CONNECTOR_RESOURCE_GROUP,
|
||||||
?MYSQL_RESOURCE_MOD,
|
?LDAP_RESOURCE_MOD,
|
||||||
CheckedConfig,
|
CheckedConfig,
|
||||||
#{}
|
#{}
|
||||||
),
|
),
|
||||||
|
@ -86,15 +101,22 @@ perform_lifecycle_check(ResourceId, InitialConfig) ->
|
||||||
emqx_resource:get_instance(ResourceId),
|
emqx_resource:get_instance(ResourceId),
|
||||||
?assertEqual({ok, connected}, emqx_resource:health_check(ResourceId)),
|
?assertEqual({ok, connected}, emqx_resource:health_check(ResourceId)),
|
||||||
% % Perform query as further check that the resource is working as expected
|
% % 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(
|
?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(
|
emqx_resource:query(
|
||||||
ResourceId,
|
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)),
|
?assertEqual(ok, emqx_resource:stop(ResourceId)),
|
||||||
% Resource will be listed still, but state will be changed and healthcheck will fail
|
% Resource will be listed still, but state will be changed and healthcheck will fail
|
||||||
% as the worker no longer exists.
|
% as the worker no longer exists.
|
||||||
|
@ -116,13 +138,13 @@ perform_lifecycle_check(ResourceId, InitialConfig) ->
|
||||||
{ok, ?CONNECTOR_RESOURCE_GROUP, #{status := InitialStatus}} =
|
{ok, ?CONNECTOR_RESOURCE_GROUP, #{status := InitialStatus}} =
|
||||||
emqx_resource:get_instance(ResourceId),
|
emqx_resource:get_instance(ResourceId),
|
||||||
?assertEqual({ok, connected}, emqx_resource:health_check(ResourceId)),
|
?assertEqual({ok, connected}, emqx_resource:health_check(ResourceId)),
|
||||||
?assertMatch({ok, _, [[1]]}, emqx_resource:query(ResourceId, test_query_no_params())),
|
?assertMatch({ok, _}, emqx_resource:query(ResourceId, test_query_no_attr())),
|
||||||
?assertMatch({ok, _, [[1]]}, emqx_resource:query(ResourceId, test_query_with_params())),
|
?assertMatch({ok, _}, emqx_resource:query(ResourceId, test_query_with_attr())),
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
{ok, _, [[1]]},
|
{ok, _},
|
||||||
emqx_resource:query(
|
emqx_resource:query(
|
||||||
ResourceId,
|
ResourceId,
|
||||||
test_query_with_params_and_timeout()
|
test_query_with_attr_and_timeout()
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
% Stop and remove the resource in one go.
|
% Stop and remove the resource in one go.
|
||||||
|
@ -134,32 +156,51 @@ perform_lifecycle_check(ResourceId, InitialConfig) ->
|
||||||
% %%------------------------------------------------------------------------------
|
% %%------------------------------------------------------------------------------
|
||||||
% %% Helpers
|
% %% Helpers
|
||||||
% %%------------------------------------------------------------------------------
|
% %%------------------------------------------------------------------------------
|
||||||
|
ldap_config(Config) ->
|
||||||
ldap_config() ->
|
|
||||||
RawConfig = list_to_binary(
|
RawConfig = list_to_binary(
|
||||||
io_lib:format(
|
io_lib:format(
|
||||||
""
|
""
|
||||||
"\n"
|
"\n"
|
||||||
" auto_reconnect = true\n"
|
" auto_reconnect = true\n"
|
||||||
" database = mqtt\n"
|
" username= \"cn=root,dc=emqx,dc=io\"\n"
|
||||||
" username= root\n"
|
|
||||||
" password = public\n"
|
" password = public\n"
|
||||||
" pool_size = 8\n"
|
" pool_size = 8\n"
|
||||||
" server = \"~s:~b\"\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),
|
{ok, LDConfig} = hocon:binary(RawConfig),
|
||||||
#{<<"config">> => Config}.
|
#{<<"config">> => LDConfig}.
|
||||||
|
|
||||||
test_query_no_params() ->
|
test_query_no_attr() ->
|
||||||
{sql, <<"SELECT 1">>}.
|
{query, data()}.
|
||||||
|
|
||||||
test_query_with_params() ->
|
test_query_with_attr() ->
|
||||||
{sql, <<"SELECT ?">>, [1]}.
|
{query, data(), ["mqttAccountName"]}.
|
||||||
|
|
||||||
test_query_with_params_and_timeout() ->
|
test_query_with_attr_and_timeout() ->
|
||||||
{sql, <<"SELECT ?">>, [1], 1000}.
|
{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.
|
||||||
|
|
|
@ -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).
|
|
@ -225,6 +225,9 @@ for dep in ${CT_DEPS}; do
|
||||||
greptimedb)
|
greptimedb)
|
||||||
FILES+=( '.ci/docker-compose-file/docker-compose-greptimedb.yaml' )
|
FILES+=( '.ci/docker-compose-file/docker-compose-greptimedb.yaml' )
|
||||||
;;
|
;;
|
||||||
|
ldap)
|
||||||
|
FILES+=( '.ci/docker-compose-file/docker-compose-ldap.yaml' )
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "unknown_ct_dependency $dep"
|
echo "unknown_ct_dependency $dep"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
Loading…
Reference in New Issue