diff --git a/.github/workflows/build_packages.yaml b/.github/workflows/build_packages.yaml
index e4cd6852..6cbad4e7 100644
--- a/.github/workflows/build_packages.yaml
+++ b/.github/workflows/build_packages.yaml
@@ -16,13 +16,13 @@ jobs:
strategy:
matrix:
os:
+ - ubuntu20.04
- ubuntu18.04
- ubuntu16.04
- - ubuntu14.04
- debian10
- debian9
- - debian8
- opensuse
+ - centos8
- centos7
- centos6
@@ -30,14 +30,14 @@ jobs:
- uses: actions/checkout@v1
- name: build emqx packages
env:
- ERL_OTP: erl22.1
+ ERL_OTP: erl22.3
SYSTEM: ${{ matrix.os }}
run: |
docker run -i --name emqtt-$SYSTEM-build -v $(pwd):/emqtt emqx/build-env:$ERL_OTP-$SYSTEM /bin/bash -c "cd /emqtt && .github/workflows/script/build.sh"
cd _packages && for var in $(ls); do sudo bash -c "echo $(sha256sum $var | awk '{print $1}') > $var.sha256"; done && cd -
- uses: actions/upload-artifact@v1
with:
- name: packages-${{ matrix.os }}
+ name: packages
path: _packages/.
build-on-mac:
@@ -47,9 +47,9 @@ jobs:
- uses: actions/checkout@v1
- name: prepare
run: |
- /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
- brew install curl zip unzip gnu-sed erlang
- echo "/usr/local/bin:$PATH" >> ~/.bashrc
+ brew install curl zip unzip gnu-sed erlang@22
+ echo "/usr/local/opt/erlang@22/bin" >> $GITHUB_PATH
+ echo "/usr/local/bin" >> $GITHUB_PATH
- name: install rebar3
run: |
curl -Lo /usr/local/bin/rebar3 https://s3.amazonaws.com/rebar3/rebar3
@@ -73,63 +73,28 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v1
with:
- name: packages-ubuntu18.04
- path: _packages
- - uses: actions/download-artifact@v1
- with:
- name: packages-ubuntu16.04
- path: _packages
- - uses: actions/download-artifact@v1
- with:
- name: packages-ubuntu14.04
- path: _packages
- - uses: actions/download-artifact@v1
- with:
- name: packages-debian10
- path: _packages
- - uses: actions/download-artifact@v1
- with:
- name: packages-debian9
- path: _packages
- - uses: actions/download-artifact@v1
- with:
- name: packages-debian8
- path: _packages
- - uses: actions/download-artifact@v1
- with:
- name: packages-opensuse
- path: _packages
- - uses: actions/download-artifact@v1
- with:
- name: packages-centos7
- path: _packages
- - uses: actions/download-artifact@v1
- with:
- name: packages-centos6
- path: _packages
- - uses: actions/download-artifact@v1
- with:
- name: packages-mac
+ name: packages
path: _packages
+ - name: get packages
+ run: |
+ cd _packages && for var in $( ls |grep emqtt |grep -v sha256); do
+ echo "$(cat $var.sha256) $var" | sha256sum -c || exit 1
+ done
- name: set aws
+ if: github.event_name == 'release'
run: |
curl "https://d1vvhvl2y92vvt.cloudfront.net/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
- aws configure set aws_access_key_id ${{ secrets.AwsAccessKeyId }}
- aws configure set aws_secret_access_key ${{ secrets.AwsSecretAccessKey }}
+ aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws configure set aws_secret_access_key ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws configure set default.region us-west-2
- - name: get packages
- run: |
- cd _packages && for var in $( ls |grep emqtt |grep -v sha256); do
- echo "$(cat $var.sha256) $var" | sha256sum -c || exit 1
- done
- name: upload aws
if: github.event_name == 'release'
run: |
version=$(echo ${{ github.ref }} | sed -r "s .*/.*/(.*) \1 g")
- aws s3 cp --recursive ./_packages s3://packages.emqx.io/emqtt/$version
- aws cloudfront create-invalidation --distribution-id E3TYD0WSP4S14P --paths "/emqtt/$version/*"
+ aws s3 cp --recursive ./_packages s3://packages.emqx/emqtt/$version
+ aws cloudfront create-invalidation --distribution-id E170YEULGLT8XB --paths "/emqtt/$version/*"
- name: upload github
if: github.event_name == 'release'
run: |
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index e2a3233d..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-language: erlang
-
-sudo: false
-
-otp_release:
- - 21.3
- - 22.0
-
-script:
- - make
diff --git a/README.md b/README.md
index a0f05167..188710b2 100644
--- a/README.md
+++ b/README.md
@@ -369,7 +369,8 @@ option() = {name, atom()} |
{auto_ack, boolean()} |
{ack_timeout, pos_integer()} |
{force_ping, boolean()} |
- {properties, properties()}
+ {properties, properties()} |
+ {enhanced_auth, enhanced_auth()}
```
**client()**
@@ -470,6 +471,33 @@ pubopt() = {retain, boolean()} |
reason_code() = 0..16#FF
```
+**method()**
+
+```
+method() = binary()
+```
+
+**params()**
+
+```
+params() = any()
+```
+
+**enhanced_auth_state()**
+
+```
+enhanced_auth_state() = #{method => method(), params => params(), stage => initialized | atom(), latest_server_data => undefined | binary(), any() => any()}).
+```
+
+**enhanced_auth()**
+
+```
+enhanced_auth() = #{method => method(), params => params()} |
+ #{method => method(), params => params(),
+ function => fun((EnhancedAuthState :: enhanced_auth_state()) ->
+ {ok, NEnhancedAuthState :: enhanced_auth_state()} | {ok, NAuthData :: binary(), NEnhancedAuthState :: enhanced_auth_state()})}
+```
+
### Exports
**emqtt:start_link() -> {ok, Pid} | ignore | {error, Reason}**
@@ -596,6 +624,10 @@ If false (the default), if any other packet is sent during keep alive interval,
Properties of CONNECT packet.
+`{enhanced_auth, EnhancedAuth}`
+
+The data required to enhance authentication
+
**emqtt:connect(Client) -> {ok, Properties} | {error, Reason}**
**Types**
@@ -766,6 +798,18 @@ Send a `PUBREL` packet to the MQTT server. `PacketId`, `ReasonCode` and `Propert
Send a `PUBCOMP` packet to the MQTT server. `PacketId`, `ReasonCode` and `Properties` specify packet identifier, reason code and properties for `PUBCOMP` packet.
+**emqtt:reauthentication(Client) -> ok**
+
+**emqtt:reauthentication(Client, EnhancedAuth) -> ok**
+
+ **Types**
+
+ **Client = [client()](#client)**
+
+ **EnhancedAuth = [enhanced_auth](#enhanced_auth)**
+
+Send a `AUTH` packet to the MQTT server.
+
**emqtt:subscriptions(Client) -> Subscriptions**
**Types**
diff --git a/rebar.config b/rebar.config
index 7aa193b0..fb606601 100644
--- a/rebar.config
+++ b/rebar.config
@@ -1,7 +1,6 @@
{minimum_otp_vsn, "21.0"}.
{erl_opts, [debug_info,
- warn_export_all,
warn_unused_vars,
warn_shadow_vars,
warn_unused_import,
@@ -10,7 +9,8 @@
{deps, [{cowlib, "2.8.0"},
{gun, {git, "https://github.com/emqx/gun", {tag, "1.3.4"}}},
- {getopt, "1.0.1"}
+ {getopt, "1.0.1"},
+ {esasl, {git, "https://github.com/emqx/esasl", {tag, "master"}}}
]}.
{escript_name, emqtt_cli}.
@@ -23,8 +23,9 @@
[{test,
[{deps,
[{meck, "0.8.13"},
- {emqx, {git, "https://github.com/emqx/emqx", {branch, "master"}}},
- {emqx_ct_helpers, {git, "https://github.com/emqx/emqx-ct-helpers", {branch, "develop"}}}
+ {emqx, {git, "https://github.com/emqx/emqx", {tag, "v4.2.2"}}},
+ {emqx_sasl, {git, "https://github.com/emqx/emqx-sasl", {tag, "v4.2.2"}}},
+ {emqx_ct_helpers, {git, "https://github.com/emqx/emqx-ct-helpers", {tag, "1.2.2"}}}
]},
{erl_opts, [debug_info]}
]},
@@ -42,7 +43,8 @@
os_mon,
inets,
compiler,
- runtime_tools
+ runtime_tools,
+ esasl
]},
{overlay_vars,["vars.config"]},
{overlay, [
@@ -70,7 +72,8 @@
os_mon,
inets,
compiler,
- runtime_tools
+ runtime_tools,
+ esasl
]},
{overlay_vars,["vars-pkg.config"]},
{overlay, [
@@ -102,5 +105,3 @@
syntax_tools,
compiler,
crypto]}.
-
-
diff --git a/rebar3 b/rebar3
index 640804d1..fe721409 100755
Binary files a/rebar3 and b/rebar3 differ
diff --git a/src/emqtt.erl b/src/emqtt.erl
index 687b9007..fd33fd34 100644
--- a/src/emqtt.erl
+++ b/src/emqtt.erl
@@ -43,6 +43,8 @@
, publish/5
, unsubscribe/2
, unsubscribe/3
+ , reauthentication/1
+ , reauthentication/2
]).
%% Puback...
@@ -165,6 +167,22 @@
-type(client() :: pid() | atom()).
+-type(method() :: binary()).
+
+-type(params() :: any()).
+
+-type(enhanced_auth_state() :: #{method => method(),
+ params => params(),
+ stage => initialized | atom(),
+ latest_server_data => undefined | binary(),
+ any() => any()}).
+
+-type(enhanced_auth_function() :: fun((EnhancedAuthState :: enhanced_auth_state()) -> {ok, NEnhancedAuthState :: enhanced_auth_state()}
+ | {ok, NAuthData :: binary(), NEnhancedAuthState :: enhanced_auth_state()})).
+
+-type(enhanced_auth() :: #{method => method(), params => params()}
+ | #{method => method(), params => params(), function => enhanced_auth_function()}).
+
-opaque(mqtt_msg() :: #mqtt_msg{}).
-record(state, {
@@ -192,6 +210,8 @@
will_flag :: boolean(),
will_msg :: mqtt_msg(),
properties :: properties(),
+ enhanced_auth :: enhanced_auth(),
+ enhanced_auth_state :: enhanced_auth_state(),
pending_calls :: list(),
subscriptions :: map(),
max_inflight :: infinity | pos_integer(),
@@ -391,6 +411,12 @@ disconnect(Client, ReasonCode) ->
disconnect(Client, ReasonCode, Properties) ->
gen_statem:call(Client, {disconnect, ReasonCode, Properties}).
+reauthentication(Client) ->
+ reauthentication(Client, #{}).
+
+reauthentication(Client, EnhancedAuth) when is_map(EnhancedAuth) ->
+ gen_statem:call(Client, {reauthentication, EnhancedAuth}).
+
%%--------------------------------------------------------------------
%% For test cases
%%--------------------------------------------------------------------
@@ -479,13 +505,16 @@ init([Options]) ->
inflight = #{},
awaiting_rel = #{},
properties = #{},
+ enhanced_auth = #{},
+ enhanced_auth_state = #{},
auto_ack = true,
ack_timeout = ?DEFAULT_ACK_TIMEOUT,
retry_interval = ?DEFAULT_RETRY_INTERVAL,
connect_timeout = ?DEFAULT_CONNECT_TIMEOUT,
last_packet_id = 1
}),
- {ok, initialized, init_parse_state(State)}.
+ NState = init_enhanced_auth(State),
+ {ok, initialized, init_parse_state(NState)}.
random_client_id() ->
rand:seed(exsplus, erlang:timestamp()),
@@ -578,6 +607,8 @@ init([{force_ping, ForcePing} | Opts], State) when is_boolean(ForcePing) ->
init(Opts, State#state{force_ping = ForcePing});
init([{properties, Properties} | Opts], State = #state{properties = InitProps}) ->
init(Opts, State#state{properties = maps:merge(InitProps, Properties)});
+init([{enhanced_auth, EnhancedAuth} | Opts], State = #state{enhanced_auth = InitEnhancedAuth}) ->
+ init(Opts, State#state{enhanced_auth = maps:merge(InitEnhancedAuth, EnhancedAuth)});
init([{max_inflight, infinity} | Opts], State) ->
init(Opts, State#state{max_inflight = infinity,
inflight = #{}});
@@ -606,7 +637,27 @@ init_will_msg({retain, Retain}, WillMsg) when is_boolean(Retain) ->
init_will_msg({qos, QoS}, WillMsg) ->
WillMsg#mqtt_msg{qos = ?QOS_I(QoS)}.
-init_parse_state(State = #state{proto_ver = Ver, properties = Properties}) ->
+init_enhanced_auth(State = #state{proto_ver = ?MQTT_PROTO_V5,
+ properties = Properties,
+ enhanced_auth =#{
+ method := AuthMethod,
+ params := Parms
+ } = EnhancedAuth,
+ enhanced_auth_state = EnhancedAuthState}) ->
+ AuthFun = maps:get(function, EnhancedAuth, fun emqtt_sasl:check/1),
+ {ok, AuthData, NEnhancedAuthState} = erlang:apply(AuthFun, [maps:merge(EnhancedAuthState,
+ #{method => AuthMethod,
+ params => Parms,
+ stage => initialized,
+ latest_server_data => undefined})]),
+ State#state{properties = maps:merge(Properties, #{'Authentication-Method' => AuthMethod,'Authentication-Data' => AuthData}),
+ enhanced_auth = maps:merge(EnhancedAuth, #{function => AuthFun}),
+ enhanced_auth_state = NEnhancedAuthState};
+
+init_enhanced_auth(State) -> State.
+
+init_parse_state(State = #state{proto_ver = Ver,
+ properties = Properties}) ->
MaxSize = maps:get('Maximum-Packet-Size', Properties, ?MAX_PACKET_SIZE),
ParseState = emqtt_frame:initial_parse_state(
#{max_size => MaxSize, version => Ver}),
@@ -629,7 +680,7 @@ initialized({call, From}, {connect, ConnMod}, State = #state{sock_opts = SockOpt
case mqtt_connect(run_sock(State#state{conn_mod = ConnMod, socket = Sock})) of
{ok, NewState} ->
{next_state, waiting_for_connack,
- add_call(new_call(connect, From), NewState), [Timeout]};
+ add_call(new_call(connect, From), NewState), [Timeout]};
Error = {error, Reason} ->
{stop_and_reply, Reason, [{reply, From, Error}]}
end;
@@ -670,6 +721,54 @@ mqtt_connect(State = #state{clientid = ClientId,
username = Username,
password = Password}), State).
+waiting_for_connack(cast, _Packet = ?AUTH_PACKET(?RC_CONTINUE_AUTHENTICATION,
+ Properties = #{'Authentication-Method' := AuthMethod,
+ 'Authentication-Data' := AuthData}),
+ State = #state{proto_ver = ?MQTT_PROTO_V5,
+ properties = AllProps,
+ enhanced_auth = #{function := AuthFun},
+ enhanced_auth_state = EnhancedAuthState}) ->
+ case erlang:apply(AuthFun, [maps:merge(EnhancedAuthState, #{latest_server_data => AuthData})]) of
+ {ok, NAuthData, NEnhancedAuthState} ->
+ NPacket = ?AUTH_PACKET(?RC_CONTINUE_AUTHENTICATION, #{'Authentication-Method' => AuthMethod,
+ 'Authentication-Data' => NAuthData}),
+
+ case send(NPacket, State) of
+ {ok, NState} ->
+ {keep_state, NState#state{properties = maps:merge(AllProps, maps:merge(Properties, #{'Authentication-Data' => NAuthData})),
+ enhanced_auth_state = NEnhancedAuthState}};
+ {error, Reason} ->
+ {stop, Reason}
+ end;
+ Reason -> {stop, Reason}
+ end;
+
+waiting_for_connack(cast, _Packet = ?CONNACK_PACKET(?RC_SUCCESS,
+ SessPresent,
+ Properties = #{'Authentication-Method' := _AuthMethod,
+ 'Authentication-Data' := AuthData}),
+ State = #state{properties = AllProps,
+ clientid = ClientId,
+ enhanced_auth = #{function := AuthFun},
+ enhanced_auth_state = EnhancedAuthState}) ->
+ case take_call(connect, State) of
+ {value, #call{from = From}, State1} ->
+ case erlang:apply(AuthFun, [maps:merge(EnhancedAuthState, #{latest_server_data => AuthData})]) of
+ {ok, NEnhancedAuthState} ->
+ AllProps1 = maps:merge(AllProps, Properties),
+ Reply = {ok, Properties},
+ State2 = State1#state{clientid = assign_id(ClientId, AllProps1),
+ properties = AllProps1,
+ session_present = SessPresent,
+ enhanced_auth_state = NEnhancedAuthState},
+ {next_state, connected, ensure_keepalive_timer(State2),
+ [{reply, From, Reply}]};
+ _ -> {stop, bad_connack}
+ end;
+ false ->
+ {stop, bad_connack}
+ end;
+
waiting_for_connack(cast, ?CONNACK_PACKET(?RC_SUCCESS,
SessPresent,
Properties),
@@ -806,6 +905,25 @@ connected({call, From}, {disconnect, ReasonCode, Properties}, State) ->
{stop_and_reply, Reason, [{reply, From, Error}]}
end;
+connected({call, From}, {reauthentication, EnhancedAuth}, State = #state{proto_ver = ?MQTT_PROTO_V5,
+ properties = #{'Authentication-Method' := AuthMethod},
+ enhanced_auth = OldEnhancedAuth,
+ enhanced_auth_state = EnhancedAuthState}) ->
+ NEnhancedAuth = #{method := AuthMethod, params := Parms, function := AuthFun} = maps:merge(OldEnhancedAuth, EnhancedAuth),
+
+ case erlang:apply(AuthFun, [maps:merge(EnhancedAuthState, #{params => Parms})]) of
+ {ok, AuthData, NEnhancedAuthState} ->
+ case send(?AUTH_PACKET(?RC_RE_AUTHENTICATE, #{'Authentication-Method' => AuthMethod,
+ 'Authentication-Data' => AuthData}), State) of
+ {ok, NewState} ->
+ {keep_state, NewState#state{enhanced_auth = NEnhancedAuth, enhanced_auth_state = NEnhancedAuthState},[{reply, From, ok}]};
+ Error = {error, Reason} ->
+ {stop_and_reply, Reason, [{reply, From, Error}]}
+ end;
+ Error = {error, Reason} ->
+ {stop_and_reply, Reason, [{reply, From, Error}]}
+ end;
+
connected(cast, {puback, PacketId, ReasonCode, Properties}, State) ->
send_puback(?PUBACK_PACKET(PacketId, ReasonCode, Properties), State);
@@ -903,6 +1021,41 @@ connected(cast, ?PACKET(?PINGRESP), State) ->
connected(cast, ?DISCONNECT_PACKET(ReasonCode, Properties), State) ->
{stop, {disconnected, ReasonCode, Properties}, State};
+connected(cast, ?AUTH_PACKET(?RC_CONTINUE_AUTHENTICATION,
+ Properties = #{'Authentication-Method' := AuthMethod,
+ 'Authentication-Data' := AuthData}),
+ State = #state{proto_ver = ?MQTT_PROTO_V5,
+ properties = AllProps,
+ enhanced_auth = #{function := AuthFun},
+ enhanced_auth_state = EnhancedAuthState}) ->
+ case erlang:apply(AuthFun, [maps:merge(EnhancedAuthState, #{latest_server_data => AuthData})]) of
+ {ok, NAuthData, NEnhancedAuthState} ->
+ NPacket = ?AUTH_PACKET(?RC_CONTINUE_AUTHENTICATION, #{'Authentication-Method' => AuthMethod,
+ 'Authentication-Data' => NAuthData}),
+ case send(NPacket, State) of
+ {ok, NState} ->
+ {keep_state, NState#state{properties = maps:merge(AllProps, maps:merge(Properties, #{'Authentication-Method' => NAuthData})),
+ enhanced_auth_state = NEnhancedAuthState}};
+ {error, Reason} ->
+ {stop, Reason}
+ end;
+ Reason -> {stop, Reason}
+ end;
+
+connected(cast, ?AUTH_PACKET(?RC_SUCCESS,
+ Properties = #{'Authentication-Method' := _AuthMethod,
+ 'Authentication-Data' := AuthData}),
+ State = #state{proto_ver = ?MQTT_PROTO_V5,
+ properties = AllProps,
+ enhanced_auth = #{function := AuthFun},
+ enhanced_auth_state = EnhancedAuthState}) ->
+ case erlang:apply(AuthFun, [maps:merge(EnhancedAuthState, #{latest_server_data => AuthData})]) of
+ {ok, NEnhancedAuthState} ->
+ {keep_state, State#state{properties = maps:merge(AllProps, Properties),
+ enhanced_auth_state = NEnhancedAuthState}};
+ Reason -> {stop, Reason}
+ end;
+
connected(info, {timeout, _TRef, keepalive}, State = #state{force_ping = true}) ->
case send(?PACKET(?PINGREQ), State) of
{ok, NewState} ->
diff --git a/src/emqtt_sasl.erl b/src/emqtt_sasl.erl
new file mode 100644
index 00000000..ed635584
--- /dev/null
+++ b/src/emqtt_sasl.erl
@@ -0,0 +1,34 @@
+%%%-------------------------------------------------------------------
+%% @doc sasl public API
+%% @end
+%%%-------------------------------------------------------------------
+
+-module(emqtt_sasl).
+
+-export([ check/1
+ , supported/0]).
+
+check(EnhancedAuthState = #{method := <<"SCRAM-SHA-1">>,
+ params := Params,
+ stage := initialized}) ->
+ Data = esasl:apply(<<"SCRAM-SHA-1">>, Params),
+ AuthContext = maps:merge(Params, #{client_first => Data}),
+ {ok, Data, maps:merge(EnhancedAuthState, #{stage => continue,
+ context => AuthContext})};
+
+check(EnhancedAuthState = #{method := <<"SCRAM-SHA-1">>,
+ stage := continue,
+ latest_server_data := ServerAuthData,
+ context := AuthContext}) ->
+ case esasl:check_server_data(<<"SCRAM-SHA-1">>, ServerAuthData, AuthContext) of
+ {continue, Data, NAuthContext} ->
+ {ok, Data, maps:merge(EnhancedAuthState, #{stage => continue, context => NAuthContext})};
+ {ok, <<>>, _} ->
+ {ok, maps:merge(EnhancedAuthState, #{stage => initialized, context => #{}})}
+ end;
+
+check(_EnhancedAuthState) ->
+ {error, authentication_failed}.
+
+supported() ->
+ [<<"SCRAM-SHA-1">>].
diff --git a/test/emqtt_SUITE.erl b/test/emqtt_SUITE.erl
index 2029e154..4fb0137f 100644
--- a/test/emqtt_SUITE.erl
+++ b/test/emqtt_SUITE.erl
@@ -68,10 +68,11 @@ groups() ->
dollar_topics_test]},
{mqttv5, [non_parallel_tests],
[basic_test_v5,
- retain_as_publish_test]}].
+ retain_as_publish_test,
+ enhanced_auth]}].
init_per_suite(Config) ->
- emqx_ct_helpers:start_apps([]),
+ emqx_ct_helpers:start_apps([emqx_sasl]),
Config.
end_per_suite(_Config) ->
@@ -327,7 +328,7 @@ anonymous_test(_Config) ->
process_flag(trap_exit, true),
{ok, C1} = emqtt:start_link(),
{_,{unauthorized_client,_}} = emqtt:connect(C1),
- receive {'EXIT', _, _} -> ok
+ receive {'EXIT',_, _} -> ok
after 500 -> error("allow_anonymous")
end,
process_flag(trap_exit, false),
@@ -335,7 +336,10 @@ anonymous_test(_Config) ->
application:set_env(emqx, allow_anonymous, true),
{ok, C2} = emqtt:start_link([{username, <<"test">>}, {password, <<"password">>}]),
{ok, _} = emqtt:connect(C2),
- ok = emqtt:disconnect(C2).
+ ok = emqtt:disconnect(C2),
+
+ [{_, #{clientinfo := #{username := Username}}, _}] = ets:tab2list(emqx_channel_info),
+ ?assertEqual(<<"test">>, Username).
retry_interval_test(_Config) ->
{ok, Pub} = emqtt:start_link([{clean_start, true}, {retry_interval, 1}]),
@@ -514,3 +518,41 @@ retain_as_publish_test(_) ->
ok = emqtt:disconnect(Pub),
clean_retained(Topic).
+
+enhanced_auth(_) ->
+ process_flag(trap_exit, true),
+
+ Username = <<"username">>,
+ Password = <<"password">>,
+ Salt = <<"emqx">>,
+ AuthMethod = <<"SCRAM-SHA-1">>,
+ ok = emqx_sasl_scram:add(Username, Password, Salt),
+
+ {error, _} = emqtt:start_link([{clean_start, true},
+ {proto_ver, v5},
+ {enhanced_auth, #{method => AuthMethod,
+ params => #{},
+ function => fun (_State) -> {error, authentication_failed} end}},
+ {connect_timeout, 6000}]),
+
+
+ {ok, Client1} = emqtt:start_link([{clean_start, true},
+ {proto_ver, v5},
+ {enhanced_auth, #{method => AuthMethod,
+ params => #{username => Username,
+ password => Password,
+ salt => Salt}}},
+ {connect_timeout, 6000}]),
+ {ok, _} = emqtt:connect(Client1),
+
+ timer:sleep(200),
+ ok = emqtt:reauthentication(Client1),
+
+ timer:sleep(200),
+ ErrorFun = fun (_State) -> {error, authentication_failed} end,
+ {error,authentication_failed} = emqtt:reauthentication(Client1, #{params => #{username => Username,
+ password => Password,
+ salt => Salt},
+ function => ErrorFun}),
+
+ process_flag(trap_exit, false).