Kaynağa Gözat

refactor(emqx_auth_mnesia): use tag e4.2.2

Removed emqx_auth_clientid and emqx_auth_username
because the new version emqx_auth_mnesia has all the features included
Zaiming Shi 5 yıl önce
ebeveyn
işleme
e236196fa6
35 değiştirilmiş dosya ile 1040 ekleme ve 1994 silme
  1. 0 117
      apps/emqx_auth_clientid/README.md
  2. 0 3
      apps/emqx_auth_clientid/TODO
  3. 0 13
      apps/emqx_auth_clientid/include/emqx_auth_clientid.hrl
  4. 0 26
      apps/emqx_auth_clientid/priv/emqx_auth_clientid.schema
  5. 0 1
      apps/emqx_auth_clientid/rebar.config
  6. 0 14
      apps/emqx_auth_clientid/src/emqx_auth_clientid.app.src
  7. 0 170
      apps/emqx_auth_clientid/src/emqx_auth_clientid.erl
  8. 0 127
      apps/emqx_auth_clientid/src/emqx_auth_clientid_api.erl
  9. 0 53
      apps/emqx_auth_clientid/src/emqx_auth_clientid_app.erl
  10. 0 191
      apps/emqx_auth_clientid/test/emqx_auth_clientid_SUITE.erl
  11. 30 0
      apps/emqx_auth_mnesia/etc/emqx_auth_mnesia.conf
  12. 13 10
      apps/emqx_auth_mnesia/include/emqx_auth_mnesia.hrl
  13. 23 15
      apps/emqx_auth_mnesia/priv/emqx_auth_mnesia.schema
  14. 29 1
      apps/emqx_auth_mnesia/rebar.config
  15. 53 33
      apps/emqx_auth_mnesia/src/emqx_acl_mnesia.erl
  16. 137 48
      apps/emqx_auth_mnesia/src/emqx_acl_mnesia_api.erl
  17. 198 0
      apps/emqx_auth_mnesia/src/emqx_acl_mnesia_cli.erl
  18. 2 2
      apps/emqx_auth_mnesia/src/emqx_auth_mnesia.app.src
  19. 32 24
      apps/emqx_auth_mnesia/src/emqx_auth_mnesia.erl
  20. 200 82
      apps/emqx_auth_mnesia/src/emqx_auth_mnesia_api.erl
  21. 13 13
      apps/emqx_auth_mnesia/src/emqx_auth_mnesia_app.erl
  22. 79 91
      apps/emqx_auth_mnesia/src/emqx_auth_mnesia_cli.erl
  23. 97 161
      apps/emqx_auth_mnesia/test/emqx_acl_mnesia_SUITE.erl
  24. 134 109
      apps/emqx_auth_mnesia/test/emqx_auth_mnesia_SUITE.erl
  25. 0 111
      apps/emqx_auth_username/README.md
  26. 0 1
      apps/emqx_auth_username/TODO
  27. 0 14
      apps/emqx_auth_username/include/emqx_auth_username.hrl
  28. 0 26
      apps/emqx_auth_username/priv/emqx_auth_username.schema
  29. 0 1
      apps/emqx_auth_username/rebar.config
  30. 0 14
      apps/emqx_auth_username/src/emqx_auth_username.app.src
  31. 0 173
      apps/emqx_auth_username/src/emqx_auth_username.erl
  32. 0 123
      apps/emqx_auth_username/src/emqx_auth_username_api.erl
  33. 0 49
      apps/emqx_auth_username/src/emqx_auth_username_app.erl
  34. 0 176
      apps/emqx_auth_username/test/emqx_auth_username_SUITE.erl
  35. 0 2
      rebar.config

+ 0 - 117
apps/emqx_auth_clientid/README.md

@@ -1,117 +0,0 @@
-emqx_auth_clientid
-==================
-
-Authentication with ClientId and Password
-
-Build
------
-
-```
-make && make tests
-```
-
-Configuration
--------------
-
-etc/emqx_auth_clientid.conf:
-
-```
-## Password hash.
-##
-## Value: plain | md5 | sha | sha256
-auth.client.password_hash = sha256
-```
-
-[REST API](https://developer.emqx.io/docs/emq/v3/en/rest.html)
-------------
-
-List all clientids:
-
-```
-# Request
-GET api/v4/auth_clientid
-
-# Response
-{
-    "code": 0,
-    "data": ["clientid1"]
-}
-```
-
-Add clientid:
-
-```
-# Request
-POST api/v4/auth_clientid
-{
-    "clientid": "a_client_id",
-    "password": "password"
-}
-
-# Response
-{
-    "code": 0
-}
-```
-
-Update password for a clientid:
-
-```
-# Request
-PUT api/v4/auth_clientid/$CLIENTID
-
-{
-    "password": "password"
-}
-
-# Response
-{
-    "code": 0
-}
-```
-
-Lookup a clientid info:
-
-```
-# Request
-GET api/v4/auth_clientid/$CLIENTID
-
-# Response
-{
-    "code": 0,
-    "data": {
-        "clientid": "a_client_id",
-        "password": "hash_password" 
-    }
-}
-```
-
-Delete a clientid:
-
-```
-# Request
-DELETE api/v4/auth_clientid/$CLIENTID
-
-# Response
-{
-    "code": 0
-}
-```
-
-Load the Plugin
----------------
-
-```
-./bin/emqx_ctl plugins load emqx_auth_clientid
-```
-
-License
--------
-
-Apache License Version 2.0
-
-Author
-------
-
-EMQ X Team.
-

+ 0 - 3
apps/emqx_auth_clientid/TODO

@@ -1,3 +0,0 @@
-1. Hash password
-2. Add Test cases
-3. Hot Reloader

+ 0 - 13
apps/emqx_auth_clientid/include/emqx_auth_clientid.hrl

@@ -1,13 +0,0 @@
--define(APP, emqx_auth_clientid).
-
--record(auth_metrics, {
-        success = 'client.auth.success',
-        failure = 'client.auth.failure',
-        ignore = 'client.auth.ignore'
-    }).
-
--define(METRICS(Type), tl(tuple_to_list(#Type{}))).
--define(METRICS(Type, K), #Type{}#Type.K).
-
--define(AUTH_METRICS, ?METRICS(auth_metrics)).
--define(AUTH_METRICS(K), ?METRICS(auth_metrics, K)).

+ 0 - 26
apps/emqx_auth_clientid/priv/emqx_auth_clientid.schema

@@ -1,26 +0,0 @@
-%%-*- mode: erlang -*-
-%% emqx_auth_clientid config mapping
-
-{mapping, "auth.client.password_hash", "emqx_auth_clientid.password_hash", [
-  {default, sha256},
-  {datatype, {enum, [plain, md5, sha, sha256]}}
-]}.
-
-{mapping, "auth.client.$id.clientid", "emqx_auth_clientid.client_list", [
-  {datatype, string}
-]}.
-
-{mapping, "auth.client.$id.password", "emqx_auth_clientid.client_list", [
-  {datatype, string}
-]}.
-
-{translation, "emqx_auth_clientid.client_list", fun(Conf) ->
-  ClientList = cuttlefish_variable:filter_by_prefix("auth.client", Conf),
-  lists:foldl(
-       fun({["auth", "client", Id, "clientid"], ClientId}, AccIn) ->
-        [{ClientId, cuttlefish:conf_get("auth.client." ++ Id ++ ".password", Conf)} | AccIn];
-       (_, AccIn) ->
-        AccIn
-       end, [], ClientList)
-end}.
-

+ 0 - 1
apps/emqx_auth_clientid/rebar.config

@@ -1 +0,0 @@
-{deps, []}.

+ 0 - 14
apps/emqx_auth_clientid/src/emqx_auth_clientid.app.src

@@ -1,14 +0,0 @@
-{application, emqx_auth_clientid,
- [{description, "EMQ X Authentication with ClientId/Password"},
-  {vsn, "5.0.0"}, % strict semver, bump manually!
-  {modules, []},
-  {registered, [emqx_auth_clientid_sup]},
-  {applications, [kernel,stdlib,minirest,emqx_passwd,emqx_libs]},
-  {mod, {emqx_auth_clientid_app, []}},
-  {env, []},
-  {licenses, ["Apache-2.0"]},
-  {maintainers, ["EMQ X Team <contact@emqx.io>"]},
-  {links, [{"Homepage", "https://emqx.io/"},
-           {"Github", "https://github.com/emqx/emqx-auth-clientid"}
-          ]}
- ]}.

+ 0 - 170
apps/emqx_auth_clientid/src/emqx_auth_clientid.erl

@@ -1,170 +0,0 @@
-%%--------------------------------------------------------------------
-%% Copyright (c) 2020 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_auth_clientid).
-
--include("emqx_auth_clientid.hrl").
-
--include_lib("emqx_libs/include/emqx.hrl").
-
-%% CLI callbacks
--export([cli/1]).
-
-%% APIs
--export([ add_clientid/2
-        , update_password/2
-        , lookup_clientid/1
-        , remove_clientid/1
-        , all_clientids/0
-        ]).
-
--export([unwrap_salt/1]).
-
-%% Auth callbacks
--export([ init/1
-        , register_metrics/0
-        , check/3
-        , description/0
-        ]).
-
--define(TAB, ?MODULE).
-
--record(?TAB, {clientid, password}).
-
-%%--------------------------------------------------------------------
-%% CLI
-%%--------------------------------------------------------------------
-
-cli(["list"]) ->
-    ClientIds = mnesia:dirty_all_keys(?TAB),
-    [emqx_ctl:print("~s~n", [ClientId]) || ClientId <- ClientIds];
-
-cli(["add", ClientId, Password]) ->
-    Ok = add_clientid(iolist_to_binary(ClientId), iolist_to_binary(Password)),
-    emqx_ctl:print("~p~n", [Ok]);
-
-cli(["update", ClientId, NewPassword]) ->
-    Ok = update_password(iolist_to_binary(ClientId), iolist_to_binary(NewPassword)),
-    emqx_ctl:print("~p~n", [Ok]);
-
-cli(["del", ClientId]) ->
-    emqx_ctl:print("~p~n", [remove_clientid(iolist_to_binary(ClientId))]);
-
-cli(_) ->
-    emqx_ctl:usage([{"clientid list", "List ClientId"},
-                    {"clientid add <ClientId> <Password>", "Add ClientId"},
-                    {"clientid update <Clientid> <NewPassword>", "Update Clientid"},
-                    {"clientid del <ClientId>", "Delete ClientId"}]).
-
-%%--------------------------------------------------------------------
-%% API
-%%--------------------------------------------------------------------
-%% @doc Add clientid with password
--spec(add_clientid(binary(), binary()) -> {atomic, ok} | {aborted, any()}).
-add_clientid(ClientId, Password) ->
-    Client = #?TAB{clientid = ClientId, password = encrypted_data(Password)},
-    ret(mnesia:transaction(fun do_add_clientid/1, [Client])).
-
-do_add_clientid(Client = #?TAB{clientid = ClientId}) ->
-    case mnesia:read(?TAB, ClientId) of
-        [] -> mnesia:write(Client);
-        [_|_] -> mnesia:abort(exitsted)
-    end.
-
-%% @doc Update clientid with newpassword
--spec(update_password(binary(), binary()) -> {atomic, ok} | {aborted, any()}).
-update_password(ClientId, NewPassword) ->
-    Client = #?TAB{clientid = ClientId, password = encrypted_data(NewPassword)},
-    ret(mnesia:transaction(fun do_update_password/1, [Client])).
-
-do_update_password(Client = #?TAB{clientid = ClientId}) ->
-    case mnesia:read(?TAB, ClientId) of
-        [_|_] -> mnesia:write(Client);
-        [] -> mnesia:abort(noexitsted)
-    end.
-
-%% @doc Lookup clientid
--spec(lookup_clientid(binary()) -> list(#?TAB{})).
-lookup_clientid(ClientId) ->
-    mnesia:dirty_read(?TAB, ClientId).
-
-%% @doc Lookup all clientids
--spec(all_clientids() -> list(binary())).
-all_clientids() ->
-    mnesia:dirty_all_keys(?TAB).
-
-%% @doc Remove clientid
--spec(remove_clientid(binary()) -> {atomic, ok} | {aborted, term()}).
-remove_clientid(ClientId) ->
-    ret(mnesia:transaction(fun mnesia:delete/1, [{?TAB, ClientId}])).
-
-unwrap_salt(<<_Salt:4/binary, HashPasswd/binary>>) ->
-    HashPasswd.
-
-%% @private
-ret({atomic, ok})     -> ok;
-ret({aborted, Error}) -> {error, Error}.
-
-%%--------------------------------------------------------------------
-%% Auth callbacks
-%%--------------------------------------------------------------------
-
-init(DefaultIds) ->
-    ok = ekka_mnesia:create_table(?TAB, [
-            {disc_copies, [node()]},
-            {attributes, record_info(fields, ?TAB)},
-            {storage_properties, [{ets, [{read_concurrency, true}]}]}]),
-    lists:foreach(fun add_default_clientid/1, DefaultIds),
-    ok = ekka_mnesia:copy_table(?TAB, disc_copies).
-
-%% @private
-add_default_clientid({ClientId, Password}) ->
-    add_clientid(iolist_to_binary(ClientId), iolist_to_binary(Password)).
-
-register_metrics() ->
-    [emqx_metrics:ensure(MetricName) || MetricName <- ?AUTH_METRICS].
-
-check(#{clientid := ClientId, password := Password}, AuthResult, #{hash_type := HashType}) ->
-    case mnesia:dirty_read(?TAB, ClientId) of
-        [] -> emqx_metrics:inc(?AUTH_METRICS(ignore));
-        [#?TAB{password = <<Salt:4/binary, Hash/binary>>}] ->
-            case Hash =:= hash(Password, Salt, HashType) of
-                true ->
-                    emqx_metrics:inc(?AUTH_METRICS(success)),
-                    {stop, AuthResult#{auth_result => success, anonymous => false}};
-                false ->
-                    emqx_metrics:inc(?AUTH_METRICS(failure)),
-                    {stop, AuthResult#{auth_result => not_authorized, anonymous => false}}
-            end
-    end.
-
-description() ->
-    "ClientId Authentication Module".
-
-encrypted_data(Password) ->
-    HashType = application:get_env(emqx_auth_clientid, password_hash, sha256),
-    SaltBin = salt(),
-    <<SaltBin/binary, (hash(Password, SaltBin, HashType))/binary>>.
-
-hash(undefined, SaltBin, HashType) ->
-    hash(<<>>, SaltBin, HashType);
-hash(Password, SaltBin, HashType) ->
-    emqx_passwd:hash(HashType, <<SaltBin/binary, Password/binary>>).
-
-salt() ->
-    rand:seed(exsplus, erlang:timestamp()),
-    Salt = rand:uniform(16#ffffffff), <<Salt:32>>.
-

+ 0 - 127
apps/emqx_auth_clientid/src/emqx_auth_clientid_api.erl

@@ -1,127 +0,0 @@
-%%--------------------------------------------------------------------
-%% Copyright (c) 2020 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_auth_clientid_api).
-
--include("emqx_auth_clientid.hrl").
-
--export([ list/2
-        , lookup/2
-        , add/2
-        , update/2
-        , delete/2
-        ]).
-
--import(proplists, [get_value/2]).
--import(minirest,  [return/0, return/1]).
-
--rest_api(#{name   => list_clientid,
-            method => 'GET',
-            path   => "/auth_clientid",
-            func   => list,
-            descr  => "List available clientid in the cluster"
-           }).
-
--rest_api(#{name   => lookup_clientid,
-            method => 'GET',
-            path   => "/auth_clientid/:bin:clientid",
-            func   => lookup,
-            descr  => "Lookup clientid in the cluster"
-           }).
-
--rest_api(#{name   => add_clientid,
-            method => 'POST',
-            path   => "/auth_clientid",
-            func   => add,
-            descr  => "Add clientid in the cluster"
-           }).
-
--rest_api(#{name   => update_clientid,
-            method => 'PUT',
-            path   => "/auth_clientid/:bin:clientid",
-            func   => update,
-            descr  => "Update clientid in the cluster"
-           }).
-
--rest_api(#{name   => delete_clientid,
-            method => 'DELETE',
-            path   => "/auth_clientid/:bin:clientid",
-            func   => delete,
-            descr  => "Delete clientid in the cluster"
-           }).
-
-list(_Bindings, _Params) ->
-    return({ok, emqx_auth_clientid:all_clientids()}).
-
-lookup(#{clientid := ClientId}, _Params) ->
-    case emqx_auth_clientid:lookup_clientid(ClientId) of
-        [] -> return({error, not_found});
-        Auth -> return({ok, format(Auth)})
-    end.
-
-add(_Bindings, Params) ->
-    ClientId = get_value(<<"clientid">>, Params),
-    Password = get_value(<<"password">>, Params),
-    case validate([clientid, password], [ClientId, Password]) of
-        ok ->
-            case emqx_auth_clientid:add_clientid(ClientId, Password) of
-                ok -> return();
-                Error -> return(Error)
-            end;
-        Error -> return(Error)
-    end.
-
-update(#{clientid := ClientId}, Params) ->
-    Password = get_value(<<"password">>, Params),
-    case validate([password], [Password]) of
-        ok ->
-            case emqx_auth_clientid:update_password(ClientId, Password) of
-                ok -> return();
-                Error -> return(Error)
-            end;
-        Error -> return(Error)
-    end.
-
-delete(#{clientid := ClientId}, _) ->
-    case emqx_auth_clientid:remove_clientid(ClientId) of
-        ok -> return();
-        Error -> return(Error)
-    end.
-
-%%------------------------------------------------------------------------------
-%% Interval Funcs
-%%------------------------------------------------------------------------------
-
-format([{?APP, ClientId, Password}]) ->
-    #{clientid => ClientId,
-      password => emqx_auth_clientid:unwrap_salt(Password)}.
-
-validate([], []) ->
-    ok;
-validate([K|Keys], [V|Values]) ->
-    case validation(K, V) of
-        false -> {error, K};
-        true  -> validate(Keys, Values)
-    end.
-
-validation(clientid, V) when is_binary(V)
-                        andalso byte_size(V) > 0 ->
-    true;
-validation(password, V) when is_binary(V)
-                        andalso byte_size(V) > 0 ->
-    true;
-validation(_, _) ->
-    false.

+ 0 - 53
apps/emqx_auth_clientid/src/emqx_auth_clientid_app.erl

@@ -1,53 +0,0 @@
-%%--------------------------------------------------------------------
-%% Copyright (c) 2020 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_auth_clientid_app).
-
--include("emqx_auth_clientid.hrl").
-
--behaviour(application).
-
--emqx_plugin(auth).
-
--export([ start/2
-        , stop/1
-        ]).
-
--behaviour(supervisor).
-
--export([init/1]).
-
-start(_Type, _Args) ->
-    emqx_ctl:register_command(clientid, {?APP, cli}, []),
-    emqx_auth_clientid:register_metrics(),
-    HashType = application:get_env(?APP, password_hash, sha256),
-    Params = #{hash_type => HashType},
-    emqx:hook('client.authenticate', fun emqx_auth_clientid:check/3, [Params]),
-    DefaultIds = application:get_env(?APP, client_list, []),
-    ok = emqx_auth_clientid:init(DefaultIds),
-    supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-
-stop(_State) ->
-    emqx:unhook('client.authenticate', fun emqx_auth_clientid:check/3),
-    emqx_ctl:unregister_command(clientid).
-
-%%--------------------------------------------------------------------
-%% Dummy supervisor
-%%--------------------------------------------------------------------
-
-init([]) ->
-    {ok, { {one_for_all, 1, 10}, []} }.
-

+ 0 - 191
apps/emqx_auth_clientid/test/emqx_auth_clientid_SUITE.erl

@@ -1,191 +0,0 @@
-%% Copyright (c) 2020 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_auth_clientid_SUITE).
-
--compile(nowarn_export_all).
--compile(export_all).
-
--include_lib("emqx_libs/include/emqx.hrl").
--include_lib("eunit/include/eunit.hrl").
--include_lib("common_test/include/ct.hrl").
-
--import(emqx_ct_http, [ request_api/3
-                      , request_api/5
-                      , get_http_data/1
-                      , create_default_app/0
-                      , default_auth_header/0
-                      ]).
-
--define(HOST, "http://127.0.0.1:8081/").
--define(API_VERSION, "v4").
--define(BASE_PATH, "api").
-
--define(CLIENTID,  <<"client_id_for_ct">>).
--define(PASSWORD,  <<"password">>).
--define(NPASSWORD, <<"password1">>).
--define(USER,      #{clientid => ?CLIENTID, zone => external}).
-
-all() ->
-    emqx_ct:all(?MODULE).
-
-init_per_suite(Config) ->
-    emqx_ct_helpers:start_apps([emqx_auth_clientid, emqx_management], fun set_special_configs/1),
-    create_default_app(),
-    Config.
-
-end_per_suite(_Config) ->
-    emqx_ct_helpers:stop_apps([emqx_auth_clientid, emqx_management]).
-
-set_special_configs(emqx) ->
-    application:set_env(emqx, allow_anonymous, true),
-    application:set_env(emqx, enable_acl_cache, false),
-    LoadedPluginPath = filename:join(["test", "emqx_SUITE_data", "loaded_plugins"]),
-    application:set_env(emqx, plugins_loaded_file,
-                        emqx_ct_helpers:deps_path(emqx, LoadedPluginPath));
-
-set_special_configs(_App) ->
-    ok.
-
-%%------------------------------------------------------------------------------
-%% Testcases
-%%------------------------------------------------------------------------------
-
-t_managing(_) ->
-    clean_all_clientids(),
-
-    ok = emqx_auth_clientid:add_clientid(?CLIENTID, ?PASSWORD),
-    ?assertNotEqual(emqx_auth_clientid:lookup_clientid(?CLIENTID), []),
-
-    {ok, #{auth_result := success,
-           anonymous := false}} = emqx_access_control:authenticate(?USER#{password => ?PASSWORD}),
-
-    {error, _} = emqx_access_control:authenticate(?USER#{password => ?NPASSWORD}),
-
-    emqx_auth_clientid:update_password(?CLIENTID, ?NPASSWORD),
-    {ok, #{auth_result := success,
-           anonymous := false}} = emqx_access_control:authenticate(?USER#{password => ?NPASSWORD}),
-
-    ok = emqx_auth_clientid:remove_clientid(?CLIENTID),
-    {ok, #{auth_result := success,
-           anonymous := true}} = emqx_access_control:authenticate(?USER#{password => ?PASSWORD}).
-
-t_cli(_) ->
-    clean_all_clientids(),
-
-    HashType = application:get_env(emqx_auth_clientid, password_hash, sha256),
-
-    emqx_auth_clientid:cli(["add", ?CLIENTID, ?PASSWORD]),
-    [{_, ?CLIENTID, <<Salt:4/binary, Hash/binary>>}] = emqx_auth_clientid:lookup_clientid(?CLIENTID),
-    ?assertEqual(Hash, emqx_passwd:hash(HashType, <<Salt/binary, ?PASSWORD/binary>>)),
-
-    emqx_auth_clientid:cli(["update", ?CLIENTID, ?NPASSWORD]),
-    [{_, ?CLIENTID, <<Salt1:4/binary, Hash1/binary>>}] = emqx_auth_clientid:lookup_clientid(?CLIENTID),
-    ?assertEqual(Hash1, emqx_passwd:hash(HashType, <<Salt1/binary, ?NPASSWORD/binary>>)),
-
-    emqx_auth_clientid:cli(["del", ?CLIENTID]),
-    ?assertEqual([], emqx_auth_clientid:lookup_clientid(?CLIENTID)),
-
-    emqx_auth_clientid:cli(["add", "user1", "pass1"]),
-    emqx_auth_clientid:cli(["add", "user2", "pass2"]),
-    ?assertEqual(2, length(emqx_auth_clientid:cli(["list"]))),
-
-    emqx_auth_clientid:cli(usage).
-
-t_rest_api(_Config) ->
-    clean_all_clientids(),
-
-    HashType = application:get_env(emqx_auth_clientid, password_hash, sha256),
-
-    {ok, Result} = request_http_rest_list(),
-    [] = get_http_data(Result),
-
-    {ok, _} = request_http_rest_add(?CLIENTID, ?PASSWORD),
-    {ok, Result1} = request_http_rest_lookup(?CLIENTID),
-    #{<<"password">> := Hash} = get_http_data(Result1),
-    [{_, ?CLIENTID, <<Salt:4/binary, Hash/binary>>}] = emqx_auth_clientid:lookup_clientid(?CLIENTID),
-    ?assertEqual(Hash, emqx_passwd:hash(HashType, <<Salt/binary, ?PASSWORD/binary>>)),
-
-    {ok, _} = request_http_rest_update(?CLIENTID, ?NPASSWORD),
-    [{_, ?CLIENTID, <<Salt1:4/binary, Hash1/binary>>}] = emqx_auth_clientid:lookup_clientid(?CLIENTID),
-    ?assertEqual(Hash1, emqx_passwd:hash(HashType, <<Salt1/binary, ?NPASSWORD/binary>>)),
-
-    {ok, _} = request_http_rest_delete(?CLIENTID),
-    ?assertEqual([], emqx_auth_clientid:lookup_clientid(?CLIENTID)).
-
-t_conf_not_override_existed(_) ->
-    clean_all_clientids(),
-
-    application:stop(emqx_auth_clientid),
-    application:set_env(emqx_auth_clientid, client_list, [{?CLIENTID, ?PASSWORD}]),
-    application:ensure_all_started(emqx_auth_clientid),
-
-    {ok, _} = emqx_access_control:authenticate(?USER#{password => ?PASSWORD}),
-    emqx_auth_clientid:cli(["update", ?CLIENTID, ?NPASSWORD]),
-
-    {error, _} = emqx_access_control:authenticate(?USER#{password => ?PASSWORD}),
-    {ok, _} = emqx_access_control:authenticate(?USER#{password => ?NPASSWORD}),
-
-    application:stop(emqx_auth_clientid),
-    application:ensure_all_started(emqx_auth_clientid),
-    {ok, _} = emqx_access_control:authenticate(?USER#{password => ?NPASSWORD}),
-
-    ct:pal("~p", [ets:tab2list(emqx_auth_clientid)]),
-    {ok, _} = request_http_rest_update(?CLIENTID, ?PASSWORD),
-    application:stop(emqx_auth_clientid),
-    application:ensure_all_started(emqx_auth_clientid),
-    ct:pal("~p", [ets:tab2list(emqx_auth_clientid)]),
-    {ok, _} = emqx_access_control:authenticate(?USER#{password => ?PASSWORD}).
-
-%%--------------------------------------------------------------------
-%% Helpers
-%%--------------------------------------------------------------------
-
-clean_all_clientids() ->
-    [mnesia:dirty_delete({emqx_auth_clientid, Id})
-     || Id <- mnesia:dirty_all_keys(emqx_auth_clientid)].
-
-%%--------------------------------------------------------------------
-%% REST API Requests
-
-request_http_rest_list() ->
-    request_api(get, uri(), default_auth_header()).
-
-request_http_rest_lookup(ClientId) ->
-    request_api(get, uri([ClientId]), default_auth_header()).
-
-request_http_rest_add(ClientId, Password) ->
-    Params = #{<<"clientid">> => ClientId, <<"password">> => Password},
-    request_api(post, uri(), [], default_auth_header(), Params).
-
-request_http_rest_update(ClientId, Password) ->
-    Params = #{<<"password">> => Password},
-    request_api(put, uri([ClientId]), [], default_auth_header(), Params).
-
-request_http_rest_delete(ClientId) ->
-    request_api(delete, uri([ClientId]), default_auth_header()).
-
-%% @private
-uri() -> uri([]).
-uri(Parts) when is_list(Parts) ->
-    NParts = [b2l(E) || E <- Parts],
-    %% http://127.0.0.1:8080/api/v4/auth_clientid/<P1>/<P2>/<Pn>
-    ?HOST ++ filename:join([?BASE_PATH, ?API_VERSION, "auth_clientid"| NParts]).
-
-%% @private
-b2l(B) when is_binary(B) ->
-    binary_to_list(B);
-b2l(L) when is_list(L) ->
-    L.
-

+ 30 - 0
apps/emqx_auth_mnesia/etc/emqx_auth_mnesia.conf

@@ -0,0 +1,30 @@
+## Password hash.
+##
+## Value: plain | md5 | sha | sha256 | sha512
+auth.mnesia.password_hash = sha256
+
+##--------------------------------------------------------------------
+## ClientId Authentication
+##--------------------------------------------------------------------
+
+## Examples
+##auth.client.1.clientid = id
+##auth.client.1.password = passwd
+##auth.client.2.clientid = dev:devid
+##auth.client.2.password = passwd2
+##auth.client.3.clientid = app:appid
+##auth.client.3.password = passwd3
+##auth.client.4.clientid = client~!@#$%^&*()_+
+##auth.client.4.password = passwd~!@#$%^&*()_+
+
+##--------------------------------------------------------------------
+## Username Authentication
+##--------------------------------------------------------------------
+
+## Examples:
+##auth.user.1.username = admin
+##auth.user.1.password = public
+##auth.user.2.username = feng@emqtt.io
+##auth.user.2.password = public
+##auth.user.3.username = name~!@#$%^&*()_+
+##auth.user.3.password = pwsswd~!@#$%^&*()_+

+ 13 - 10
apps/emqx_auth_mnesia/include/emqx_auth_mnesia.hrl

@@ -1,17 +1,20 @@
 -define(APP, emqx_auth_mnesia).
 
+-type(login():: {clientid, binary()}
+              | {username, binary()}).
+
 -record(emqx_user, {
-        login,
-        password,
-        is_superuser
-    }).
+          login :: login(),
+          password :: binary(),
+          created_at :: integer()
+        }).
 
 -record(emqx_acl, {
-        login,
-        topic,
-        action,
-        allow
-    }).
+          filter:: {login() | all, emqx_topic:topic()},
+          action :: pub | sub | pubsub,
+          access :: allow | deny,
+          created_at :: integer()
+         }).
 
 -record(auth_metrics, {
         success = 'client.auth.success',
@@ -32,4 +35,4 @@
 -define(AUTH_METRICS(K), ?METRICS(auth_metrics, K)).
 
 -define(ACL_METRICS, ?METRICS(acl_metrics)).
--define(ACL_METRICS(K), ?METRICS(acl_metrics, K)).
+-define(ACL_METRICS(K), ?METRICS(acl_metrics, K)).

+ 23 - 15
apps/emqx_auth_mnesia/priv/emqx_auth_mnesia.schema

@@ -1,34 +1,42 @@
 %%-*- mode: erlang -*-
 %% emqx_auth_mnesia config mapping
 
-{mapping, "auth.mnesia.as", "emqx_auth_mnesia.as", [
-  {default, username},
-  {datatype, {enum, [username, clientid]}}
-]}.
-
 {mapping, "auth.mnesia.password_hash", "emqx_auth_mnesia.password_hash", [
   {default, sha256},
-  {datatype, {enum, [plain, md5, sha, sha256]}}
+  {datatype, {enum, [plain, md5, sha, sha256, sha512]}}
 ]}.
 
-{mapping, "auth.mnesia.$id.login", "emqx_auth_mnesia.userlist", [
+{mapping, "auth.client.$id.clientid", "emqx_auth_mnesia.clientid_list", [
   {datatype, string}
 ]}.
 
-{mapping, "auth.mnesia.$id.password", "emqx_auth_mnesia.userlist", [
+{mapping, "auth.client.$id.password", "emqx_auth_mnesia.clientid_list", [
   {datatype, string}
 ]}.
 
-{mapping, "auth.mnesia.$id.is_superuser", "emqx_auth_mnesia.userlist", [
-  {default, false},
-  {datatype, {enum, [false, true]}}
+{translation, "emqx_auth_mnesia.clientid_list", fun(Conf) ->
+  ClientList = cuttlefish_variable:filter_by_prefix("auth.client", Conf),
+  lists:foldl(
+       fun({["auth", "client", Id, "clientid"], ClientId}, AccIn) ->
+        [{ClientId, cuttlefish:conf_get("auth.client." ++ Id ++ ".password", Conf)} | AccIn];
+       (_, AccIn) ->
+        AccIn
+       end, [], ClientList)
+end}.
+
+{mapping, "auth.user.$id.username", "emqx_auth_mnesia.username_list", [
+  {datatype, string}
+]}.
+
+{mapping, "auth.user.$id.password", "emqx_auth_mnesia.username_list", [
+  {datatype, string}
 ]}.
 
-{translation, "emqx_auth_mnesia.userlist", fun(Conf) ->
-  Userlist = cuttlefish_variable:filter_by_prefix("auth.mnesia", Conf),
+{translation, "emqx_auth_mnesia.username_list", fun(Conf) ->
+  Userlist = cuttlefish_variable:filter_by_prefix("auth.user", Conf),
   lists:foldl(
-    fun({["auth", "mnesia", Id, "login"], Username}, AccIn) ->
-        [{Username, cuttlefish:conf_get("auth.mnesia." ++ Id ++ ".password", Conf), cuttlefish:conf_get("auth.mnesia." ++ Id ++ ".is_superuser", Conf)} | AccIn];
+    fun({["auth", "user", Id, "username"], Username}, AccIn) ->
+        [{Username, cuttlefish:conf_get("auth.user." ++ Id ++ ".password", Conf)} | AccIn];
        (_, AccIn) ->
         AccIn
        end, [], Userlist)

+ 29 - 1
apps/emqx_auth_mnesia/rebar.config

@@ -1 +1,29 @@
-{deps, []}.
+{minimum_otp_vsn, "21"}.
+
+{deps,
+ [{emqx_passwd, {git, "https://github.com/emqx/emqx-passwd.git", {tag, "v1.1.1"}}},
+  {minirest, {git, "https://github.com/emqx/minirest.git", {tag, "0.3.0"}}}
+ ]}.
+
+{profiles,
+ [{test,
+   [{deps,
+     [{emqx_ct_helpers, {git, "https://github.com/emqx/emqx-ct-helpers", {tag, "v1.2.2"}}}
+     ]}
+   ]}
+ ]}.
+
+{erl_opts, [warn_unused_vars,
+            warn_shadow_vars,
+            warn_unused_import,
+            warn_obsolete_guard,
+            debug_info,
+            {parse_transform}]}.
+
+{xref_checks, [undefined_function_calls, undefined_functions,
+               locals_not_used, deprecated_function_calls,
+               warnings_as_errors, deprecated_functions]}.
+{cover_enabled, true}.
+{cover_opts, [verbose]}.
+{cover_export_enabled, true}.
+

+ 53 - 33
apps/emqx_auth_mnesia/src/emqx_acl_mnesia.erl

@@ -18,6 +18,10 @@
 
 -include("emqx_auth_mnesia.hrl").
 
+-include_lib("stdlib/include/ms_transform.hrl").
+
+-define(TABLE, emqx_acl).
+
 %% ACL Callbacks
 -export([ init/0
         , register_metrics/0
@@ -27,22 +31,38 @@
 
 init() ->
     ok = ekka_mnesia:create_table(emqx_acl, [
-            {type, bag},
             {disc_copies, [node()]},
             {attributes, record_info(fields, emqx_acl)},
             {storage_properties, [{ets, [{read_concurrency, true}]}]}]),
-    ok = ekka_mnesia:copy_table(emqx_user, disc_copies).
+    ok = ekka_mnesia:copy_table(emqx_acl, disc_copies).
 
 -spec(register_metrics() -> ok).
 register_metrics() ->
     lists:foreach(fun emqx_metrics:ensure/1, ?ACL_METRICS).
 
-check_acl(ClientInfo, PubSub, Topic, NoMatchAction, #{key_as := As}) ->
-    Login = maps:get(As, ClientInfo),
-    case do_check_acl(Login, PubSub, Topic, NoMatchAction) of
-        ok -> emqx_metrics:inc(?ACL_METRICS(ignore)), ok;
-        {stop, allow} -> emqx_metrics:inc(?ACL_METRICS(allow)), {stop, allow};
-        {stop, deny} -> emqx_metrics:inc(?ACL_METRICS(deny)), {stop, deny}
+check_acl(ClientInfo = #{ clientid := Clientid }, PubSub, Topic, _NoMatchAction, _Params) ->
+    Username = maps:get(username, ClientInfo, undefined),
+
+    Acls = case Username of
+               undefined ->
+                   emqx_acl_mnesia_cli:lookup_acl({clientid, Clientid}) ++
+                   emqx_acl_mnesia_cli:lookup_acl(all);
+               _ ->
+                   emqx_acl_mnesia_cli:lookup_acl({clientid, Clientid}) ++
+                   emqx_acl_mnesia_cli:lookup_acl({username, Username}) ++
+                   emqx_acl_mnesia_cli:lookup_acl(all)
+           end,
+
+    case match(ClientInfo, PubSub, Topic, Acls) of
+        allow ->
+            emqx_metrics:inc(?ACL_METRICS(allow)),
+            {stop, allow};
+        deny ->
+            emqx_metrics:inc(?ACL_METRICS(deny)),
+            {stop, deny};
+        _ ->
+            emqx_metrics:inc(?ACL_METRICS(ignore)),
+            ok
     end.
 
 description() -> "Acl with Mnesia".
@@ -51,33 +71,33 @@ description() -> "Acl with Mnesia".
 %% Internal functions
 %%-------------------------------------------------------------------
 
-do_check_acl(Login, PubSub, Topic, _NoMatchAction) ->
-    case match(PubSub, Topic, emqx_auth_mnesia_cli:lookup_acl(Login)) of
-        allow -> {stop, allow};
-        deny -> {stop, deny};
-        _ ->
-            case match(PubSub, Topic,  emqx_auth_mnesia_cli:lookup_acl(<<"$all">>)) of
-                allow -> {stop, allow};
-                deny -> {stop, deny};
-                _ -> ok
-            end
-    end.
-
-match(_PubSub, _Topic, []) ->
+match(_ClientInfo,  _PubSub, _Topic, []) ->
     nomatch;
-match(PubSub, Topic, [ #emqx_acl{topic = ACLTopic, action = Action, allow = Allow} | UserAcl]) ->
-    case match_actions(PubSub, Action) andalso match_topic(Topic, ACLTopic) of
-        true -> case Allow of
-                    true -> allow;
-                    _ -> deny
-                end;
-        false -> match(PubSub, Topic, UserAcl)
+match(ClientInfo, PubSub, Topic, [ {_, ACLTopic, Action, Access, _} | Acls]) ->
+    case match_actions(PubSub, Action) andalso match_topic(ClientInfo, Topic, ACLTopic) of
+        true -> Access;
+        false -> match(ClientInfo, PubSub, Topic, Acls)
     end.
 
-match_topic(Topic, ACLTopic) when is_binary(Topic) ->
-    emqx_topic:match(Topic, ACLTopic).
+match_topic(ClientInfo, Topic, ACLTopic) when is_binary(Topic) ->
+    emqx_topic:match(Topic, feed_var(ClientInfo, ACLTopic)).
 
-match_actions(_, <<"pubsub">>) -> true;
-match_actions(subscribe, <<"sub">>) -> true;
-match_actions(publish, <<"pub">>) -> true;
+match_actions(_, pubsub) -> true;
+match_actions(subscribe, sub) -> true;
+match_actions(publish, pub) -> true;
 match_actions(_, _) -> false.
+
+feed_var(ClientInfo, Pattern) ->
+    feed_var(ClientInfo, emqx_topic:words(Pattern), []).
+feed_var(_ClientInfo, [], Acc) ->
+    emqx_topic:join(lists:reverse(Acc));
+feed_var(ClientInfo = #{clientid := undefined}, [<<"%c">>|Words], Acc) ->
+    feed_var(ClientInfo, Words, [<<"%c">>|Acc]);
+feed_var(ClientInfo = #{clientid := ClientId}, [<<"%c">>|Words], Acc) ->
+    feed_var(ClientInfo, Words, [ClientId |Acc]);
+feed_var(ClientInfo = #{username := undefined}, [<<"%u">>|Words], Acc) ->
+    feed_var(ClientInfo, Words, [<<"%u">>|Acc]);
+feed_var(ClientInfo = #{username := Username}, [<<"%u">>|Words], Acc) ->
+    feed_var(ClientInfo, Words, [Username|Acc]);
+feed_var(ClientInfo, [W|Words], Acc) ->
+    feed_var(ClientInfo, Words, [W|Acc]).

+ 137 - 48
apps/emqx_auth_mnesia/src/emqx_acl_mnesia_api.erl

@@ -1,4 +1,4 @@
-%%--------------------------------------------------------------------
+%c%--------------------------------------------------------------------
 %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved.
 %%
 %% Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,94 +18,174 @@
 
 -include("emqx_auth_mnesia.hrl").
 
--import(proplists, [get_value/2]).
+-include_lib("stdlib/include/ms_transform.hrl").
+
+-import(proplists, [ get_value/2
+                   , get_value/3
+                   ]).
 
 -import(minirest,  [return/1]).
 
--rest_api(#{name   => list_emqx_acl,
+-rest_api(#{name   => list_clientid,
+            method => 'GET',
+            path   => "/acl/clientid",
+            func   => list_clientid,
+            descr  => "List available mnesia in the cluster"
+           }).
+
+-rest_api(#{name   => list_username,
             method => 'GET',
-            path   => "/mqtt_acl",
-            func   => list,
+            path   => "/acl/username",
+            func   => list_username,
             descr  => "List available mnesia in the cluster"
            }).
 
--rest_api(#{name   => lookup_emqx_acl,
+-rest_api(#{name   => list_all,
+            method => 'GET',
+            path   => "/acl/$all",
+            func   => list_all,
+            descr  => "List available mnesia in the cluster"
+           }).
+
+-rest_api(#{name   => lookup_clientid,
+            method => 'GET',
+            path   => "/acl/clientid/:bin:clientid",
+            func   => lookup,
+            descr  => "Lookup mnesia in the cluster"
+           }).
+
+-rest_api(#{name   => lookup_username,
             method => 'GET',
-            path   => "/mqtt_acl/:bin:login",
+            path   => "/acl/username/:bin:username",
             func   => lookup,
             descr  => "Lookup mnesia in the cluster"
            }).
 
--rest_api(#{name   => add_emqx_acl,
+-rest_api(#{name   => add,
             method => 'POST',
-            path   => "/mqtt_acl",
+            path   => "/acl",
             func   => add,
             descr  => "Add mnesia in the cluster"
            }).
 
--rest_api(#{name   => delete_emqx_acl,
+-rest_api(#{name   => delete_clientid,
+            method => 'DELETE',
+            path   => "/acl/clientid/:bin:clientid/topic/:bin:topic",
+            func   => delete,
+            descr  => "Delete mnesia in the cluster"
+           }).
+
+-rest_api(#{name   => delete_username,
             method => 'DELETE',
-            path   => "/mqtt_acl/:bin:login/:bin:topic",
+            path   => "/acl/username/:bin:username/topic/:bin:topic",
             func   => delete,
             descr  => "Delete mnesia in the cluster"
            }).
 
--export([ list/2
+-rest_api(#{name   => delete_all,
+            method => 'DELETE',
+            path   => "/acl/$all/topic/:bin:topic",
+            func   => delete,
+            descr  => "Delete mnesia in the cluster"
+           }).
+
+
+-export([ list_clientid/2
+        , list_username/2
+        , list_all/2
         , lookup/2
         , add/2
         , delete/2
         ]).
 
-list(_Bindings, Params) ->
-    return({ok, emqx_auth_mnesia_api:paginate(emqx_acl, Params, fun format/1)}).
+list_clientid(_Bindings, Params) ->
+    MatchSpec = ets:fun2ms(
+                  fun({emqx_acl, {{clientid, Clientid}, Topic}, Action, Access, CreatedAt}) -> {{clientid,Clientid}, Topic, Action,Access, CreatedAt} end),
+    return({ok, emqx_auth_mnesia_api:paginate(emqx_acl, MatchSpec, Params, fun emqx_acl_mnesia_cli:comparing/2, fun format/1)}).
+
+list_username(_Bindings, Params) ->
+    MatchSpec = ets:fun2ms(
+                  fun({emqx_acl, {{username, Username}, Topic}, Action, Access, CreatedAt}) -> {{username, Username}, Topic, Action,Access, CreatedAt} end),
+    return({ok, emqx_auth_mnesia_api:paginate(emqx_acl, MatchSpec, Params, fun emqx_acl_mnesia_cli:comparing/2, fun format/1)}).
 
-lookup(#{login := Login}, _Params) ->
-    return({ok, format(emqx_auth_mnesia_cli:lookup_acl(urldecode(Login)))}).
+list_all(_Bindings, Params) ->
+    MatchSpec = ets:fun2ms(
+                  fun({emqx_acl, {all, Topic}, Action, Access, CreatedAt}) -> {all, Topic, Action,Access, CreatedAt}end
+                 ),
+    return({ok, emqx_auth_mnesia_api:paginate(emqx_acl, MatchSpec, Params, fun emqx_acl_mnesia_cli:comparing/2, fun format/1)}).
+
+
+lookup(#{clientid := Clientid}, _Params) ->
+    return({ok, format(emqx_acl_mnesia_cli:lookup_acl({clientid, urldecode(Clientid)}))});
+lookup(#{username := Username}, _Params) ->
+    return({ok, format(emqx_acl_mnesia_cli:lookup_acl({username, urldecode(Username)}))}).
 
 add(_Bindings, Params) ->
     [ P | _] = Params,
     case is_list(P) of
-        true -> return(add_acl(Params, []));
-        false -> return(add_acl([Params], []))
+        true -> return(do_add(Params, []));
+        false ->
+            Re = do_add(Params),
+            case Re of
+                #{result := ok} -> return({ok, Re});
+                #{result := <<"ok">>} -> return({ok, Re});
+                _ -> return({error, Re})
+            end
     end.
 
-add_acl([ Params | ParamsN ], ReList ) ->
-    Login = urldecode(get_value(<<"login">>, Params)),
+do_add([ Params | ParamsN ], ReList) ->
+    do_add(ParamsN, [do_add(Params) | ReList]);
+
+do_add([], ReList) ->
+    {ok, ReList}.
+
+do_add(Params) ->
+    Clientid = get_value(<<"clientid">>, Params, undefined),
+    Username = get_value(<<"username">>, Params, undefined),
+    Login = case {Clientid, Username} of
+                {undefined, undefined} -> all;
+                {_, undefined} -> {clientid, urldecode(Clientid)};
+                {undefined, _} -> {username, urldecode(Username)}
+            end,
     Topic = urldecode(get_value(<<"topic">>, Params)),
     Action = urldecode(get_value(<<"action">>, Params)),
-    Allow = get_value(<<"allow">>, Params),
-    Re = case validate([login, topic, action, allow], [Login, Topic, Action, Allow]) of
+    Access = urldecode(get_value(<<"access">>, Params)),
+    Re = case validate([login, topic, action, access], [Login, Topic, Action, Access]) of
         ok -> 
-            emqx_auth_mnesia_cli:add_acl(Login, Topic, Action, Allow);
+            emqx_acl_mnesia_cli:add_acl(Login, Topic, erlang:binary_to_atom(Action, utf8), erlang:binary_to_atom(Access, utf8));
         Err -> Err
     end,
-    add_acl(ParamsN, [{Login, format_msg(Re)} | ReList]);   
+    maps:merge(#{topic => Topic,
+                 action => Action,
+                 access => Access,
+                 result => format_msg(Re)
+                }, case Login of
+                     all -> #{all => '$all'};
+                     _ -> maps:from_list([Login])
+                   end).
     
-add_acl([], ReList) ->
-    {ok, ReList}.
-
-delete(#{login := Login, topic := Topic}, _) ->
-    return(emqx_auth_mnesia_cli:remove_acl(urldecode(Login), urldecode(Topic))).
+delete(#{clientid := Clientid, topic := Topic}, _) ->
+    return(emqx_acl_mnesia_cli:remove_acl({clientid, urldecode(Clientid)}, urldecode(Topic)));
+delete(#{username := Username, topic := Topic}, _) ->
+    return(emqx_acl_mnesia_cli:remove_acl({username, urldecode(Username)}, urldecode(Topic)));
+delete(#{topic := Topic}, _) ->
+    return(emqx_acl_mnesia_cli:remove_acl(all, urldecode(Topic))).
 
 %%------------------------------------------------------------------------------
 %% Interval Funcs
 %%------------------------------------------------------------------------------
-
-format(#emqx_acl{login = Login, topic = Topic, action = Action, allow = Allow}) ->
-    #{login => Login, topic => Topic, action => Action, allow => Allow };
-
-format([]) ->
-    #{};
-
-format([#emqx_acl{login = Login, topic = Topic, action = Action, allow = Allow}]) ->
-    format(#emqx_acl{login = Login, topic = Topic, action = Action, allow = Allow});
-
-format([ #emqx_acl{login = _Key, topic = _Topic, action = _Action, allow = _Allow}| _] = List) ->
+format({{clientid, Clientid}, Topic, Action, Access, _CreatedAt}) ->
+    #{clientid => Clientid, topic => Topic, action => Action, access => Access};
+format({{username, Username}, Topic, Action, Access, _CreatedAt}) ->
+    #{username => Username, topic => Topic, action => Action, access => Access};
+format({all, Topic, Action, Access, _CreatedAt}) ->
+    #{all => '$all', topic => Topic, action => Action, access => Access};
+format(List) when is_list(List) ->
     format(List, []).
-    
-format([#emqx_acl{login = Login, topic = Topic, action = Action, allow = Allow} | List], ReList) ->
-    format(List, [ format(#emqx_acl{login = Login, topic = Topic, action = Action, allow = Allow}) | ReList]);
-format([], ReList) -> ReList.
+
+format([L | List], Relist) ->
+    format(List, [format(L) | Relist]);
+format([], ReList) -> lists:reverse(ReList).
 
 validate([], []) ->
     ok;
@@ -114,8 +194,18 @@ validate([K|Keys], [V|Values]) ->
        false -> {error, K};
        true  -> validate(Keys, Values)
    end.
-
-do_validation(login, V) when is_binary(V)
+do_validation(login, all) ->
+    true;
+do_validation(login, {clientid, V}) when is_binary(V)
+                     andalso byte_size(V) > 0->
+    true;
+do_validation(login, {username, V}) when is_binary(V)
+                     andalso byte_size(V) > 0->
+    true;
+do_validation(clientid, V) when is_binary(V)
+                     andalso byte_size(V) > 0 ->
+    true;
+do_validation(username, V) when is_binary(V)
                      andalso byte_size(V) > 0 ->
     true;
 do_validation(topic, V) when is_binary(V)
@@ -126,7 +216,7 @@ do_validation(action, V) when is_binary(V) ->
         true -> true;
         false -> false
     end;
-do_validation(allow, V) when is_boolean(V) ->
+do_validation(access, V) when V =:= <<"allow">> orelse V =:= <<"deny">> ->
     true;
 do_validation(_, _) ->
     false.
@@ -145,4 +235,3 @@ urldecode(S) ->
 urldecode(S) ->
     http_uri:decode(S).
 -endif.
-

+ 198 - 0
apps/emqx_auth_mnesia/src/emqx_acl_mnesia_cli.erl

@@ -0,0 +1,198 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2020 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_acl_mnesia_cli).
+
+-include("emqx_auth_mnesia.hrl").
+-include_lib("emqx_libs/include/logger.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
+-define(TABLE, emqx_acl).
+
+%% Acl APIs
+-export([ add_acl/4
+        , lookup_acl/1
+        , all_acls/0
+        , all_acls/1
+        , remove_acl/2
+        ]).
+
+-export([cli/1]).
+-export([comparing/2]).
+%%--------------------------------------------------------------------
+%% Acl API
+%%--------------------------------------------------------------------
+
+%% @doc Add Acls
+-spec(add_acl(login() |all, emqx_topic:topic(), pub | sub| pubsub, allow | deny) -> ok | {error, any()}).
+add_acl(Login, Topic, Action, Access) ->
+    Acls = #?TABLE{
+              filter = {Login, Topic},
+              action = Action,
+              access = Access,
+              created_at = erlang:system_time(millisecond)
+             },
+    ret(mnesia:transaction(fun mnesia:write/1, [Acls])).
+
+%% @doc Lookup acl by login
+-spec(lookup_acl(login() | all) -> list()).
+lookup_acl(undefined) -> [];
+lookup_acl(Login) ->
+    MatchSpec = ets:fun2ms(fun({?TABLE, {Filter, ACLTopic}, Action, Access, CreatedAt})
+                                 when Filter =:= Login -> {Filter, ACLTopic, Action, Access, CreatedAt} end),
+    lists:sort(fun comparing/2, ets:select(?TABLE, MatchSpec)).
+
+%% @doc Remove acl
+-spec(remove_acl(login() | all, emqx_topic:topic()) -> ok | {error, any()}).
+remove_acl(Login, Topic) ->
+    ret(mnesia:transaction(fun mnesia:delete/1, [{?TABLE, {Login, Topic}}])).
+
+%% @doc All logins
+-spec(all_acls() -> list()).
+all_acls() -> 
+    all_acls(clientid) ++
+    all_acls(username) ++
+    all_acls(all).
+
+all_acls(clientid) -> 
+    MatchSpec = ets:fun2ms(fun({?TABLE, {{clientid, Clientid}, Topic}, Action, Access, CreatedAt}) -> {{clientid, Clientid}, Topic, Action, Access, CreatedAt} end),
+    lists:sort(fun comparing/2, ets:select(?TABLE, MatchSpec));
+all_acls(username) -> 
+    MatchSpec = ets:fun2ms(fun({?TABLE, {{username, Username}, Topic}, Action, Access, CreatedAt}) -> {{username, Username}, Topic, Action, Access, CreatedAt} end),
+    lists:sort(fun comparing/2, ets:select(?TABLE, MatchSpec));
+all_acls(all) -> 
+    MatchSpec = ets:fun2ms(fun({?TABLE, {all, Topic}, Action, Access, CreatedAt}) -> {all, Topic, Action, Access, CreatedAt} end),
+    lists:sort(fun comparing/2, ets:select(?TABLE, MatchSpec)).
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+
+comparing({_, _, _, _, CreatedAt1},
+          {_, _, _, _, CreatedAt2}) ->
+    CreatedAt1 >= CreatedAt2.
+
+ret({atomic, ok})     -> ok;
+ret({aborted, Error}) -> {error, Error}.
+
+validate(action, "pub") -> true;
+validate(action, "sub") -> true;
+validate(action, "pubsub") -> true;
+validate(access, "allow") -> true;
+validate(access, "deny") -> true;
+validate(_, _) -> false.
+
+%%--------------------------------------------------------------------
+%% ACL Cli
+%%--------------------------------------------------------------------
+
+cli(["list"]) ->
+    [ begin
+        case Filter of
+            {clientid, Clientid} ->
+                emqx_ctl:print("Acl(clientid = ~p topic = ~p action = ~p access = ~p)~n",[Clientid, Topic, Action, Access]);
+            {username, Username} ->
+                emqx_ctl:print("Acl(username = ~p topic = ~p action = ~p access = ~p)~n",[Username, Topic, Action, Access]);
+            all ->
+                emqx_ctl:print("Acl($all topic = ~p action = ~p access = ~p)~n",[Topic, Action, Access])
+        end
+      end || {Filter, Topic, Action, Access, _} <- all_acls()];
+
+cli(["list", "clientid"]) ->
+    [emqx_ctl:print("Acl(clientid = ~p topic = ~p action = ~p access = ~p)~n",[Clientid, Topic, Action, Access])
+     || {{clientid, Clientid}, Topic, Action, Access, _} <- all_acls(clientid) ];
+
+cli(["list", "username"]) ->
+    [emqx_ctl:print("Acl(username = ~p topic = ~p action = ~p access = ~p)~n",[Username, Topic, Action, Access])
+     || {{username, Username}, Topic, Action, Access, _} <- all_acls(username) ];
+
+cli(["list", "_all"]) ->
+    [emqx_ctl:print("Acl($all topic = ~p action = ~p access = ~p)~n",[Topic, Action, Access])
+     || {all, Topic, Action, Access, _} <- all_acls(all) ];
+
+cli(["add", "clientid", Clientid, Topic, Action, Access]) ->
+    case validate(action, Action) andalso validate(access, Access) of
+        true ->
+            case add_acl({clientid, iolist_to_binary(Clientid)}, iolist_to_binary(Topic), list_to_existing_atom(Action), list_to_existing_atom(Access)) of
+                ok -> emqx_ctl:print("ok~n");
+                {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
+            end;
+        _ ->
+             emqx_ctl:print("Error: Input is illegal~n")
+    end;
+
+cli(["add", "username", Username, Topic, Action, Access]) ->
+    case validate(action, Action) andalso validate(access, Access) of
+        true ->
+            case add_acl({username, iolist_to_binary(Username)}, iolist_to_binary(Topic), list_to_existing_atom(Action), list_to_existing_atom(Access)) of
+                ok -> emqx_ctl:print("ok~n");
+                {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
+            end;
+        _ ->
+             emqx_ctl:print("Error: Input is illegal~n")
+    end;
+
+cli(["add", "_all", Topic, Action, Access]) ->
+    case validate(action, Action) andalso validate(access, Access) of
+        true ->
+            case add_acl(all, iolist_to_binary(Topic), list_to_existing_atom(Action), list_to_existing_atom(Access)) of
+                ok -> emqx_ctl:print("ok~n");
+                {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
+            end;
+        _ ->
+             emqx_ctl:print("Error: Input is illegal~n")
+    end;
+
+cli(["show", "clientid", Clientid]) ->
+    [emqx_ctl:print("Acl(clientid = ~p topic = ~p action = ~p access = ~p)~n",[NClientid, Topic, Action, Access])
+     || {{clientid, NClientid}, Topic, Action, Access, _} <- lookup_acl({clientid, iolist_to_binary(Clientid)}) ];
+
+cli(["show", "username", Username]) ->
+    [emqx_ctl:print("Acl(username = ~p topic = ~p action = ~p access = ~p)~n",[NUsername, Topic, Action, Access])
+     || {{username, NUsername}, Topic, Action, Access, _} <- lookup_acl({username, iolist_to_binary(Username)}) ];
+
+cli(["del", "clientid", Clientid, Topic])->
+    case remove_acl({clientid, iolist_to_binary(Clientid)}, iolist_to_binary(Topic)) of
+         ok -> emqx_ctl:print("ok~n");
+        {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
+    end;
+
+cli(["del", "username", Username, Topic])->
+    case remove_acl({username, iolist_to_binary(Username)}, iolist_to_binary(Topic)) of
+         ok -> emqx_ctl:print("ok~n");
+        {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
+    end;
+
+cli(["del", "_all", Topic])->
+    case remove_acl(all, iolist_to_binary(Topic)) of
+         ok -> emqx_ctl:print("ok~n");
+        {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
+    end;
+
+cli(_) ->
+    emqx_ctl:usage([ {"acl list clientid","List clientid acls"}
+                   , {"acl list username","List username acls"}
+                   , {"acl list _all","List $all acls"}
+                   , {"acl show clientid <Clientid>", "Lookup clientid acl detail"}
+                   , {"acl show username <Username>", "Lookup username acl detail"}
+                   , {"acl aad clientid <Clientid> <Topic> <Action> <Access>", "Add clientid acl"}
+                   , {"acl add Username <Username> <Topic> <Action> <Access>", "Add username acl"}
+                   , {"acl add _all <Topic> <Action> <Access>", "Add $all acl"}
+                   , {"acl del clientid <Clientid> <Topic>", "Delete clientid acl"}
+                   , {"acl del username <Username> <Topic>", "Delete username acl"}
+                   , {"acl del _all, <Topic>", "Delete $all acl"}
+                   ]).
+
+

+ 2 - 2
apps/emqx_auth_mnesia/src/emqx_auth_mnesia.app.src

@@ -1,9 +1,9 @@
 {application, emqx_auth_mnesia,
  [{description, "EMQ X Authentication with Mnesia"},
-  {vsn, "5.0.0"}, % strict semver, bump manually!
+  {vsn, "4.3.0"}, % strict semver, bump manually
   {modules, []},
   {registered, []},
-  {applications, [kernel,stdlib,mnesia,emqx_libs]},
+  {applications, [kernel,stdlib,mnesia]},
   {mod, {emqx_auth_mnesia_app,[]}},
   {env, []},
   {licenses, ["Apache-2.0"]},

+ 32 - 24
apps/emqx_auth_mnesia/src/emqx_auth_mnesia.erl

@@ -22,6 +22,9 @@
 -include_lib("emqx_libs/include/logger.hrl").
 -include_lib("emqx_libs/include/types.hrl").
 
+-include_lib("stdlib/include/ms_transform.hrl").
+
+-define(TABLE, emqx_user).
 %% Auth callbacks
 -export([ init/1
         , register_metrics/0
@@ -29,48 +32,53 @@
         , description/0
         ]).
 
-init(DefaultUsers) ->
+init(#{clientid_list := ClientidList, username_list := UsernameList}) ->
     ok = ekka_mnesia:create_table(emqx_user, [
             {disc_copies, [node()]},
             {attributes, record_info(fields, emqx_user)},
             {storage_properties, [{ets, [{read_concurrency, true}]}]}]),
-    ok = lists:foreach(fun add_default_user/1, DefaultUsers),
+    [ add_default_user({{clientid, iolist_to_binary(Clientid)}, iolist_to_binary(Password)})
+      || {Clientid, Password} <- ClientidList],
+    [ add_default_user({{username, iolist_to_binary(Username)}, iolist_to_binary(Password)})
+      || {Username, Password} <- UsernameList],
     ok = ekka_mnesia:copy_table(emqx_user, disc_copies).
 
 %% @private
-add_default_user({Login, Password, IsSuperuser}) ->
-    emqx_auth_mnesia_cli:add_user(iolist_to_binary(Login), iolist_to_binary(Password), IsSuperuser).
+add_default_user({Login, Password}) when is_tuple(Login) ->
+    emqx_auth_mnesia_cli:add_user(Login, Password).
 
 -spec(register_metrics() -> ok).
 register_metrics() ->
     lists:foreach(fun emqx_metrics:ensure/1, ?AUTH_METRICS).
 
-check(ClientInfo = #{password := Password}, AuthResult, #{hash_type := HashType, key_as := As}) ->
-    Login = maps:get(As, ClientInfo),
-    case emqx_auth_mnesia_cli:lookup_user(Login) of
+check(ClientInfo = #{ clientid := Clientid
+                    , password := NPassword
+                    }, AuthResult, #{hash_type := HashType}) ->
+    Username = maps:get(username, ClientInfo, undefined),
+    MatchSpec = ets:fun2ms(fun({?TABLE, {clientid, X }, Password, InterTime}) when X =:= Clientid-> Password;
+                              ({?TABLE, {username, X }, Password, InterTime}) when X =:= Username andalso X =/= undefined -> Password
+                           end),
+    case ets:select(?TABLE, MatchSpec) of
         [] -> 
             emqx_metrics:inc(?AUTH_METRICS(ignore)),
             ok;
-        [User] ->
-            case emqx_passwd:check_pass({User#emqx_user.password, Password}, HashType) of
-                ok -> 
-                    emqx_metrics:inc(?AUTH_METRICS(success)),
-                    {stop, AuthResult#{is_superuser => is_superuser(User),
-                                       anonymous => false,
-                                       auth_result => success}};
-                {error, Reason} -> 
-                    ?LOG(error, "[Mnesia] Auth from mnesia failed: ~p", [Reason]),
+        List ->
+            case [ Hash  || <<Salt:4/binary, Hash/binary>> <- lists:sort(fun emqx_auth_mnesia_cli:comparing/2, List),
+                            Hash =:= hash(NPassword, Salt, HashType)
+                 ] of
+                [] ->
+                    ?LOG(error, "[Mnesia] Auth from mnesia failed: ~p", [ClientInfo]),
                     emqx_metrics:inc(?AUTH_METRICS(failure)),
-                    {stop, AuthResult#{auth_result => password_error, anonymous => false}}
+                    {stop, AuthResult#{anonymous => false, auth_result => password_error}};
+                _ ->
+                    emqx_metrics:inc(?AUTH_METRICS(success)),
+                    {stop, AuthResult#{anonymous => false, auth_result => success}}
             end
     end.
 
 description() -> "Authentication with Mnesia".
 
-%%--------------------------------------------------------------------
-%% Internal functions
-%%--------------------------------------------------------------------
-is_superuser(#emqx_user{is_superuser = true}) ->
-    true;
-is_superuser(_) ->
-    false.
+hash(undefined, SaltBin, HashType) ->
+    hash(<<>>, SaltBin, HashType);
+hash(Password, SaltBin, HashType) ->
+    emqx_passwd:hash(HashType, <<SaltBin/binary, Password/binary>>).

+ 200 - 82
apps/emqx_auth_mnesia/src/emqx_auth_mnesia_api.erl

@@ -17,100 +17,206 @@
 -module(emqx_auth_mnesia_api).
 
 -include_lib("stdlib/include/qlc.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
 
--import(proplists, [get_value/2]).
+-define(TABLE, emqx_user).
 
+-import(proplists, [get_value/2]).
 -import(minirest,  [return/1]).
+-export([paginate/5]).
+
+-export([ list_clientid/2
+        , lookup_clientid/2
+        , add_clientid/2
+        , update_clientid/2
+        , delete_clientid/2
+        ]).
 
--rest_api(#{name   => list_emqx_user,
+-rest_api(#{name   => list_clientid,
             method => 'GET',
-            path   => "/mqtt_user",
-            func   => list,
-            descr  => "List available mnesia in the cluster"
+            path   => "/auth_clientid",
+            func   => list_clientid,
+            descr  => "List available clientid in the cluster"
            }).
 
--rest_api(#{name   => lookup_emqx_user,
+-rest_api(#{name   => lookup_clientid,
             method => 'GET',
-            path   => "/mqtt_user/:bin:login",
-            func   => lookup,
-            descr  => "Lookup mnesia in the cluster"
+            path   => "/auth_clientid/:bin:clientid",
+            func   => lookup_clientid,
+            descr  => "Lookup clientid in the cluster"
            }).
 
--rest_api(#{name   => add_emqx_user,
+-rest_api(#{name   => add_clientid,
             method => 'POST',
-            path   => "/mqtt_user",
-            func   => add,
-            descr  => "Add mnesia in the cluster"
+            path   => "/auth_clientid",
+            func   => add_clientid,
+            descr  => "Add clientid in the cluster"
            }).
 
--rest_api(#{name   => update_emqx_user,
+-rest_api(#{name   => update_clientid,
             method => 'PUT',
-            path   => "/mqtt_user/:bin:login",
-            func   => update,
-            descr  => "Update mnesia in the cluster"
+            path   => "/auth_clientid/:bin:clientid",
+            func   => update_clientid,
+            descr  => "Update clientid in the cluster"
            }).
 
--rest_api(#{name   => delete_emqx_user,
+-rest_api(#{name   => delete_clientid,
             method => 'DELETE',
-            path   => "/mqtt_user/:bin:login",
-            func   => delete,
-            descr  => "Delete mnesia in the cluster"
+            path   => "/auth_clientid/:bin:clientid",
+            func   => delete_clientid,
+            descr  => "Delete clientid in the cluster"
            }).
 
--export([ list/2
-        , lookup/2
-        , add/2
-        , update/2
-        , delete/2
+-export([ list_username/2
+        , lookup_username/2
+        , add_username/2
+        , update_username/2
+        , delete_username/2
         ]).
 
--export([paginate/3]).
+-rest_api(#{name   => list_username,
+            method => 'GET',
+            path   => "/auth_username",
+            func   => list_username,
+            descr  => "List available username in the cluster"
+           }).
+
+-rest_api(#{name   => lookup_username,
+            method => 'GET',
+            path   => "/auth_username/:bin:username",
+            func   => lookup_username,
+            descr  => "Lookup username in the cluster"
+           }).
+
+-rest_api(#{name   => add_username,
+            method => 'POST',
+            path   => "/auth_username",
+            func   => add_username,
+            descr  => "Add username in the cluster"
+           }).
+
+-rest_api(#{name   => update_username,
+            method => 'PUT',
+            path   => "/auth_username/:bin:username",
+            func   => update_username,
+            descr  => "Update username in the cluster"
+           }).
+
+-rest_api(#{name   => delete_username,
+            method => 'DELETE',
+            path   => "/auth_username/:bin:username",
+            func   => delete_username,
+            descr  => "Delete username in the cluster"
+           }).
+
+%%------------------------------------------------------------------------------
+%% Auth Clientid Api
+%%------------------------------------------------------------------------------
 
-list(_Bindings, Params) ->
-    return({ok, paginate(emqx_user, Params, fun format/1)}).
+list_clientid(_Bindings, Params) ->
+    MatchSpec = ets:fun2ms(fun({?TABLE, {clientid, Clientid}, Password, CreatedAt}) -> {?TABLE, {clientid, Clientid}, Password, CreatedAt} end),
+    return({ok, paginate(?TABLE, MatchSpec, Params, fun emqx_auth_mnesia_cli:comparing/2, fun({?TABLE, {clientid, X}, _, _}) -> #{clientid => X} end)}).
 
-lookup(#{login := Login}, _Params) ->
-    return({ok, format(emqx_auth_mnesia_cli:lookup_user(urldecode(Login)))}).
+lookup_clientid(#{clientid := Clientid}, _Params) ->
+    return({ok, format(emqx_auth_mnesia_cli:lookup_user({clientid, urldecode(Clientid)}))}).
 
-add(_Bindings, Params) ->
+add_clientid(_Bindings, Params) ->
     [ P | _] = Params,
     case is_list(P) of
-        true -> return(add_user(Params, []));
-        false -> return(add_user([Params], []))
+        true -> return(do_add_clientid(Params, []));
+        false ->
+            Re = do_add_clientid(Params),
+            case Re of
+                ok -> return(ok);
+                <<"ok">> -> return(ok);
+                _ -> return({error, format_msg(Re)})
+            end
     end.
 
-add_user([ Params | ParamsN ], ReList ) ->
-    Login = urldecode(get_value(<<"login">>, Params)),
+do_add_clientid([ Params | ParamsN ], ReList ) ->
+    Clientid = urldecode(get_value(<<"clientid">>, Params)),
+    do_add_clientid(ParamsN, [{Clientid, format_msg(do_add_clientid(Params))} | ReList]);
+
+do_add_clientid([], ReList) ->
+    {ok, ReList}.
+
+do_add_clientid(Params) ->
+    Clientid = urldecode(get_value(<<"clientid">>, Params)),
     Password = urldecode(get_value(<<"password">>, Params)),
-    IsSuperuser = get_value(<<"is_superuser">>, Params),
-    Re = case validate([login, password, is_superuser], [Login, Password, IsSuperuser]) of
+    Login = {clientid, Clientid},
+    case validate([login, password], [Login, Password]) of
         ok -> 
-            emqx_auth_mnesia_cli:add_user(Login, Password, IsSuperuser);
+            emqx_auth_mnesia_cli:add_user(Login, Password);
         Err -> Err
-    end,
-    add_user(ParamsN, [{Login, format_msg(Re)} | ReList]);   
-    
-add_user([], ReList) ->
+    end.
+
+update_clientid(#{clientid := Clientid}, Params) ->
+    Password = get_value(<<"password">>, Params),
+    case validate([password], [Password]) of
+        ok -> return(emqx_auth_mnesia_cli:update_user({clientid, urldecode(Clientid)}, urldecode(Password)));
+        Err -> return(Err)
+    end.
+
+delete_clientid(#{clientid := Clientid}, _) ->
+    return(emqx_auth_mnesia_cli:remove_user({clientid, urldecode(Clientid)})).
+
+%%------------------------------------------------------------------------------
+%% Auth Username Api
+%%------------------------------------------------------------------------------
+
+list_username(_Bindings, Params) ->
+    MatchSpec = ets:fun2ms(fun({?TABLE, {username, Username}, Password, CreatedAt}) -> {?TABLE, {username, Username}, Password, CreatedAt} end),
+    return({ok, paginate(?TABLE, MatchSpec, Params, fun emqx_auth_mnesia_cli:comparing/2, fun({?TABLE, {username, X}, _, _}) -> #{username => X} end)}).
+
+lookup_username(#{username := Username}, _Params) ->
+    return({ok, format(emqx_auth_mnesia_cli:lookup_user({username, urldecode(Username)}))}).
+
+add_username(_Bindings, Params) ->
+    [ P | _] = Params,
+    case is_list(P) of
+        true -> return(do_add_username(Params, []));
+        false ->
+            case do_add_username(Params) of
+                ok -> return(ok);
+                <<"ok">> -> return(ok);
+                Error -> return({error, format_msg(Error)})
+            end
+    end.
+
+do_add_username([ Params | ParamsN ], ReList ) ->
+    Username = urldecode(get_value(<<"username">>, Params)),
+    do_add_username(ParamsN, [{Username, format_msg(do_add_username(Params))} | ReList]);
+
+do_add_username([], ReList) ->
     {ok, ReList}.
 
-update(#{login := Login}, Params) ->
+do_add_username(Params) ->
+    Username = urldecode(get_value(<<"username">>, Params)),
+    Password = urldecode(get_value(<<"password">>, Params)),
+    Login = {username, Username},
+    case validate([login, password], [Login, Password]) of
+        ok ->
+            emqx_auth_mnesia_cli:add_user(Login, Password);
+        Err -> Err
+    end.
+
+update_username(#{username := Username}, Params) ->
     Password = get_value(<<"password">>, Params),
-    IsSuperuser = get_value(<<"is_superuser">>, Params),
-    case validate([password, is_superuser], [Password, IsSuperuser]) of
-        ok -> return(emqx_auth_mnesia_cli:update_user(urldecode(Login), urldecode(Password), IsSuperuser));
+    case validate([password], [Password]) of
+        ok -> return(emqx_auth_mnesia_cli:update_user({username, urldecode(Username)}, urldecode(Password)));
         Err -> return(Err)
     end.
 
-delete(#{login := Login}, _) ->
-    return(emqx_auth_mnesia_cli:remove_user(urldecode(Login))).
+delete_username(#{username := Username}, _) ->
+    return(emqx_auth_mnesia_cli:remove_user({username, urldecode(Username)})).
 
 %%------------------------------------------------------------------------------
 %% Paging Query
 %%------------------------------------------------------------------------------
 
-paginate(Tables, Params, RowFun) ->
-    Qh = query_handle(Tables),
-    Count = count(Tables),
+paginate(Tables, MatchSpec, Params, ComparingFun, RowFun) ->
+    Qh = query_handle(Tables, MatchSpec),
+    Count = count(Tables, MatchSpec),
     Page = page(Params),
     Limit = limit(Params),
     Cursor = qlc:cursor(Qh),
@@ -121,21 +227,28 @@ paginate(Tables, Params, RowFun) ->
     Rows = qlc:next_answers(Cursor, Limit),
     qlc:delete_cursor(Cursor),
     #{meta  => #{page => Page, limit => Limit, count => Count},
-      data  => [RowFun(Row) || Row <- Rows]}.
-
-query_handle(Table) when is_atom(Table) ->
-    qlc:q([R|| R <- ets:table(Table)]);
-query_handle([Table]) when is_atom(Table) ->
-    qlc:q([R|| R <- ets:table(Table)]);
-query_handle(Tables) ->
-    qlc:append([qlc:q([E || E <- ets:table(T)]) || T <- Tables]).
-
-count(Table) when is_atom(Table) ->
-    ets:info(Table, size);
-count([Table]) when is_atom(Table) ->
-    ets:info(Table, size);
-count(Tables) ->
-    lists:sum([count(T) || T <- Tables]).
+      data  => [RowFun(Row) || Row <- lists:sort(ComparingFun, Rows)]}.
+
+query_handle(Table, MatchSpec) when is_atom(Table) ->
+    Options = {traverse, {select, MatchSpec}},
+    qlc:q([R|| R <- ets:table(Table, Options)]);
+query_handle([Table], MatchSpec) when is_atom(Table) ->
+    Options = {traverse, {select, MatchSpec}},
+    qlc:q([R|| R <- ets:table(Table, Options)]);
+query_handle(Tables, MatchSpec) ->
+    Options = {traverse, {select, MatchSpec}},
+    qlc:append([qlc:q([E || E <- ets:table(T, Options)]) || T <- Tables]).
+
+count(Table, MatchSpec) when is_atom(Table) ->
+    [{MatchPattern, Where, _Re}] = MatchSpec,
+    NMatchSpec = [{MatchPattern, Where, [true]}],
+    ets:select_count(Table, NMatchSpec);
+count([Table], MatchSpec) when is_atom(Table) ->
+    [{MatchPattern, Where, _Re}] = MatchSpec,
+    NMatchSpec = [{MatchPattern, Where, [true]}],
+    ets:select_count(Table, NMatchSpec);
+count(Tables, MatchSpec) ->
+    lists:sum([count(T, MatchSpec) || T <- Tables]).
 
 page(Params) ->
     binary_to_integer(proplists:get_value(<<"_page">>, Params, <<"1">>)).
@@ -146,24 +259,28 @@ limit(Params) ->
         Size      -> binary_to_integer(Size)
     end.
 
-
-
 %%------------------------------------------------------------------------------
 %% Interval Funcs
 %%------------------------------------------------------------------------------
 
-format({emqx_user, Login, Password, IsSuperuser}) ->
-    #{login => Login,
-      password => Password,
-      is_superuser => IsSuperuser};
+format({?TABLE, {clientid, ClientId}, Password, _InterTime}) ->
+    #{clientid => ClientId,
+      password => Password};
 
-format([]) ->
-    #{};
+format({?TABLE, {username, Username}, Password, _InterTime}) ->
+    #{username => Username,
+      password => Password};
 
-format([{emqx_user, Login, Password, IsSuperuser}]) ->
-    #{login => Login,
-      password => Password,
-      is_superuser => IsSuperuser}.
+format([{?TABLE, {clientid, ClientId}, Password, _InterTime}]) ->
+    #{clientid => ClientId,
+      password => Password};
+
+format([{?TABLE, {username, Username}, Password, _InterTime}]) ->
+    #{username => Username,
+      password => Password};
+
+format([]) ->
+    #{}.
 
 validate([], []) ->
     ok;
@@ -173,13 +290,14 @@ validate([K|Keys], [V|Values]) ->
        true  -> validate(Keys, Values)
    end.
 
-do_validation(login, V) when is_binary(V)
+do_validation(login, {clientid, V}) when is_binary(V)
                      andalso byte_size(V) > 0 ->
     true;
-do_validation(password, V) when is_binary(V)
+do_validation(login, {username, V}) when is_binary(V)
                      andalso byte_size(V) > 0 ->
     true;
-do_validation(is_superuser, V) when is_boolean(V) ->
+do_validation(password, V) when is_binary(V)
+                     andalso byte_size(V) > 0 ->
     true;
 do_validation(_, _) ->
     false.

+ 13 - 13
apps/emqx_auth_mnesia/src/emqx_auth_mnesia_app.erl

@@ -34,8 +34,10 @@
 
 start(_StartType, _StartArgs) ->
     {ok, Sup} = emqx_auth_mnesia_sup:start_link(),
-    emqx_ctl:register_command('mqtt-user', {emqx_auth_mnesia_cli, auth_cli}, []),
-    emqx_ctl:register_command('mqtt-acl', {emqx_auth_mnesia_cli, acl_cli}, []),
+    emqx_ctl:register_command(clientid, {emqx_auth_mnesia_cli, auth_clientid_cli}, []),
+    emqx_ctl:register_command(username, {emqx_auth_mnesia_cli, auth_username_cli}, []),
+    emqx_ctl:register_command(user, {emqx_auth_mnesia_cli, auth_username_cli}, []),
+    emqx_ctl:register_command(acl, {emqx_acl_mnesia_cli, cli}, []),
     load_auth_hook(),
     load_acl_hook(),
     {ok, Sup}.
@@ -43,28 +45,26 @@ start(_StartType, _StartArgs) ->
 prep_stop(State) ->
     emqx:unhook('client.authenticate', fun emqx_auth_mnesia:check/3),
     emqx:unhook('client.check_acl', fun emqx_acl_mnesia:check_acl/5),
-    emqx_ctl:unregister_command('mqtt-user'),
-    emqx_ctl:unregister_command('mqtt-acl'),
+    emqx_ctl:unregister_command(clientid),
+    emqx_ctl:unregister_command(username),
+    emqx_ctl:unregister_command(user),
+    emqx_ctl:unregister_command(acl),
     State.
 
 stop(_State) ->
     ok.
 
 load_auth_hook() ->
-    DefaultUsers = application:get_env(?APP, userlist, []),
-    ok = emqx_auth_mnesia:init(DefaultUsers),
+    ClientidList = application:get_env(?APP, clientid_list, []),
+    UsernameList = application:get_env(?APP, username_list, []),
+    ok = emqx_auth_mnesia:init(#{clientid_list => ClientidList, username_list => UsernameList}),
     ok = emqx_auth_mnesia:register_metrics(),
     Params = #{
-            hash_type => application:get_env(emqx_auth_mnesia, hash_type, sha256),
-            key_as => application:get_env(emqx_auth_mnesia, as, username)
+            hash_type => application:get_env(emqx_auth_mnesia, hash_type, sha256)
             },
     emqx:hook('client.authenticate', fun emqx_auth_mnesia:check/3, [Params]).
 
 load_acl_hook() ->
     ok = emqx_acl_mnesia:init(),
     ok = emqx_acl_mnesia:register_metrics(),
-    Params = #{
-            key_as => application:get_env(emqx_auth_mnesia, as, username)
-            },
-    emqx:hook('client.check_acl', fun emqx_acl_mnesia:check_acl/5, [Params]).
-
+    emqx:hook('client.check_acl', fun emqx_acl_mnesia:check_acl/5, [#{}]).

+ 79 - 91
apps/emqx_auth_mnesia/src/emqx_auth_mnesia_cli.erl

@@ -18,31 +18,32 @@
 
 -include("emqx_auth_mnesia.hrl").
 -include_lib("emqx_libs/include/logger.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
 -define(TABLE, emqx_user).
 %% Auth APIs
--export([ add_user/3
-        , update_user/3
+-export([ add_user/2
+        , update_user/2
         , remove_user/1
         , lookup_user/1
         , all_users/0
-        ]).
-%% Acl APIs
--export([ add_acl/4
-        , remove_acl/2
-        , lookup_acl/1
-        , all_acls/0
+        , all_users/1
         ]).
 %% Cli
--export([ auth_cli/1
-        , acl_cli/1]).
+-export([ auth_clientid_cli/1
+        , auth_username_cli/1
+        ]).
+
+%% Helper
+-export([comparing/2]).
+
 %%--------------------------------------------------------------------
 %% Auth APIs
 %%--------------------------------------------------------------------
 
 %% @doc Add User
--spec(add_user(binary(), binary(), atom()) -> ok | {error, any()}).
-add_user(Login, Password, IsSuperuser) ->
-    User = #emqx_user{login = Login, password = encrypted_data(Password), is_superuser = IsSuperuser},
+-spec(add_user(tuple(), binary()) -> ok | {error, any()}).
+add_user(Login, Password) ->
+    User = #emqx_user{login = Login, password = encrypted_data(Password), created_at = erlang:system_time(millisecond)},
     ret(mnesia:transaction(fun insert_user/1, [User])).
 
 insert_user(User = #emqx_user{login = Login}) ->
@@ -52,30 +53,31 @@ insert_user(User = #emqx_user{login = Login}) ->
     end.
 
 %% @doc Update User
--spec(update_user(binary(), binary(), atom()) -> ok | {error, any()}).
-update_user(Login, NewPassword, IsSuperuser) ->
-    User = #emqx_user{login = Login, password = encrypted_data(NewPassword), is_superuser = IsSuperuser},
+-spec(update_user(tuple(), binary()) -> ok | {error, any()}).
+update_user(Login, NewPassword) ->
+    User = #emqx_user{login = Login, password = encrypted_data(NewPassword)},
     ret(mnesia:transaction(fun do_update_user/1, [User])).
 
 do_update_user(User = #emqx_user{login = Login}) ->
     case mnesia:read(?TABLE, Login) of
-        [_|_] -> mnesia:write(User);
+        [{?TABLE, Login, _, CreateAt}] -> mnesia:write(User#emqx_user{created_at = CreateAt});
         [] -> mnesia:abort(noexisted)
     end.
 
 %% @doc Lookup user by login
--spec(lookup_user(binary()) -> list()).
+-spec(lookup_user(tuple()) -> list()).
 lookup_user(undefined) -> [];
 lookup_user(Login) ->
     case mnesia:dirty_read(?TABLE, Login) of
         {error, Reason} ->
             ?LOG(error, "[Mnesia] do_check_user error: ~p~n", [Reason]),
             [];
-        Re -> Re
+        Re ->
+            lists:sort(fun comparing/2, Re)
     end.
 
 %% @doc Remove user
--spec(remove_user(binary()) -> ok | {error, any()}).
+-spec(remove_user(tuple()) -> ok | {error, any()}).
 remove_user(Login) ->
     ret(mnesia:transaction(fun mnesia:delete/1, [{?TABLE, Login}])).
 
@@ -83,111 +85,97 @@ remove_user(Login) ->
 -spec(all_users() -> list()).
 all_users() -> mnesia:dirty_all_keys(?TABLE).
 
-%%--------------------------------------------------------------------
-%% Acl API
-%%--------------------------------------------------------------------
-
-%% @doc Add Acls
--spec(add_acl(binary(), binary(), binary(), atom()) -> ok | {error, any()}).
-add_acl(Login, Topic, Action, Allow) ->
-    Acls = #emqx_acl{login = Login, topic = Topic, action = Action, allow = Allow},
-    ret(mnesia:transaction(fun mnesia:write/1, [Acls])).
-
-%% @doc Lookup acl by login
--spec(lookup_acl(binary()) -> list()).
-lookup_acl(undefined) -> [];
-lookup_acl(Login) ->
-    case mnesia:dirty_read(emqx_acl, Login) of
-        {error, Reason} ->
-            ?LOG(error, "[Mnesia] do_check_acl error: ~p~n", [Reason]),
-            [];
-        Re -> Re
-    end.
-
-%% @doc Remove acl
--spec(remove_acl(binary(), binary()) -> ok | {error, any()}).
-remove_acl(Login, Topic) ->
-    [ ok = mnesia:dirty_delete_object(emqx_acl, #emqx_acl{login = Login, topic = Topic, action = Action, allow = Allow})  || [Action, Allow] <- ets:select(emqx_acl, [{{emqx_acl, Login, Topic,'$1','$2'}, [], ['$$']}])],
-    ok.
-
-
-%% @doc All logins
--spec(all_acls() -> list()).
-all_acls() -> mnesia:dirty_all_keys(emqx_acl).
+all_users(clientid) ->
+    MatchSpec = ets:fun2ms(fun({?TABLE, {clientid, Clientid}, Password, CreatedAt}) -> {?TABLE, {clientid, Clientid}, Password, CreatedAt} end),
+    lists:sort(fun comparing/2, ets:select(?TABLE, MatchSpec));
 
+all_users(username) ->
+    MatchSpec = ets:fun2ms(fun({?TABLE, {username, Username}, Password, CreatedAt}) -> {?TABLE, {username, Username}, Password, CreatedAt} end),
+    lists:sort(fun comparing/2, ets:select(?TABLE, MatchSpec)).
 
 %%--------------------------------------------------------------------
 %% Internal functions
 %%--------------------------------------------------------------------
 
+comparing({?TABLE, _, _, CreatedAt1},
+          {?TABLE, _, _, CreatedAt2}) ->
+    CreatedAt1 >= CreatedAt2.
+
 ret({atomic, ok})     -> ok;
 ret({aborted, Error}) -> {error, Error}.
 
 encrypted_data(Password) ->
-    HashType = application:get_env(emqx_auth_mnesia, hash_type, sha256),
-    emqx_passwd:hash(HashType, Password).
+    HashType = application:get_env(emqx_auth_mnesia, password_hash, sha256),
+    SaltBin = salt(),
+    <<SaltBin/binary, (hash(Password, SaltBin, HashType))/binary>>.
+
+hash(undefined, SaltBin, HashType) ->
+    hash(<<>>, SaltBin, HashType);
+hash(Password, SaltBin, HashType) ->
+    emqx_passwd:hash(HashType, <<SaltBin/binary, Password/binary>>).
+
+salt() ->
+    rand:seed(exsplus, erlang:timestamp()),
+    Salt = rand:uniform(16#ffffffff), <<Salt:32>>.
 
 %%--------------------------------------------------------------------
-%% Auth APIs
+%% Auth Clientid Cli
 %%--------------------------------------------------------------------
 
-%% User
-auth_cli(["add", Login, Password, IsSuperuser]) ->
-    case add_user(iolist_to_binary(Login), iolist_to_binary(Password), IsSuperuser) of
+auth_clientid_cli(["list"]) ->
+    [emqx_ctl:print("~s~n", [ClientId]) || {?TABLE, {clientid, ClientId}, _Password, _CreatedAt} <- all_users(clientid)];
+
+auth_clientid_cli(["add", ClientId, Password]) ->
+    case add_user({clientid, iolist_to_binary(ClientId)}, iolist_to_binary(Password)) of
         ok -> emqx_ctl:print("ok~n");
         {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
     end;
 
-auth_cli(["update", Login, NewPassword, IsSuperuser]) ->
-    case update_user(iolist_to_binary(Login), iolist_to_binary(NewPassword), IsSuperuser) of
+auth_clientid_cli(["update", ClientId, NewPassword]) ->
+    case update_user({clientid, iolist_to_binary(ClientId)}, iolist_to_binary(NewPassword)) of
         ok -> emqx_ctl:print("ok~n");
         {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
     end;
 
-auth_cli(["del", Login]) ->
-    case  remove_user(iolist_to_binary(Login)) of
+auth_clientid_cli(["del", ClientId]) ->
+    case  remove_user({clientid, iolist_to_binary(ClientId)}) of
         ok -> emqx_ctl:print("ok~n");
         {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
     end;
 
-auth_cli(["show", P]) ->
-    [emqx_ctl:print("User(login = ~p is_super = ~p)~n", [Login, IsSuperuser])
-     || {_, Login, _Password, IsSuperuser} <- lookup_user(iolist_to_binary(P))];
+auth_clientid_cli(_) ->
+    emqx_ctl:usage([{"clientid list", "List clientid auth rules"},
+                    {"clientid add <Username> <Password>", "Add clientid auth rule"},
+                    {"clientid update <Username> <NewPassword>", "Update clientid auth rule"},
+                    {"clientid del <Username>", "Delete clientid auth rule"}]).
 
-auth_cli(["list"]) ->
-    [emqx_ctl:print("User(login = ~p)~n",[E])
-     || E <- all_users()];
+%%--------------------------------------------------------------------
+%% Auth Username Cli
+%%--------------------------------------------------------------------
 
-auth_cli(_) ->
-    emqx_ctl:usage([{"mqtt-user add <Login> <Password> <IsSuper>", "Add user"},
-                    {"mqtt-user update <Login> <NewPassword> <IsSuper>", "Update user"},
-                    {"mqtt-user delete <Login>", "Delete user"},
-                    {"mqtt-user show <Login>", "Lookup user detail"},
-                    {"mqtt-user list", "List all users"}]).
+auth_username_cli(["list"]) ->
+    [emqx_ctl:print("~s~n", [Username]) || {?TABLE, {username, Username}, _Password, _CreatedAt}<- all_users(username)];
 
-%% Acl
-acl_cli(["add", Login, Topic, Action, Allow]) ->
-    case add_acl(iolist_to_binary(Login), iolist_to_binary(Topic), iolist_to_binary(Action), Allow) of
+auth_username_cli(["add", Username, Password]) ->
+    case add_user({username, iolist_to_binary(Username)}, iolist_to_binary(Password)) of
         ok -> emqx_ctl:print("ok~n");
         {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
     end;
 
-acl_cli(["del", Login, Topic])->
-    case remove_acl(iolist_to_binary(Login), iolist_to_binary(Topic)) of
-         ok -> emqx_ctl:print("ok~n");
+auth_username_cli(["update", Username, NewPassword]) ->
+    case update_user({username, iolist_to_binary(Username)}, iolist_to_binary(NewPassword)) of
+        ok -> emqx_ctl:print("ok~n");
         {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
     end;
 
-acl_cli(["show", P]) ->
-    [emqx_ctl:print("Acl(login = ~p topic = ~p action = ~p allow = ~p)~n",[Login, Topic, Action, Allow])
-     || {_, Login, Topic, Action, Allow} <- lookup_acl(iolist_to_binary(P)) ];
-
-acl_cli(["list"]) ->
-    [emqx_ctl:print("Acl(login = ~p)~n",[E])
-     || E <- all_acls() ];
+auth_username_cli(["del", Username]) ->
+    case  remove_user({username, iolist_to_binary(Username)}) of
+        ok -> emqx_ctl:print("ok~n");
+        {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
+    end;
 
-acl_cli(_) ->
-    emqx_ctl:usage([{"mqtt-acl add <Login> <Topic> <Action> <Allow>", "Add acl"},
-                    {"mqtt-acl show <Login>", "Lookup acl detail"},
-                    {"mqtt-acl del <Login>", "Delete acl"},
-                    {"mqtt-acl list","List all acls"}]).
+auth_username_cli(_) ->
+    emqx_ctl:usage([{"users list", "List username auth rules"},
+                    {"users add <Username> <Password>", "Add username auth rule"},
+                    {"users update <Username> <NewPassword>", "Update username auth rule"},
+                    {"users del <Username>", "Delete username auth rule"}]).

+ 97 - 161
apps/emqx_auth_mnesia/test/emqx_acl_mnesia_SUITE.erl

@@ -76,154 +76,41 @@ set_special_configs(_App) ->
 t_management(_Config) ->
     clean_all_acls(),
     ?assertEqual("Acl with Mnesia", emqx_acl_mnesia:description()),
-    ?assertEqual([], emqx_auth_mnesia_cli:all_acls()),
-
-    ok = emqx_auth_mnesia_cli:add_acl(<<"test_username">>, <<"Topic/A">>, <<"sub">>, true),
-    ok = emqx_auth_mnesia_cli:add_acl(<<"test_username">>, <<"Topic/B">>, <<"pub">>, true),
-    ok = emqx_auth_mnesia_cli:add_acl(<<"test_username">>, <<"Topic/C">>, <<"pubsub">>, true),
-    
-    ?assertEqual([{emqx_acl,<<"test_username">>,<<"Topic/A">>,<<"sub">>, true},
-                  {emqx_acl,<<"test_username">>,<<"Topic/B">>,<<"pub">>, true},
-                  {emqx_acl,<<"test_username">>,<<"Topic/C">>,<<"pubsub">>, true}],emqx_auth_mnesia_cli:lookup_acl(<<"test_username">>)),
-    ok = emqx_auth_mnesia_cli:remove_acl(<<"test_username">>, <<"Topic/A">>),
-    ?assertEqual([{emqx_acl,<<"test_username">>,<<"Topic/B">>,<<"pub">>, true},
-                  {emqx_acl,<<"test_username">>,<<"Topic/C">>,<<"pubsub">>, true}], emqx_auth_mnesia_cli:lookup_acl(<<"test_username">>)),
-
-
-    ok = emqx_auth_mnesia_cli:add_acl(<<"$all">>, <<"Topic/A">>, <<"sub">>, true),
-    ok = emqx_auth_mnesia_cli:add_acl(<<"$all">>, <<"Topic/B">>, <<"pub">>, true),
-    ok = emqx_auth_mnesia_cli:add_acl(<<"$all">>, <<"Topic/C">>, <<"pubsub">>, true),
-
-    ?assertEqual([{emqx_acl,<<"$all">>,<<"Topic/A">>,<<"sub">>, true},
-                  {emqx_acl,<<"$all">>,<<"Topic/B">>,<<"pub">>, true},
-                  {emqx_acl,<<"$all">>,<<"Topic/C">>,<<"pubsub">>, true}],emqx_auth_mnesia_cli:lookup_acl(<<"$all">>)),
-    ok = emqx_auth_mnesia_cli:remove_acl(<<"$all">>, <<"Topic/A">>),
-    ?assertEqual([{emqx_acl,<<"$all">>,<<"Topic/B">>,<<"pub">>, true},
-                  {emqx_acl,<<"$all">>,<<"Topic/C">>,<<"pubsub">>, true}], emqx_auth_mnesia_cli:lookup_acl(<<"$all">>)).
-
-t_check_acl_as_clientid(_) ->
-    clean_all_acls(),
-    emqx_modules:load_module(emqx_mod_acl_internal, false),
-
-    User1 = #{zone => external, clientid => <<"test_clientid">>},
-    User2 = #{zone => external, clientid => <<"no_exist">>},
-
-    ok = emqx_auth_mnesia_cli:add_acl(<<"test_clientid">>, <<"#">>, <<"sub">>, false),
-    ok = emqx_auth_mnesia_cli:add_acl(<<"test_clientid">>, <<"+/A">>, <<"pub">>, false),
-    ok = emqx_auth_mnesia_cli:add_acl(<<"test_clientid">>, <<"Topic/A/B">>, <<"pubsub">>, true),
-
-    deny  = emqx_access_control:check_acl(User1, subscribe, <<"Any">>),
-    deny  = emqx_access_control:check_acl(User1, publish, <<"Any/A">>),
-    allow  = emqx_access_control:check_acl(User1, publish, <<"Any/C">>),
-    allow = emqx_access_control:check_acl(User1, publish, <<"Topic/A/B">>),
-
-    allow = emqx_access_control:check_acl(User2, subscribe, <<"Topic/C">>),
-    allow = emqx_access_control:check_acl(User2, publish,   <<"Topic/D">>).
-
-t_check_acl_as_username(_Config) ->
-    clean_all_acls(),
-    emqx_modules:load_module(emqx_mod_acl_internal, false),
-    
-    User1 = #{zone => external, username => <<"test_username">>},
-    User2 = #{zone => external, username => <<"no_exist">>},
-
-    ok = emqx_auth_mnesia_cli:add_acl(<<"test_username">>, <<"Topic/A">>, <<"sub">>, true),
-    ok = emqx_auth_mnesia_cli:add_acl(<<"test_username">>, <<"Topic/B">>, <<"pub">>, true),
-    ok = emqx_auth_mnesia_cli:add_acl(<<"test_username">>, <<"Topic/A/B">>, <<"pubsub">>, false),
-    allow = emqx_access_control:check_acl(User1, subscribe, <<"Topic/A">>),
-    allow = emqx_access_control:check_acl(User1, subscribe, <<"Topic/B">>),
-    deny  = emqx_access_control:check_acl(User1, subscribe, <<"Topic/A/B">>),
-    allow = emqx_access_control:check_acl(User1, publish,   <<"Topic/A">>),
-    allow = emqx_access_control:check_acl(User1, publish,   <<"Topic/B">>),
-    deny  = emqx_access_control:check_acl(User1, publish,   <<"Topic/A/B">>),
-
-    allow = emqx_access_control:check_acl(User2, subscribe, <<"Topic/C">>),
-    allow = emqx_access_control:check_acl(User2, publish,   <<"Topic/D">>).
-
-t_check_acl_as_all(_) ->
-    clean_all_acls(),
-    emqx_modules:load_module(emqx_mod_acl_internal, false),
-
-    ok = emqx_auth_mnesia_cli:add_acl(<<"$all">>, <<"Topic/A">>, <<"sub">>, false),
-    ok = emqx_auth_mnesia_cli:add_acl(<<"$all">>, <<"Topic/B">>, <<"pub">>, false),
-    ok = emqx_auth_mnesia_cli:add_acl(<<"$all">>, <<"Topic/A/B">>, <<"pubsub">>, true),
-
-    User1 = #{zone => external, username => <<"test_username">>},
-    User2 = #{zone => external, username => <<"no_exist">>},
+    ?assertEqual([], emqx_acl_mnesia_cli:all_acls()),
 
-    ok = emqx_auth_mnesia_cli:add_acl(<<"test_username">>, <<"Topic/A">>, <<"sub">>, true),
-    ok = emqx_auth_mnesia_cli:add_acl(<<"test_username">>, <<"Topic/B">>, <<"pub">>, true),
-    ok = emqx_auth_mnesia_cli:add_acl(<<"test_username">>, <<"Topic/A/B">>, <<"pubsub">>, false),
+    ok = emqx_acl_mnesia_cli:add_acl({clientid, <<"test_clientid">>}, <<"topic/%c">>, sub, allow),
+    ok = emqx_acl_mnesia_cli:add_acl({clientid, <<"test_clientid">>}, <<"topic/+">>, pub, deny),
+    ok = emqx_acl_mnesia_cli:add_acl({username, <<"test_username">>}, <<"topic/%u">>, sub, deny),
+    ok = emqx_acl_mnesia_cli:add_acl({username, <<"test_username">>}, <<"topic/+">>, pub, allow),
+    ok = emqx_acl_mnesia_cli:add_acl(all, <<"#">>, pubsub, deny),
 
-    allow = emqx_access_control:check_acl(User1, subscribe, <<"Topic/A">>),
-    allow = emqx_access_control:check_acl(User1, subscribe, <<"Topic/B">>),
-    deny  = emqx_access_control:check_acl(User1, subscribe, <<"Topic/A/B">>),
-    allow = emqx_access_control:check_acl(User1, publish,   <<"Topic/A">>),
-    allow = emqx_access_control:check_acl(User1, publish,   <<"Topic/B">>),
-    deny  = emqx_access_control:check_acl(User1, publish,   <<"Topic/A/B">>),
+    ?assertEqual(2, length(emqx_acl_mnesia_cli:lookup_acl({clientid, <<"test_clientid">>}))),
+    ?assertEqual(2, length(emqx_acl_mnesia_cli:lookup_acl({username, <<"test_username">>}))),
+    ?assertEqual(1, length(emqx_acl_mnesia_cli:lookup_acl(all))),
+    ?assertEqual(5, length(emqx_acl_mnesia_cli:all_acls())),
 
-    deny  = emqx_access_control:check_acl(User2, subscribe, <<"Topic/A">>),
-    deny  = emqx_access_control:check_acl(User2, publish,   <<"Topic/B">>),
-    allow = emqx_access_control:check_acl(User2, subscribe, <<"Topic/A/B">>),
-    allow = emqx_access_control:check_acl(User2, publish,   <<"Topic/A/B">>),
-    allow = emqx_access_control:check_acl(User2, subscribe, <<"Topic/C">>),
-    allow = emqx_access_control:check_acl(User2, publish,   <<"Topic/D">>).
-
-t_rest_api(_Config) ->
-    clean_all_acls(),
-
-    {ok, Result} = request_http_rest_list(),
-    [] = get_http_data(Result),
-
-    Params = #{<<"login">> => <<"test_username">>, <<"topic">> => <<"Topic/A">>, <<"action">> => <<"pubsub">>, <<"allow">> => true},
-    {ok, _} = request_http_rest_add(Params),
-    {ok, Result1} = request_http_rest_lookup(<<"test_username">>),
-    #{<<"login">> := <<"test_username">>, <<"topic">> := <<"Topic/A">>, <<"action">> := <<"pubsub">>, <<"allow">> := true} = get_http_data(Result1),
-
-    Params1 = [
-                #{<<"login">> => <<"$all">>, <<"topic">> => <<"+/A">>, <<"action">> => <<"pub">>, <<"allow">> => true},
-                #{<<"login">> => <<"test_username">>, <<"topic">> => <<"+/A">>, <<"action">> => <<"pub">>, <<"allow">> => true},
-                #{<<"login">> => <<"test_username/1">>, <<"topic">> => <<"#">>, <<"action">> => <<"sub">>, <<"allow">> => true},
-                #{<<"login">> => <<"test_username/2">>, <<"topic">> => <<"+/A">>, <<"action">> => <<"error_format">>, <<"allow">> => true}
-                ],
-    {ok, Result2} = request_http_rest_add(Params1),
-    #{
-        <<"$all">> := <<"ok">>,
-        <<"test_username">> := <<"ok">>,
-        <<"test_username/1">> := <<"ok">>,
-        <<"test_username/2">> := <<"{error,action}">>
-        } = get_http_data(Result2),
-
-    {ok, Result3} = request_http_rest_lookup(<<"test_username">>),
-    [#{<<"login">> := <<"test_username">>, <<"topic">> := <<"+/A">>, <<"action">> := <<"pub">>, <<"allow">> := true},
-     #{<<"login">> := <<"test_username">>, <<"topic">> := <<"Topic/A">>, <<"action">> := <<"pubsub">>, <<"allow">> := true}]
-     = get_http_data(Result3),
-
-    {ok, Result4} = request_http_rest_lookup(<<"$all">>),
-    #{<<"login">> := <<"$all">>, <<"topic">> := <<"+/A">>, <<"action">> := <<"pub">>, <<"allow">> := true}
-      = get_http_data(Result4),
-
-    {ok, _} = request_http_rest_delete(<<"$all">>, <<"+/A">>),
-    {ok, _} = request_http_rest_delete(<<"test_username">>, <<"+/A">>),
-    {ok, _} = request_http_rest_delete(<<"test_username">>, <<"Topic/A">>),
-    {ok, _} = request_http_rest_delete(<<"test_username/1">>, <<"#">>),
-    {ok, Result5} = request_http_rest_list(),
-    [] = get_http_data(Result5).
-
-
-t_run_command(_) ->
-    clean_all_acls(),
-    ?assertEqual(ok, emqx_ctl:run_command(["mqtt-acl", "add", "TestUser", "Topic/A", "sub", true])),
-    ?assertEqual([{emqx_acl,<<"TestUser">>,<<"Topic/A">>,<<"sub">>, true}],emqx_auth_mnesia_cli:lookup_acl(<<"TestUser">>)),
-
-    ?assertEqual(ok, emqx_ctl:run_command(["mqtt-acl", "del", "TestUser", "Topic/A"])),
-    ?assertEqual([],emqx_auth_mnesia_cli:lookup_acl(<<"TestUser">>)),
-
-    ?assertEqual(ok, emqx_ctl:run_command(["mqtt-acl", "show", "TestUser"])),
-    ?assertEqual(ok, emqx_ctl:run_command(["mqtt-acl", "list"])),
-    ?assertEqual(ok, emqx_ctl:run_command(["mqtt-acl"])).
-
-t_cli(_) ->
+    User1 = #{zone => external, clientid => <<"test_clientid">>},
+    User2 = #{zone => external, clientid => <<"no_exist">>, username => <<"test_username">>},
+    User3 = #{zone => external, clientid => <<"test_clientid">>, username => <<"test_username">>},
+    allow = emqx_access_control:check_acl(User1, subscribe, <<"topic/test_clientid">>),
+    deny  = emqx_access_control:check_acl(User1, publish,   <<"topic/A">>),
+    deny  = emqx_access_control:check_acl(User2, subscribe, <<"topic/test_username">>),
+    allow = emqx_access_control:check_acl(User2, publish,   <<"topic/A">>),
+    allow = emqx_access_control:check_acl(User3, subscribe, <<"topic/test_clientid">>),
+    deny  = emqx_access_control:check_acl(User3, subscribe, <<"topic/test_username">>),
+    deny  = emqx_access_control:check_acl(User3, publish,   <<"topic/A">>),
+    deny  = emqx_access_control:check_acl(User3, subscribe, <<"topic/A/B">>),
+    deny  = emqx_access_control:check_acl(User3, publish,   <<"topic/A/B">>),
+
+    ok = emqx_acl_mnesia_cli:remove_acl({clientid, <<"test_clientid">>}, <<"topic/%c">>),
+    ok = emqx_acl_mnesia_cli:remove_acl({clientid, <<"test_clientid">>}, <<"topic/+">>),
+    ok = emqx_acl_mnesia_cli:remove_acl({username, <<"test_username">>}, <<"topic/%u">>),
+    ok = emqx_acl_mnesia_cli:remove_acl({username, <<"test_username">>}, <<"topic/+">>),
+    ok = emqx_acl_mnesia_cli:remove_acl(all, <<"#">>),
+
+    ?assertEqual([], emqx_acl_mnesia_cli:all_acls()).
+
+t_acl_cli(_Config) ->
     meck:new(emqx_ctl, [non_strict, passthrough]),
     meck:expect(emqx_ctl, print, fun(Arg) -> emqx_ctl:format(Arg) end),
     meck:expect(emqx_ctl, print, fun(Msg, Arg) -> emqx_ctl:format(Msg, Arg) end),
@@ -231,18 +118,67 @@ t_cli(_) ->
     meck:expect(emqx_ctl, usage, fun(Cmd, Descr) -> emqx_ctl:format_usage(Cmd, Descr) end),
 
     clean_all_acls(),
-    ?assertMatch({match, _}, re:run(emqx_auth_mnesia_cli:acl_cli(["add", "TestUser", "Topic/A", "sub", true]), "ok")),
-    ?assertMatch(["Acl(login = <<\"TestUser\">> topic = <<\"Topic/A\">> action = <<\"sub\">> allow = true)\n"], emqx_auth_mnesia_cli:acl_cli(["show", "TestUser"])),
-    ?assertMatch(["Acl(login = <<\"TestUser\">>)\n"], emqx_auth_mnesia_cli:acl_cli(["list"])),
 
-    ?assertMatch({match, _}, re:run(emqx_auth_mnesia_cli:acl_cli(["del", "TestUser", "Topic/A"]), "ok")),
-    ?assertMatch([], emqx_auth_mnesia_cli:acl_cli(["show", "TestUser"])),
-    ?assertMatch([], emqx_auth_mnesia_cli:acl_cli(["list"])),
+    ?assertEqual(0, length(emqx_acl_mnesia_cli:cli(["list"]))),
+
+    emqx_acl_mnesia_cli:cli(["add", "clientid", "test_clientid", "topic/A", "pub", "allow"]),
+    ?assertMatch(["Acl(clientid = <<\"test_clientid\">> topic = <<\"topic/A\">> action = pub access = allow)\n"], emqx_acl_mnesia_cli:cli(["show", "clientid", "test_clientid"])),
+    ?assertMatch(["Acl(clientid = <<\"test_clientid\">> topic = <<\"topic/A\">> action = pub access = allow)\n"], emqx_acl_mnesia_cli:cli(["list", "clientid"])),
 
-    ?assertMatch({match, _}, re:run(emqx_auth_mnesia_cli:acl_cli([]), "mqtt-acl")),
+    emqx_acl_mnesia_cli:cli(["add", "username", "test_username", "topic/B", "sub", "deny"]),
+    ?assertMatch(["Acl(username = <<\"test_username\">> topic = <<\"topic/B\">> action = sub access = deny)\n"], emqx_acl_mnesia_cli:cli(["show", "username", "test_username"])),
+    ?assertMatch(["Acl(username = <<\"test_username\">> topic = <<\"topic/B\">> action = sub access = deny)\n"], emqx_acl_mnesia_cli:cli(["list", "username"])),
+
+    emqx_acl_mnesia_cli:cli(["add", "_all", "#", "pubsub", "deny"]),
+    ?assertMatch(["Acl($all topic = <<\"#\">> action = pubsub access = deny)\n"], emqx_acl_mnesia_cli:cli(["list", "_all"])),
+    ?assertEqual(3, length(emqx_acl_mnesia_cli:cli(["list"]))),
+
+    emqx_acl_mnesia_cli:cli(["del", "clientid", "test_clientid", "topic/A"]),
+    emqx_acl_mnesia_cli:cli(["del", "username", "test_username", "topic/B"]),
+    emqx_acl_mnesia_cli:cli(["del", "_all", "#"]),
+    ?assertEqual(0, length(emqx_acl_mnesia_cli:cli(["list"]))),
 
     meck:unload(emqx_ctl).
 
+t_rest_api(_Config) ->
+    clean_all_acls(),
+
+    Params1 = [#{<<"clientid">> => <<"test_clientid">>, <<"topic">> => <<"topic/A">>, <<"action">> => <<"pub">>, <<"access">> => <<"allow">>},
+               #{<<"clientid">> => <<"test_clientid">>, <<"topic">> => <<"topic/B">>, <<"action">> => <<"sub">>, <<"access">> => <<"allow">>},
+               #{<<"clientid">> => <<"test_clientid">>, <<"topic">> => <<"topic/C">>, <<"action">> => <<"pubsub">>, <<"access">> => <<"deny">>}],
+    {ok, _} = request_http_rest_add([], Params1),
+    {ok, Re1} = request_http_rest_list(["clientid", "test_clientid"]),
+    ?assertMatch(3, length(get_http_data(Re1))),
+    {ok, _} = request_http_rest_delete(["clientid", "test_clientid", "topic", "topic/A"]),
+    {ok, _} = request_http_rest_delete(["clientid", "test_clientid", "topic", "topic/B"]),
+    {ok, _} = request_http_rest_delete(["clientid", "test_clientid", "topic", "topic/C"]),
+    {ok, Res1} = request_http_rest_list(["clientid"]),
+    ?assertMatch([], get_http_data(Res1)),
+
+    Params2 = [#{<<"username">> => <<"test_username">>, <<"topic">> => <<"topic/A">>, <<"action">> => <<"pub">>, <<"access">> => <<"allow">>},
+               #{<<"username">> => <<"test_username">>, <<"topic">> => <<"topic/B">>, <<"action">> => <<"sub">>, <<"access">> => <<"allow">>},
+               #{<<"username">> => <<"test_username">>, <<"topic">> => <<"topic/C">>, <<"action">> => <<"pubsub">>, <<"access">> => <<"deny">>}],
+    {ok, _} = request_http_rest_add([], Params2),
+    {ok, Re2} = request_http_rest_list(["username", "test_username"]),
+    ?assertMatch(3, length(get_http_data(Re2))),
+    {ok, _} = request_http_rest_delete(["username", "test_username", "topic", "topic/A"]),
+    {ok, _} = request_http_rest_delete(["username", "test_username", "topic", "topic/B"]),
+    {ok, _} = request_http_rest_delete(["username", "test_username", "topic", "topic/C"]),
+    {ok, Res2} = request_http_rest_list(["username"]),
+    ?assertMatch([], get_http_data(Res2)),
+
+    Params3 = [#{<<"topic">> => <<"topic/A">>, <<"action">> => <<"pub">>, <<"access">> => <<"allow">>},
+               #{<<"topic">> => <<"topic/B">>, <<"action">> => <<"sub">>, <<"access">> => <<"allow">>},
+               #{<<"topic">> => <<"topic/C">>, <<"action">> => <<"pubsub">>, <<"access">> => <<"deny">>}],
+    {ok, _} = request_http_rest_add([], Params3),
+    {ok, Re3} = request_http_rest_list(["$all"]),
+    ?assertMatch(3, length(get_http_data(Re3))),
+    {ok, _} = request_http_rest_delete(["$all", "topic", "topic/A"]),
+    {ok, _} = request_http_rest_delete(["$all", "topic", "topic/B"]),
+    {ok, _} = request_http_rest_delete(["$all", "topic", "topic/C"]),
+    {ok, Res3} = request_http_rest_list(["$all"]),
+    ?assertMatch([], get_http_data(Res3)).
+
 %%------------------------------------------------------------------------------
 %% Helpers
 %%------------------------------------------------------------------------------
@@ -255,22 +191,22 @@ clean_all_acls() ->
 %% HTTP Request
 %%--------------------------------------------------------------------
 
-request_http_rest_list() ->
-    request_api(get, uri(), default_auth_header()).
+request_http_rest_list(Path) ->
+    request_api(get, uri(Path), default_auth_header()).
 
-request_http_rest_lookup(Login) ->
-    request_api(get, uri([Login]), default_auth_header()).
+request_http_rest_lookup(Path) ->
+    request_api(get, uri(Path), default_auth_header()).
 
-request_http_rest_add(Params) ->
-    request_api(post, uri(), [], default_auth_header(), Params).
+request_http_rest_add(Path, Params) ->
+    request_api(post, uri(Path), [], default_auth_header(), Params).
 
-request_http_rest_delete(Login, Topic) ->
-    request_api(delete, uri([Login, Topic]), default_auth_header()).
+request_http_rest_delete(Path) ->
+    request_api(delete, uri(Path), default_auth_header()).
 
 uri() -> uri([]).
 uri(Parts) when is_list(Parts) ->
     NParts = [b2l(E) || E <- Parts],
-    ?HOST ++ filename:join([?BASE_PATH, ?API_VERSION, "mqtt_acl"| NParts]).
+    ?HOST ++ filename:join([?BASE_PATH, ?API_VERSION, "acl"| NParts]).
 
 %% @private
 b2l(B) when is_binary(B) ->

+ 134 - 109
apps/emqx_auth_mnesia/test/emqx_auth_mnesia_SUITE.erl

@@ -2,10 +2,7 @@
 %% Copyright (c) 2020 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
+%% 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,
@@ -33,6 +30,12 @@
 -define(API_VERSION, "v4").
 -define(BASE_PATH, "api").
 
+-define(TABLE, emqx_user).
+-define(CLIENTID,  <<"clientid_for_ct">>).
+-define(USERNAME,  <<"username_for_ct">>).
+-define(PASSWORD,  <<"password">>).
+-define(NPASSWORD, <<"new_password">>).
+
 all() ->
     emqx_ct:all(?MODULE).
 
@@ -81,143 +84,166 @@ set_special_configs(_App) ->
 %% Testcases
 %%------------------------------------------------------------------------------
 
-t_check_as_username(_Config) ->
+t_management(_Config) ->
     clean_all_users(),
 
-    ok = emqx_auth_mnesia_cli:add_user(<<"test_username">>, <<"password">>, true),
-    {error, existed} = emqx_auth_mnesia_cli:add_user(<<"test_username">>, <<"password">>, true),
+    ok = emqx_auth_mnesia_cli:add_user({username,?USERNAME}, ?PASSWORD),
+    {error, existed} = emqx_auth_mnesia_cli:add_user({username,?USERNAME}, ?PASSWORD),
+    ?assertMatch([{?TABLE, {username, ?USERNAME}, _Password, _InterTime}], emqx_auth_mnesia_cli:all_users(username)),
+
+    ok = emqx_auth_mnesia_cli:add_user({clientid,?CLIENTID}, ?PASSWORD),
+    {error, existed} = emqx_auth_mnesia_cli:add_user({clientid,?CLIENTID}, ?PASSWORD),
+    ?assertMatch([{?TABLE, {clientid, ?CLIENTID}, _Password, _InterTime}], emqx_auth_mnesia_cli:all_users(clientid)),
+
+    ?assertEqual(2,length(emqx_auth_mnesia_cli:all_users())),
 
-    ok = emqx_auth_mnesia_cli:update_user(<<"test_username">>, <<"new_password">>, false),
-    {error,noexisted} = emqx_auth_mnesia_cli:update_user(<<"no_existed_user">>, <<"password">>, true),
+    ok = emqx_auth_mnesia_cli:update_user({username,?USERNAME}, ?NPASSWORD),
+    {error,noexisted} = emqx_auth_mnesia_cli:update_user({username, <<"no_existed_user">>}, ?PASSWORD),
 
-    [<<"test_username">>] = emqx_auth_mnesia_cli:all_users(),
-    [{emqx_user, <<"test_username">>, _HashedPass, false}] =
-        emqx_auth_mnesia_cli:lookup_user(<<"test_username">>),
+    ok = emqx_auth_mnesia_cli:update_user({clientid,?CLIENTID}, ?NPASSWORD),
+    {error,noexisted} = emqx_auth_mnesia_cli:update_user({clientid, <<"no_existed_user">>}, ?PASSWORD),
 
-    User1 = #{username => <<"test_username">>,
-              password => <<"new_password">>,
+    
+    ?assertMatch([{?TABLE, {username, ?USERNAME}, _Password, _InterTime}], emqx_auth_mnesia_cli:lookup_user({username, ?USERNAME})),
+    ?assertMatch([{?TABLE, {clientid, ?CLIENTID}, _Password, _InterTime}], emqx_auth_mnesia_cli:lookup_user({clientid, ?CLIENTID})),
+
+    User1 = #{username => ?USERNAME,
+              clientid => undefined,
+              password => ?NPASSWORD,
               zone     => external},
 
-    {ok, #{is_superuser := false, 
-           auth_result := success,
+    {ok, #{auth_result := success,
            anonymous := false}} = emqx_access_control:authenticate(User1),
 
     {error,password_error} = emqx_access_control:authenticate(User1#{password => <<"error_password">>}),
 
-    ok = emqx_auth_mnesia_cli:remove_user(<<"test_username">>),
+    ok = emqx_auth_mnesia_cli:remove_user({username,?USERNAME}),
     {ok, #{auth_result := success,
-           anonymous := true }} = emqx_access_control:authenticate(User1).
+           anonymous := true }} = emqx_access_control:authenticate(User1),
 
-t_check_as_clientid(_Config) ->
-    clean_all_users(),
-
-    ok = emqx_auth_mnesia_cli:add_user(<<"test_clientid">>, <<"password">>, false),
-    {error, existed} = emqx_auth_mnesia_cli:add_user(<<"test_clientid">>, <<"password">>, false),
-
-    ok = emqx_auth_mnesia_cli:update_user(<<"test_clientid">>, <<"new_password">>, true),
-    {error,noexisted} = emqx_auth_mnesia_cli:update_user(<<"no_existed_user">>, <<"password">>, true),
-
-    [<<"test_clientid">>] = emqx_auth_mnesia_cli:all_users(),
-    [{emqx_user, <<"test_clientid">>, _HashedPass, true}] =
-    emqx_auth_mnesia_cli:lookup_user(<<"test_clientid">>),
-
-    User1 = #{clientid => <<"test_clientid">>,
-              password => <<"new_password">>,
+    User2 = #{clientid => ?CLIENTID,
+              password => ?NPASSWORD,
               zone     => external},
 
-    {ok, #{is_superuser := true, 
-           auth_result := success,
-           anonymous := false}} = emqx_access_control:authenticate(User1),
+    {ok, #{auth_result := success,
+           anonymous := false}} = emqx_access_control:authenticate(User2),
 
-    {error,password_error} = emqx_access_control:authenticate(User1#{password => <<"error_password">>}),
+    {error,password_error} = emqx_access_control:authenticate(User2#{password => <<"error_password">>}),
 
-    ok = emqx_auth_mnesia_cli:remove_user(<<"test_clientid">>),
+    ok = emqx_auth_mnesia_cli:remove_user({clientid,?CLIENTID}),
     {ok, #{auth_result := success,
-           anonymous := true }} = emqx_access_control:authenticate(User1).
+           anonymous := true }} = emqx_access_control:authenticate(User2),
+
+    [] = emqx_auth_mnesia_cli:all_users().
 
-t_rest_api(_Config) ->
+t_auth_clientid_cli(_) ->
     clean_all_users(),
 
-    {ok, Result1} = request_http_rest_list(),
-    [] = get_http_data(Result1),
+    HashType = application:get_env(emqx_auth_mnesia, password_hash, sha256),
 
-    Params = #{<<"login">> => <<"test_username">>, <<"password">> => <<"password">>, <<"is_superuser">> => true},
-    {ok, _} = request_http_rest_add(Params),
+    emqx_auth_mnesia_cli:auth_clientid_cli(["add", ?CLIENTID, ?PASSWORD]),
+    [{_, {clientid, ?CLIENTID}, <<Salt:4/binary, Hash/binary>>, _}] = emqx_auth_mnesia_cli:lookup_user({clientid, ?CLIENTID}),
+    ?assertEqual(Hash, emqx_passwd:hash(HashType, <<Salt/binary, ?PASSWORD/binary>>)),
 
-    Params1 = [
-                #{<<"login">> => <<"test_username">>, <<"password">> => <<"password">>, <<"is_superuser">> => true},
-                #{<<"login">> => <<"test_username/1">>, <<"password">> => <<"password">>, <<"is_superuser">> => error_format},
-                #{<<"login">> => <<"test_username/2">>, <<"password">> => <<"password">>, <<"is_superuser">> => true}
-                ],
-    {ok, Result2} = request_http_rest_add(Params1),
-    #{
-        <<"test_username">> := <<"{error,existed}">>,
-        <<"test_username/1">> := <<"{error,is_superuser}">>,
-        <<"test_username/2">> := <<"ok">>
-        } = get_http_data(Result2),
+    emqx_auth_mnesia_cli:auth_clientid_cli(["update", ?CLIENTID, ?NPASSWORD]),
+    [{_, {clientid, ?CLIENTID}, <<Salt1:4/binary, Hash1/binary>>, _}] = emqx_auth_mnesia_cli:lookup_user({clientid, ?CLIENTID}),
+    ?assertEqual(Hash1, emqx_passwd:hash(HashType, <<Salt1/binary, ?NPASSWORD/binary>>)),
 
-    {ok, Result3} = request_http_rest_lookup(<<"test_username">>),
-    #{<<"login">> := <<"test_username">>, <<"is_superuser">> := true} = get_http_data(Result3),
+    emqx_auth_mnesia_cli:auth_clientid_cli(["del", ?CLIENTID]),
+    ?assertEqual([], emqx_auth_mnesia_cli:lookup_user(?CLIENTID)),
 
-    {ok, _} = request_http_rest_update(<<"test_username">>, <<"new_password">>, error_format),
-    {ok, _} = request_http_rest_update(<<"error_username">>, <<"new_password">>, false),
+    emqx_auth_mnesia_cli:auth_clientid_cli(["add", "user1", "pass1"]),
+    emqx_auth_mnesia_cli:auth_clientid_cli(["add", "user2", "pass2"]),
+    ?assertEqual(2, length(emqx_auth_mnesia_cli:auth_clientid_cli(["list"]))),
 
-    {ok, _} = request_http_rest_update(<<"test_username">>, <<"new_password">>, false),
-    {ok, Result4} = request_http_rest_lookup(<<"test_username">>),
-    #{<<"login">> := <<"test_username">>, <<"is_superuser">> := false} = get_http_data(Result4),
+    emqx_auth_mnesia_cli:auth_clientid_cli(usage).
 
-    User1 = #{username => <<"test_username">>,
-        password => <<"new_password">>,
-        zone     => external},
+t_auth_username_cli(_) ->
+    clean_all_users(),
 
-    {ok, #{is_superuser := false, 
-        auth_result := success,
-        anonymous := false}} = emqx_access_control:authenticate(User1),
+    HashType = application:get_env(emqx_auth_mnesia, password_hash, sha256),
 
-    {ok, _} = request_http_rest_delete(<<"test_username">>),
-    {ok, #{auth_result := success,
-           anonymous := true }} = emqx_access_control:authenticate(User1).
+    emqx_auth_mnesia_cli:auth_username_cli(["add", ?USERNAME, ?PASSWORD]),
+    [{_, {username, ?USERNAME}, <<Salt:4/binary, Hash/binary>>, _}] = emqx_auth_mnesia_cli:lookup_user({username, ?USERNAME}),
+    ?assertEqual(Hash, emqx_passwd:hash(HashType, <<Salt/binary, ?PASSWORD/binary>>)),
 
-t_run_command(_) ->
-    clean_all_users(),
-    ?assertEqual(ok, emqx_ctl:run_command(["mqtt-user", "add", "TestUser", "Password", false])),
-    ?assertMatch([{emqx_user, <<"TestUser">>, _, false}], emqx_auth_mnesia_cli:lookup_user(<<"TestUser">>)),
+    emqx_auth_mnesia_cli:auth_username_cli(["update", ?USERNAME, ?NPASSWORD]),
+    [{_, {username, ?USERNAME}, <<Salt1:4/binary, Hash1/binary>>, _}] = emqx_auth_mnesia_cli:lookup_user({username, ?USERNAME}),
+    ?assertEqual(Hash1, emqx_passwd:hash(HashType, <<Salt1/binary, ?NPASSWORD/binary>>)),
 
-    ?assertEqual(ok, emqx_ctl:run_command(["mqtt-user", "update", "TestUser", "NewPassword", true])),
-    ?assertMatch([{emqx_user, <<"TestUser">>, _, true}], emqx_auth_mnesia_cli:lookup_user(<<"TestUser">>)),
+    emqx_auth_mnesia_cli:auth_username_cli(["del", ?USERNAME]),
+    ?assertEqual([], emqx_auth_mnesia_cli:lookup_user(?USERNAME)),
 
-    ?assertEqual(ok, emqx_ctl:run_command(["mqtt-user", "del", "TestUser"])),
-    ?assertMatch([], emqx_auth_mnesia_cli:lookup_user(<<"TestUser">>)),
+    emqx_auth_mnesia_cli:auth_username_cli(["add", "user1", "pass1"]),
+    emqx_auth_mnesia_cli:auth_username_cli(["add", "user2", "pass2"]),
+    ?assertEqual(2, length(emqx_auth_mnesia_cli:auth_username_cli(["list"]))),
 
-    ?assertEqual(ok, emqx_ctl:run_command(["mqtt-user", "show", "TestUser"])),
-    ?assertEqual(ok, emqx_ctl:run_command(["mqtt-user", "list"])),
-    ?assertEqual(ok, emqx_ctl:run_command(["mqtt-user"])).
+    emqx_auth_mnesia_cli:auth_username_cli(usage).
 
-t_cli(_) ->
-    meck:new(emqx_ctl, [non_strict, passthrough]),
-    meck:expect(emqx_ctl, print, fun(Arg) -> emqx_ctl:format(Arg) end),
-    meck:expect(emqx_ctl, print, fun(Msg, Arg) -> emqx_ctl:format(Msg, Arg) end),
-    meck:expect(emqx_ctl, usage, fun(Usages) -> emqx_ctl:format_usage(Usages) end),
-    meck:expect(emqx_ctl, usage, fun(Cmd, Descr) -> emqx_ctl:format_usage(Cmd, Descr) end),
 
+t_clientid_rest_api(_Config) ->
     clean_all_users(),
 
-    ?assertMatch({match, _}, re:run(emqx_auth_mnesia_cli:auth_cli(["add", "TestUser", "Password", true]), "ok")),
-    ?assertMatch({match, _}, re:run(emqx_auth_mnesia_cli:auth_cli(["add", "TestUser", "Password", true]), "Error")),
+    {ok, Result1} = request_http_rest_list(["auth_clientid"]),
+    [] = get_http_data(Result1),
+
+    Params1 = #{<<"clientid">> => ?CLIENTID, <<"password">> => ?PASSWORD},
+    {ok, _} = request_http_rest_add(["auth_clientid"], Params1),
+
+    Params2 = #{<<"clientid">> => ?CLIENTID, <<"password">> => ?NPASSWORD},
+    {ok, _} = request_http_rest_update(["auth_clientid/" ++ binary_to_list(?CLIENTID)], Params2),
+ 
+    {ok, Result2} = request_http_rest_lookup(["auth_clientid/" ++ binary_to_list(?CLIENTID)]),
+    ?assertMatch(#{<<"clientid">> := ?CLIENTID}, get_http_data(Result2)),
+
+    Params3 = [ #{<<"clientid">> => ?CLIENTID, <<"password">> => ?PASSWORD}
+              , #{<<"clientid">> => <<"clientid1">>, <<"password">> => ?PASSWORD}
+              , #{<<"clientid">> => <<"clientid2">>, <<"password">> => ?PASSWORD}
+              ],
+    {ok, Result3} = request_http_rest_add(["auth_clientid"], Params3),
+    ?assertMatch(#{ ?CLIENTID := <<"{error,existed}">>
+                  , <<"clientid1">> := <<"ok">>
+                  , <<"clientid2">> := <<"ok">>
+                  }, get_http_data(Result3)),
+
+    {ok, Result4} = request_http_rest_list(["auth_clientid"]),
+    ?assertEqual(3, length(get_http_data(Result4))),
+
+    {ok, _} = request_http_rest_delete(["auth_clientid/" ++ binary_to_list(?CLIENTID)]),
+    {ok, Result5} = request_http_rest_lookup(["auth_clientid/" ++ binary_to_list(?CLIENTID)]),
+    ?assertMatch(#{}, get_http_data(Result5)).
+
+t_username_rest_api(_Config) ->
+    clean_all_users(),
+
+    {ok, Result1} = request_http_rest_list(["auth_username"]),
+    [] = get_http_data(Result1),
+
+    Params1 = #{<<"username">> => ?USERNAME, <<"password">> => ?PASSWORD},
+    {ok, _} = request_http_rest_add(["auth_username"], Params1),
 
-    ?assertMatch({match, _}, re:run(emqx_auth_mnesia_cli:auth_cli(["update", "NoExisted", "Password", false]), "Error")),
-    ?assertMatch({match, _}, re:run(emqx_auth_mnesia_cli:auth_cli(["update", "TestUser", "Password", false]), "ok")),
+    Params2 = #{<<"username">> => ?USERNAME, <<"password">> => ?NPASSWORD},
+    {ok, _} = request_http_rest_update(["auth_username/" ++ binary_to_list(?USERNAME)], Params2),
 
-    ?assertMatch(["User(login = <<\"TestUser\">> is_super = false)\n"], emqx_auth_mnesia_cli:auth_cli(["show", "TestUser"])),
-    ?assertMatch(["User(login = <<\"TestUser\">>)\n"], emqx_auth_mnesia_cli:auth_cli(["list"])),
+    {ok, Result2} = request_http_rest_lookup(["auth_username/" ++ binary_to_list(?USERNAME)]),
+    ?assertMatch(#{<<"username">> := ?USERNAME}, get_http_data(Result2)),
 
-    ?assertMatch({match, _}, re:run(emqx_auth_mnesia_cli:auth_cli(["del", "TestUser"]), "ok")),
-    ?assertMatch([], emqx_auth_mnesia_cli:auth_cli(["show", "TestUser"])),
-    ?assertMatch([], emqx_auth_mnesia_cli:auth_cli(["list"])),
+    Params3 = [ #{<<"username">> => ?USERNAME, <<"password">> => ?PASSWORD}
+              , #{<<"username">> => <<"username1">>, <<"password">> => ?PASSWORD}
+              , #{<<"username">> => <<"username2">>, <<"password">> => ?PASSWORD}
+              ],
+    {ok, Result3} = request_http_rest_add(["auth_username"], Params3),
+    ?assertMatch(#{ ?USERNAME := <<"{error,existed}">>
+                  , <<"username1">> := <<"ok">>
+                  , <<"username2">> := <<"ok">>
+                  }, get_http_data(Result3)),
 
-    ?assertMatch({match, _}, re:run(emqx_auth_mnesia_cli:auth_cli([]), "mqtt-user")),
+    {ok, Result4} = request_http_rest_list(["auth_username"]),
+    ?assertEqual(3, length(get_http_data(Result4))),
 
-    meck:unload(emqx_ctl).
+    {ok, _} = request_http_rest_delete(["auth_username/" ++ binary_to_list(?USERNAME)]),
+    {ok, Result5} = request_http_rest_lookup(["auth_username/" ++ binary_to_list(?USERNAME)]),
+    ?assertMatch(#{}, get_http_data(Result5)).
 
 %%------------------------------------------------------------------------------
 %% Helpers
@@ -231,18 +257,17 @@ clean_all_users() ->
 %% HTTP Request
 %%--------------------------------------------------------------------
 
-request_http_rest_list() ->
-    request_api(get, uri(), default_auth_header()).
+request_http_rest_list(Path) ->
+    request_api(get, uri(Path), default_auth_header()).
 
-request_http_rest_lookup(Login) ->
-    request_api(get, uri([Login]), default_auth_header()).
+request_http_rest_lookup(Path) ->
+    request_api(get, uri([Path]), default_auth_header()).
 
-request_http_rest_add(Params) ->
-    request_api(post, uri(), [], default_auth_header(), Params).
+request_http_rest_add(Path, Params) ->
+    request_api(post, uri(Path), [], default_auth_header(), Params).
 
-request_http_rest_update(Login, Password, IsSuperuser) ->
-    Params = #{<<"password">> => Password, <<"is_superuser">> => IsSuperuser},
-    request_api(put, uri([Login]), [], default_auth_header(), Params).
+request_http_rest_update(Path, Params) ->
+    request_api(put, uri([Path]), [], default_auth_header(), Params).
 
 request_http_rest_delete(Login) ->
     request_api(delete, uri([Login]), default_auth_header()).
@@ -250,7 +275,7 @@ request_http_rest_delete(Login) ->
 uri() -> uri([]).
 uri(Parts) when is_list(Parts) ->
     NParts = [b2l(E) || E <- Parts],
-    ?HOST ++ filename:join([?BASE_PATH, ?API_VERSION, "mqtt_user"| NParts]).
+    ?HOST ++ filename:join([?BASE_PATH, ?API_VERSION | NParts]).
 
 %% @private
 b2l(B) when is_binary(B) ->

+ 0 - 111
apps/emqx_auth_username/README.md

@@ -1,111 +0,0 @@
-emqx_auth_username
-==================
-
-EMQ X Authentication with Username and Password
-
-Build
------
-
-```
-make && make tests
-```
-
-Configuration
--------------
-
-etc/emqx_auth_username.conf:
-
-```
-## Password hash.
-##
-## Value: plain | md5 | sha | sha256 
-auth.user.password_hash = sha256
-```
-
-[REST API](https://developer.emqx.io/docs/emq/v3/en/rest.html)
-------------
-
-List all usernames
-```
-# Request
-GET api/v4/auth_username
-
-# Response
-{
-    "code": 0,
-    "data": ["username1"]
-}
-```
-
-Add a username:
-```
-# Request
-POST api/v4/auth_username
-{
-    "username": "some_name",
-    "password": "password"
-}
-
-# Response
-{
-    "code": 0
-}
-```
-
-Update password for a username:
-```
-# Request
-PUT api/v4/auth_username/$NAME
-{
-    "password": "password"
-}
-
-# Response
-{
-    "code", 0
-}
-```
-
-Lookup a username info:
-```
-# Request
-GET api/v4/auth_username/$NAME
-
-# Response
-{
-    "code": 0,
-    "data": {
-        "username": "some_username",
-        "password": "hashed_password"
-    }
-}
-```
-
-Delete a username:
-```
-# Request
-DELETE api/v4/auth_username/$NAME
-
-# Response
-{
-    "code": 0
-}
-```
-
-Load the Plugin
----------------
-
-```
-./bin/emqx_ctl plugins load emqx_auth_username
-```
-
-License
--------
-
-Apache License Version 2.0
-
-Author
-------
-
-EMQ X Team.
-

+ 0 - 1
apps/emqx_auth_username/TODO

@@ -1 +0,0 @@
-Upgrade test cases for 3.0

+ 0 - 14
apps/emqx_auth_username/include/emqx_auth_username.hrl

@@ -1,14 +0,0 @@
--define(APP, emqx_auth_username).
-
--record(auth_metrics, {
-        success = 'client.auth.success',
-        failure = 'client.auth.failure',
-        ignore  = 'client.auth.ignore'
-    }).
-
--define(METRICS(Type), tl(tuple_to_list(#Type{}))).
--define(METRICS(Type, K), #Type{}#Type.K).
-
--define(AUTH_METRICS, ?METRICS(auth_metrics)).
--define(AUTH_METRICS(K), ?METRICS(auth_metrics, K)).
-

+ 0 - 26
apps/emqx_auth_username/priv/emqx_auth_username.schema

@@ -1,26 +0,0 @@
-%%-*- mode: erlang -*-
-%% emqx_auth_username config mapping
-
-{mapping, "auth.user.password_hash", "emqx_auth_username.password_hash", [
-  {default, sha256},
-  {datatype, {enum, [plain, md5, sha, sha256]}}
-]}.
-
-{mapping, "auth.user.$id.username", "emqx_auth_username.userlist", [
-  {datatype, string}
-]}.
-
-{mapping, "auth.user.$id.password", "emqx_auth_username.userlist", [
-  {datatype, string}
-]}.
-
-{translation, "emqx_auth_username.userlist", fun(Conf) ->
-  Userlist = cuttlefish_variable:filter_by_prefix("auth.user", Conf),
-  lists:foldl(
-    fun({["auth", "user", Id, "username"], Username}, AccIn) ->
-        [{Username, cuttlefish:conf_get("auth.user." ++ Id ++ ".password", Conf)} | AccIn];
-       (_, AccIn) ->
-        AccIn
-       end, [], Userlist)
-end}.
-

+ 0 - 1
apps/emqx_auth_username/rebar.config

@@ -1 +0,0 @@
-{deps, []}.

+ 0 - 14
apps/emqx_auth_username/src/emqx_auth_username.app.src

@@ -1,14 +0,0 @@
-{application, emqx_auth_username,
- [{description, "EMQ X Authentication with Username and Password"},
-  {vsn, "5.0.0"}, % strict semver, bump manually!
-  {modules, []},
-  {registered, []},
-  {applications, [kernel,stdlib,emqx_libs]},
-  {mod, {emqx_auth_username_app,[]}},
-  {env, []},
-  {licenses, ["Apache-2.0"]},
-  {maintainers, ["EMQ X Team <contact@emqx.io>"]},
-  {links, [{"Homepage", "https://emqx.io/"},
-           {"Github", "https://github.com/emqx/emqx-auth-username"}
-          ]}
- ]}.

+ 0 - 173
apps/emqx_auth_username/src/emqx_auth_username.erl

@@ -1,173 +0,0 @@
-%%--------------------------------------------------------------------
-%% Copyright (c) 2020 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_auth_username).
-
--include("emqx_auth_username.hrl").
--include_lib("emqx_libs/include/emqx.hrl").
-
-%% CLI callbacks
--export([cli/1]).
-
-%% APIs
--export([ add_user/2
-        , update_password/2
-        , remove_user/1
-        , lookup_user/1
-        , all_users/0
-        ]).
-
--export([unwrap_salt/1]).
-
-%% Auth callbacks
--export([ init/1
-        , register_metrics/0
-        , check/3
-        , description/0
-        ]).
-
--define(TAB, ?MODULE).
-
--record(?TAB, {username, password}).
-
-%%--------------------------------------------------------------------
-%% CLI
-%%--------------------------------------------------------------------
-
-cli(["list"]) ->
-    Usernames = mnesia:dirty_all_keys(?TAB),
-    [emqx_ctl:print("~s~n", [Username]) || Username <- Usernames];
-
-cli(["add", Username, Password]) ->
-    Ok = add_user(iolist_to_binary(Username), iolist_to_binary(Password)),
-    emqx_ctl:print("~p~n", [Ok]);
-
-cli(["update", Username, NewPassword]) ->
-    Ok = update_password(iolist_to_binary(Username), iolist_to_binary(NewPassword)),
-    emqx_ctl:print("~p~n", [Ok]);
-
-cli(["del", Username]) ->
-    emqx_ctl:print("~p~n", [remove_user(iolist_to_binary(Username))]);
-
-cli(_) ->
-    emqx_ctl:usage([{"users list", "List users"},
-                    {"users add <Username> <Password>", "Add User"},
-                    {"users update <Username> <NewPassword>", "Update User"},
-                    {"users del <Username>", "Delete User"}]).
-
-%%--------------------------------------------------------------------
-%% API
-%%--------------------------------------------------------------------
-
-%% @doc Add User
--spec(add_user(binary(), binary()) -> ok | {error, any()}).
-add_user(Username, Password) ->
-    User = #?TAB{username = Username, password = encrypted_data(Password)},
-    ret(mnesia:transaction(fun insert_user/1, [User])).
-
-insert_user(User = #?TAB{username = Username}) ->
-    case mnesia:read(?TAB, Username) of
-        []    -> mnesia:write(User);
-        [_|_] -> mnesia:abort(existed)
-    end.
-
-%% @doc Update User
--spec(update_password(binary(), binary()) -> ok | {error, any()}).
-update_password(Username, NewPassword) ->
-    User = #?TAB{username = Username, password = encrypted_data(NewPassword)},
-    ret(mnesia:transaction(fun do_update_password/1, [User])).
-
-do_update_password(User = #?TAB{username = Username}) ->
-    case mnesia:read(?TAB, Username) of
-        [_|_] -> mnesia:write(User);
-        [] -> mnesia:abort(noexisted)
-    end.
-
-%% @doc Lookup user by username
--spec(lookup_user(binary()) -> list()).
-lookup_user(Username) ->
-    mnesia:dirty_read(?TAB, Username).
-
-%% @doc Remove user
--spec(remove_user(binary()) -> ok | {error, any()}).
-remove_user(Username) ->
-    ret(mnesia:transaction(fun mnesia:delete/1, [{?TAB, Username}])).
-
-ret({atomic, ok})     -> ok;
-ret({aborted, Error}) -> {error, Error}.
-
-%% @doc All usernames
--spec(all_users() -> list()).
-all_users() -> mnesia:dirty_all_keys(?TAB).
-
-unwrap_salt(<<_Salt:4/binary, HashPasswd/binary>>) ->
-    HashPasswd.
-
-%%--------------------------------------------------------------------
-%% Auth callbacks
-%%--------------------------------------------------------------------
-
-init(DefaultUsers) ->
-    ok = ekka_mnesia:create_table(?TAB, [
-            {disc_copies, [node()]},
-            {attributes, record_info(fields, ?TAB)},
-            {storage_properties, [{ets, [{read_concurrency, true}]}]}]),
-    ok = lists:foreach(fun add_default_user/1, DefaultUsers),
-    ok = ekka_mnesia:copy_table(?TAB, disc_copies).
-
-%% @private
-add_default_user({Username, Password}) ->
-    add_user(iolist_to_binary(Username), iolist_to_binary(Password)).
-
--spec(register_metrics() -> ok).
-register_metrics() ->
-    lists:foreach(fun emqx_metrics:ensure/1, ?AUTH_METRICS).
-
-check(#{username := Username, password := Password}, AuthResult, #{hash_type := HashType}) ->
-    case mnesia:dirty_read(?TAB, Username) of
-        [] -> emqx_metrics:inc(?AUTH_METRICS(ignore));
-        [#?TAB{password = <<Salt:4/binary, Hash/binary>>}] ->
-            case Hash =:= hash(Password, Salt, HashType) of
-                true ->
-                    ok = emqx_metrics:inc(?AUTH_METRICS(success)),
-                    {stop, AuthResult#{auth_result => success, anonymous => false}};
-                false ->
-                    ok = emqx_metrics:inc(?AUTH_METRICS(failure)),
-                    {stop, AuthResult#{auth_result => not_authorized, anonymous => false}}
-            end
-    end.
-
-description() ->
-    "Username password Authentication Module".
-
-%%--------------------------------------------------------------------
-%% Internal functions
-%%--------------------------------------------------------------------
-
-encrypted_data(Password) ->
-    HashType = application:get_env(emqx_auth_username, password_hash, sha256),
-    SaltBin = salt(),
-    <<SaltBin/binary, (hash(Password, SaltBin, HashType))/binary>>.
-
-hash(undefined, SaltBin, HashType) ->
-    hash(<<>>, SaltBin, HashType);
-hash(Password, SaltBin, HashType) ->
-    emqx_passwd:hash(HashType, <<SaltBin/binary, Password/binary>>).
-
-salt() ->
-    rand:seed(exsplus, erlang:timestamp()),
-    Salt = rand:uniform(16#ffffffff), <<Salt:32>>.
-

+ 0 - 123
apps/emqx_auth_username/src/emqx_auth_username_api.erl

@@ -1,123 +0,0 @@
-%%--------------------------------------------------------------------
-%% Copyright (c) 2020 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_auth_username_api).
-
--include("emqx_auth_username.hrl").
-
--import(proplists, [get_value/2]).
-
--import(minirest,  [return/0, return/1]).
-
--rest_api(#{name   => list_username,
-            method => 'GET',
-            path   => "/auth_username",
-            func   => list,
-            descr  => "List available username in the cluster"
-           }).
-
--rest_api(#{name   => lookup_username,
-            method => 'GET',
-            path   => "/auth_username/:bin:username",
-            func   => lookup,
-            descr  => "Lookup username in the cluster"
-           }).
-
--rest_api(#{name   => add_username,
-            method => 'POST',
-            path   => "/auth_username",
-            func   => add,
-            descr  => "Add username in the cluster"
-           }).
-
--rest_api(#{name   => update_username,
-            method => 'PUT',
-            path   => "/auth_username/:bin:username",
-            func   => update,
-            descr  => "Update username in the cluster"
-           }).
-
--rest_api(#{name   => delete_username,
-            method => 'DELETE',
-            path   => "/auth_username/:bin:username",
-            func   => delete,
-            descr  => "Delete username in the cluster"
-           }).
-
--export([ list/2
-        , lookup/2
-        , add/2
-        , update/2
-        , delete/2
-        ]).
-
-list(_Bindings, _Params) ->
-    return({ok, emqx_auth_username:all_users()}).
-
-lookup(#{username := Username}, _Params) ->
-    return({ok, format(emqx_auth_username:lookup_user(Username))}).
-
-add(_Bindings, Params) ->
-    Username = get_value(<<"username">>, Params),
-    Password = get_value(<<"password">>, Params),
-    case validate([username, password], [Username, Password]) of
-        ok ->
-            case emqx_auth_username:add_user(Username, Password) of
-                ok  -> return();
-                Err -> return(Err)
-            end;
-        Err -> return(Err)
-    end.
-
-update(#{username := Username}, Params) ->
-    Password = get_value(<<"password">>, Params),
-    case validate([password], [Password]) of
-        ok ->
-            case emqx_auth_username:update_password(Username, Password) of
-                ok  -> return();
-                Err -> return(Err)
-            end;
-        Err -> return(Err)
-    end.
-
-delete(#{username := Username}, _) ->
-    ok = emqx_auth_username:remove_user(Username),
-    return().
-
-%%------------------------------------------------------------------------------
-%% Interval Funcs
-%%------------------------------------------------------------------------------
-
-format([{?APP, Username, Password}]) ->
-    #{username => Username,
-      password => emqx_auth_username:unwrap_salt(Password)}.
-
-validate([], []) ->
-    ok;
-validate([K|Keys], [V|Values]) ->
-   case validation(K, V) of
-       false -> {error, K};
-       true  -> validate(Keys, Values)
-   end.
-
-validation(username, V) when is_binary(V)
-                     andalso byte_size(V) > 0 ->
-    true;
-validation(password, V) when is_binary(V)
-                     andalso byte_size(V) > 0 ->
-    true;
-validation(_, _) ->
-    false.

+ 0 - 49
apps/emqx_auth_username/src/emqx_auth_username_app.erl

@@ -1,49 +0,0 @@
-%%--------------------------------------------------------------------
-%% Copyright (c) 2020 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_auth_username_app).
-
--include("emqx_auth_username.hrl").
-
--behaviour(application).
--behaviour(supervisor).
-
--emqx_plugin(auth).
-
--export([ start/2
-        , stop/1
-        ]).
--export([init/1]).
-
-start(_Type, _Args) ->
-    emqx_ctl:register_command(users, {?APP, cli}, []),
-    ok = emqx_auth_username:register_metrics(),
-    HashType = application:get_env(?APP, password_hash, sha256),
-    Params = #{hash_type => HashType},
-    emqx:hook('client.authenticate', fun emqx_auth_username:check/3, [Params]),
-    DefaultUsers = application:get_env(?APP, userlist, []),
-    ok = emqx_auth_username:init(DefaultUsers),
-    supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-
-stop(_State) ->
-    emqx:unhook('client.authenticate', fun emqx_auth_username:check/3),
-    emqx_ctl:unregister_command(users).
-
-%%--------------------------------------------------------------------
-
-init([]) ->
-    {ok, { {one_for_all, 1, 10}, []} }.
-

+ 0 - 176
apps/emqx_auth_username/test/emqx_auth_username_SUITE.erl

@@ -1,176 +0,0 @@
-%% Copyright (c) 2020 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_auth_username_SUITE).
-
--compile(nowarn_export_all).
--compile(export_all).
-
--include_lib("emqx_libs/include/emqx.hrl").
-
--include_lib("eunit/include/eunit.hrl").
--include_lib("common_test/include/ct.hrl").
-
--define(TAB, emqx_auth_username).
-
-all() ->
-    emqx_ct:all(?MODULE).
-
-init_per_suite(Config) ->
-    emqx_ct_helpers:start_apps([emqx_auth_username], fun set_special_configs/1),
-    Config.
-
-end_per_suite(_Config) ->
-    emqx_ct_helpers:stop_apps([emqx_auth_username]).
-
-set_special_configs(emqx) ->
-    application:set_env(emqx, allow_anonymous, true),
-    application:set_env(emqx, enable_acl_cache, false),
-    LoadedPluginPath = filename:join(["test", "emqx_SUITE_data", "loaded_plugins"]),
-    application:set_env(emqx, plugins_loaded_file,
-                        emqx_ct_helpers:deps_path(emqx, LoadedPluginPath));
-
-set_special_configs(_App) ->
-    ok.
-
-%%------------------------------------------------------------------------------
-%% Testcases
-%%------------------------------------------------------------------------------
-
-t_managing(_Config) ->
-    clean_all_users(),
-
-    ok = emqx_auth_username:add_user(<<"test_username">>, <<"password">>),
-    [{?TAB, <<"test_username">>, _HashedPass}] =
-        emqx_auth_username:lookup_user(<<"test_username">>),
-    User1 = #{username => <<"test_username">>,
-              password => <<"password">>,
-              zone     => external},
-
-    {ok, #{auth_result := success,
-           anonymous := false}} = emqx_access_control:authenticate(User1),
-
-    ok = emqx_auth_username:remove_user(<<"test_username">>),
-    {ok, #{auth_result := success,
-           anonymous := true }} = emqx_access_control:authenticate(User1).
-
-t_rest_api(_Config) ->
-    clean_all_users(),
-
-    Username = <<"username">>,
-    Password = <<"password">>,
-    Password1 = <<"password1">>,
-    User = #{username => Username, zone => external},
-
-    ?assertEqual(return(),
-                 emqx_auth_username_api:add(#{}, rest_params(Username, Password))),
-    ?assertEqual(return({error, existed}),
-                 emqx_auth_username_api:add(#{}, rest_params(Username, Password))),
-    ?assertEqual(return([Username]),
-                 emqx_auth_username_api:list(#{}, [])),
-
-    {ok, #{code := 0, data := Data}} =
-        emqx_auth_username_api:lookup(rest_binding(Username), []),
-    ?assertEqual(true, match_password(maps:get(username, Data),  Password)),
-
-    {ok, _} = emqx_access_control:authenticate(User#{password => Password}),
-
-    ?assertEqual(return(),
-                 emqx_auth_username_api:update(rest_binding(Username), rest_params(Password))),
-    ?assertEqual(return({error, noexisted}),
-                 emqx_auth_username_api:update(#{username => <<"another_user">>}, rest_params(<<"another_passwd">>))),
-
-    {error, _} = emqx_access_control:authenticate(User#{password => Password1}),
-
-    ?assertEqual(return(),
-                 emqx_auth_username_api:delete(rest_binding(Username), [])),
-    {ok, #{auth_result := success,
-           anonymous := true}} = emqx_access_control:authenticate(User#{password => Password}).
-
-t_cli(_Config) ->
-    clean_all_users(),
-
-    emqx_auth_username:cli(["add", "username", "password"]),
-    ?assertEqual(true, match_password(<<"username">>, <<"password">>)),
-
-    emqx_auth_username:cli(["update", "username", "newpassword"]),
-    ?assertEqual(true, match_password(<<"username">>, <<"newpassword">>)),
-
-    emqx_auth_username:cli(["del", "username"]),
-    [] = emqx_auth_username:lookup_user(<<"username">>),
-    emqx_auth_username:cli(["add", "user1", "pass1"]),
-    emqx_auth_username:cli(["add", "user2", "pass2"]),
-    UserList = emqx_auth_username:cli(["list"]),
-    2 = length(UserList),
-    emqx_auth_username:cli(usage).
-
-t_conf_not_override_existed(_) ->
-    clean_all_users(),
-
-    Username = <<"username">>,
-    Password = <<"password">>,
-    NPassword = <<"password1">>,
-    User = #{username => Username, zone => external},
-
-    application:stop(emqx_auth_username),
-    application:set_env(emqx_auth_username, userlist, [{Username, Password}]),
-    application:ensure_all_started(emqx_auth_username),
-
-    {ok, _} = emqx_access_control:authenticate(User#{password => Password}),
-    emqx_auth_username:cli(["update", Username, NPassword]),
-
-    {error, _} = emqx_access_control:authenticate(User#{password => Password}),
-    {ok, _} = emqx_access_control:authenticate(User#{password => NPassword}),
-
-    application:stop(emqx_auth_username),
-    application:ensure_all_started(emqx_auth_username),
-    {ok, _} = emqx_access_control:authenticate(User#{password => NPassword}),
-
-    ?assertEqual(return(),
-                 emqx_auth_username_api:update(rest_binding(Username), rest_params(Password))),
-    application:stop(emqx_auth_username),
-    application:ensure_all_started(emqx_auth_username),
-    {ok, _} = emqx_access_control:authenticate(User#{password => Password}).
-
-%%------------------------------------------------------------------------------
-%% Helpers
-%%------------------------------------------------------------------------------
-
-clean_all_users() ->
-    [ mnesia:dirty_delete({emqx_auth_username, Username})
-      || Username <- mnesia:dirty_all_keys(emqx_auth_username)].
-
-match_password(Username, PlainPassword) ->
-    HashType = application:get_env(emqx_auth_username, password_hash, sha256),
-    [{?TAB, Username, <<Salt:4/binary, Hash/binary>>}] =
-        emqx_auth_username:lookup_user(Username),
-    Hash =:= emqx_passwd:hash(HashType, <<Salt/binary, PlainPassword/binary>>).
-
-rest_params(Passwd) ->
-    [{<<"password">>, Passwd}].
-
-rest_params(Username, Passwd) ->
-    [{<<"username">>, Username},
-     {<<"password">>, Passwd}].
-
-rest_binding(Username) ->
-    #{username => Username}.
-
-return() ->
-    {ok, #{code => 0}}.
-return({error, Err}) ->
-    {ok, #{message => Err}};
-return(Data) ->
-    {ok, #{code => 0, data => Data}}.
-

+ 0 - 2
rebar.config

@@ -119,8 +119,6 @@
     , {emqx_sn, load}
     , {emqx_coap, load}
     , {emqx_stomp, load}
-    , {emqx_auth_clientid, load}
-    , {emqx_auth_username, load}
     , {emqx_auth_http, load}
     , {emqx_auth_mysql, load}
     , {emqx_auth_jwt, load}