diff --git a/apps/emqx/include/emqx.hrl b/apps/emqx/include/emqx.hrl index 63ab13256..550e650a2 100644 --- a/apps/emqx/include/emqx.hrl +++ b/apps/emqx/include/emqx.hrl @@ -147,6 +147,6 @@ }). -record(chain, - { name :: binary() + { name :: atom() , authenticators :: [#authenticator{}] }). \ No newline at end of file diff --git a/apps/emqx/src/emqx_access_control.erl b/apps/emqx/src/emqx_access_control.erl index 7d5b009ba..914651535 100644 --- a/apps/emqx/src/emqx_access_control.erl +++ b/apps/emqx/src/emqx_access_control.erl @@ -29,9 +29,9 @@ -spec(authenticate(emqx_types:clientinfo()) -> {ok, map()} | {ok, map(), binary()} | {continue, map()} | {continue, binary(), map()} | {error, term()}). authenticate(Credential) -> - case run_hooks('client.authenticate', [Credential], {ok, #{superuser => false}}) of + case run_hooks('client.authenticate', [Credential], {ok, #{is_superuser => false}}) of ok -> - {ok, #{superuser => false}}; + {ok, #{is_superuser => false}}; Other -> Other end. diff --git a/apps/emqx/src/emqx_authentication.erl b/apps/emqx/src/emqx_authentication.erl index 2b561d298..8cc8cf2df 100644 --- a/apps/emqx/src/emqx_authentication.erl +++ b/apps/emqx/src/emqx_authentication.erl @@ -80,7 +80,7 @@ -type config() :: #{atom() => term()}. -type state() :: #{atom() => term()}. --type extra() :: #{superuser := boolean(), +-type extra() :: #{is_superuser := boolean(), atom() => term()}. -type user_info() :: #{user_id := binary(), atom() => term()}. @@ -473,7 +473,7 @@ handle_call({update_authenticator, ChainName, AuthenticatorID, Config}, _From, S state = #{version := Version} = ST} = Authenticator -> case AuthenticatorID =:= generate_id(Config) of true -> - Unique = <>, + Unique = unique(ChainName, AuthenticatorID, Version), case Provider:update(Config#{'_unique' => Unique}, ST) of {ok, NewST} -> NewAuthenticator = Authenticator#authenticator{state = switch_version(NewST)}, @@ -575,17 +575,17 @@ split_by_id(ID, AuthenticatorsConfig) -> end. global_chain(mqtt) -> - <<"mqtt:global">>; + 'mqtt:global'; global_chain('mqtt-sn') -> - <<"mqtt-sn:global">>; + 'mqtt-sn:global'; global_chain(coap) -> - <<"coap:global">>; + 'coap:global'; global_chain(lwm2m) -> - <<"lwm2m:global">>; + 'lwm2m:global'; global_chain(stomp) -> - <<"stomp:global">>; + 'stomp:global'; global_chain(_) -> - <<"unknown:global">>. + 'unknown:global'. may_hook(#{hooked := false} = State) -> case lists:any(fun(#chain{authenticators = []}) -> false; @@ -618,7 +618,7 @@ do_create_authenticator(ChainName, AuthenticatorID, #{enable := Enable} = Config undefined -> {error, no_available_provider}; Provider -> - Unique = <>, + Unique = unique(ChainName, AuthenticatorID, ?VER_1), case Provider:create(Config#{'_unique' => Unique}) of {ok, State} -> Authenticator = #authenticator{id = AuthenticatorID, @@ -704,6 +704,10 @@ serialize_authenticator(#authenticator{id = ID, , state => State }. +unique(ChainName, AuthenticatorID, Version) -> + NChainName = atom_to_binary(ChainName), + <>. + switch_version(State = #{version := ?VER_1}) -> State#{version := ?VER_2}; switch_version(State = #{version := ?VER_2}) -> diff --git a/apps/emqx/src/emqx_channel.erl b/apps/emqx/src/emqx_channel.erl index e25a9c8d6..0b1ff7e25 100644 --- a/apps/emqx/src/emqx_channel.erl +++ b/apps/emqx/src/emqx_channel.erl @@ -214,7 +214,7 @@ init(ConnInfo = #{peername := {PeerHost, _Port}, ClientInfo = set_peercert_infos( Peercert, #{zone => Zone, - listener => Listener, + listener => emqx_listeners:listener_id(Type, Listener), protocol => Protocol, peerhost => PeerHost, sockport => SockPort, @@ -223,7 +223,7 @@ init(ConnInfo = #{peername := {PeerHost, _Port}, mountpoint => MountPoint, is_bridge => false, is_superuser => false - }, Zone, Listener), + }, Zone), {NClientInfo, NConnInfo} = take_ws_cookie(ClientInfo, ConnInfo), #channel{conninfo = NConnInfo, clientinfo = NClientInfo, @@ -244,12 +244,12 @@ quota_policy(RawPolicy) -> erlang:trunc(hocon_postprocess:duration(StrWind) / 1000)}} || {Name, [StrCount, StrWind]} <- maps:to_list(RawPolicy)]. -set_peercert_infos(NoSSL, ClientInfo, _, _) +set_peercert_infos(NoSSL, ClientInfo, _) when NoSSL =:= nossl; NoSSL =:= undefined -> ClientInfo#{username => undefined}; -set_peercert_infos(Peercert, ClientInfo, Zone, _Listener) -> +set_peercert_infos(Peercert, ClientInfo, Zone) -> {DN, CN} = {esockd_peercert:subject(Peercert), esockd_peercert:common_name(Peercert)}, PeercetAs = fun(Key) -> @@ -1303,11 +1303,11 @@ do_authenticate(#{auth_method := AuthMethod} = Credential, #channel{clientinfo = case emqx_access_control:authenticate(Credential) of {ok, Result} -> {ok, Properties, - Channel#channel{clientinfo = ClientInfo#{is_superuser => maps:get(superuser, Result, false)}, + Channel#channel{clientinfo = ClientInfo#{is_superuser => maps:get(is_superuser, Result, false)}, auth_cache = #{}}}; {ok, Result, AuthData} -> {ok, Properties#{'Authentication-Data' => AuthData}, - Channel#channel{clientinfo = ClientInfo#{is_superuser => maps:get(superuser, Result, false)}, + Channel#channel{clientinfo = ClientInfo#{is_superuser => maps:get(is_superuser, Result, false)}, auth_cache = #{}}}; {continue, AuthCache} -> {continue, Properties, Channel#channel{auth_cache = AuthCache}}; @@ -1320,8 +1320,8 @@ do_authenticate(#{auth_method := AuthMethod} = Credential, #channel{clientinfo = do_authenticate(Credential, #channel{clientinfo = ClientInfo} = Channel) -> case emqx_access_control:authenticate(Credential) of - {ok, #{superuser := Superuser}} -> - {ok, #{}, Channel#channel{clientinfo = ClientInfo#{is_superuser => Superuser}}}; + {ok, #{is_superuser := IsSuperuser}} -> + {ok, #{}, Channel#channel{clientinfo = ClientInfo#{is_superuser => IsSuperuser}}}; {error, Reason} -> {error, emqx_reason_codes:connack_error(Reason)} end. diff --git a/apps/emqx/test/emqx_authentication_SUITE.erl b/apps/emqx/test/emqx_authentication_SUITE.erl index a940adc88..aa4d55fee 100644 --- a/apps/emqx/test/emqx_authentication_SUITE.erl +++ b/apps/emqx/test/emqx_authentication_SUITE.erl @@ -73,7 +73,7 @@ update(_Config, _State) -> {ok, #{mark => 2}}. authenticate(#{username := <<"good">>}, _State) -> - {ok, #{superuser => true}}; + {ok, #{is_superuser => true}}; authenticate(#{username := _}, _State) -> {error, bad_username_or_password}. @@ -94,7 +94,7 @@ end_per_suite(_) -> t_chain(_) -> % CRUD of authentication chain - ChainName = <<"test">>, + ChainName = 'test', ?assertMatch({ok, []}, ?AUTHN:list_chains()), ?assertMatch({ok, #{name := ChainName, authenticators := []}}, ?AUTHN:create_chain(ChainName)), ?assertEqual({error, {already_exists, {chain, ChainName}}}, ?AUTHN:create_chain(ChainName)), @@ -105,7 +105,7 @@ t_chain(_) -> ok. t_authenticator(_) -> - ChainName = <<"test">>, + ChainName = 'test', AuthenticatorConfig1 = #{mechanism => 'password-based', backend => 'built-in-database', enable => true}, @@ -155,13 +155,13 @@ t_authenticator(_) -> ok. t_authenticate(_) -> - ListenerID = <<"tcp:default">>, + ListenerID = 'tcp:default', ClientInfo = #{zone => default, listener => ListenerID, protocol => mqtt, username => <<"good">>, password => <<"any">>}, - ?assertEqual({ok, #{superuser => false}}, emqx_access_control:authenticate(ClientInfo)), + ?assertEqual({ok, #{is_superuser => false}}, emqx_access_control:authenticate(ClientInfo)), AuthNType = {'password-based', 'built-in-database'}, ?AUTHN:add_provider(AuthNType, ?MODULE), @@ -171,7 +171,7 @@ t_authenticate(_) -> enable => true}, ?AUTHN:create_chain(ListenerID), ?assertMatch({ok, _}, ?AUTHN:create_authenticator(ListenerID, AuthenticatorConfig)), - ?assertEqual({ok, #{superuser => true}}, emqx_access_control:authenticate(ClientInfo)), + ?assertEqual({ok, #{is_superuser => true}}, emqx_access_control:authenticate(ClientInfo)), ?assertEqual({error, bad_username_or_password}, emqx_access_control:authenticate(ClientInfo#{username => <<"bad">>})), ?AUTHN:delete_chain(ListenerID), @@ -186,7 +186,7 @@ t_update_config(_) -> ?AUTHN:add_provider(AuthNType1, ?MODULE), ?AUTHN:add_provider(AuthNType2, ?MODULE), - Global = <<"mqtt:global">>, + Global = 'mqtt:global', AuthenticatorConfig1 = #{mechanism => 'password-based', backend => 'built-in-database', enable => true}, @@ -212,7 +212,7 @@ t_update_config(_) -> ?assertMatch({ok, _}, update_config([authentication], {delete_authenticator, Global, ID1})), ?assertEqual({error, {not_found, {authenticator, ID1}}}, ?AUTHN:lookup_authenticator(Global, ID1)), - ListenerID = <<"tcp:default">>, + ListenerID = 'tcp:default', ConfKeyPath = [listeners, tcp, default, authentication], ?assertMatch({ok, _}, update_config(ConfKeyPath, {create_authenticator, ListenerID, AuthenticatorConfig1})), ?assertMatch({ok, #{id := ID1, state := #{mark := 1}}}, ?AUTHN:lookup_authenticator(ListenerID, ID1)), diff --git a/apps/emqx/test/emqx_channel_SUITE.erl b/apps/emqx/test/emqx_channel_SUITE.erl index 031f89612..775b40ee8 100644 --- a/apps/emqx/test/emqx_channel_SUITE.erl +++ b/apps/emqx/test/emqx_channel_SUITE.erl @@ -144,7 +144,7 @@ init_per_suite(Config) -> %% Access Control Meck ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]), ok = meck:expect(emqx_access_control, authenticate, - fun(_) -> {ok, #{superuser => false}} end), + fun(_) -> {ok, #{is_superuser => false}} end), ok = meck:expect(emqx_access_control, authorize, fun(_, _, _) -> allow end), %% Broker Meck ok = meck:new(emqx_broker, [passthrough, no_history, no_link]), diff --git a/apps/emqx_authn/data/user-credentials.csv b/apps/emqx_authn/data/user-credentials.csv index 0548308b7..cbadaefbc 100644 --- a/apps/emqx_authn/data/user-credentials.csv +++ b/apps/emqx_authn/data/user-credentials.csv @@ -1,3 +1,3 @@ -user_id,password_hash,salt,superuser +user_id,password_hash,salt,is_superuser myuser3,b6c743545a7817ae8c8f624371d5f5f0373234bb0ff36b8ffbf19bce0e06ab75,de1024f462fb83910fd13151bd4bd235,true myuser4,ee68c985a69208b6eda8c6c9b4c7c2d2b15ee2352cdd64a903171710a99182e8,ad773b5be9dd0613fe6c2f4d8c403139,false diff --git a/apps/emqx_authn/data/user-credentials.json b/apps/emqx_authn/data/user-credentials.json index e54501233..94375df22 100644 --- a/apps/emqx_authn/data/user-credentials.json +++ b/apps/emqx_authn/data/user-credentials.json @@ -3,12 +3,12 @@ "user_id":"myuser1", "password_hash":"c5e46903df45e5dc096dc74657610dbee8deaacae656df88a1788f1847390242", "salt": "e378187547bf2d6f0545a3f441aa4d8a", - "superuser": true + "is_superuser": true }, { "user_id":"myuser2", "password_hash":"f4d17f300b11e522fd33f497c11b126ef1ea5149c74d2220f9a16dc876d4567b", "salt": "6d3f9bd5b54d94b98adbcfe10b6d181f", - "superuser": false + "is_superuser": false } ] diff --git a/apps/emqx_authn/include/emqx_authn.hrl b/apps/emqx_authn/include/emqx_authn.hrl index bdf93204a..5eef08012 100644 --- a/apps/emqx_authn/include/emqx_authn.hrl +++ b/apps/emqx_authn/include/emqx_authn.hrl @@ -18,7 +18,7 @@ -define(AUTHN, emqx_authentication). --define(GLOBAL, <<"mqtt:global">>). +-define(GLOBAL, 'mqtt:global'). -define(RE_PLACEHOLDER, "\\$\\{[a-z0-9\\-]+\\}"). diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl index 3303f88ef..5ba1419f0 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/apps/emqx_authn/src/emqx_authn_api.erl @@ -28,8 +28,11 @@ , move/2 , move2/2 , import_users/2 + , import_users2/2 , users/2 , users2/2 + , users3/2 + , users4/2 ]). -define(EXAMPLE_1, #{mechanism => <<"password-based">>, @@ -70,6 +73,7 @@ }, password_hash_field => <<"password_hash">>, salt_field => <<"salt">>, + is_superuser_field => <<"is_superuser">>, password_hash_algorithm => <<"sha256">>, salt_position => <<"prefix">> }). @@ -153,8 +157,11 @@ api_spec() -> , authentication_api4() , move_api2() , import_users_api() + , import_users_api2() , users_api() , users2_api() + , users3_api() + , users4_api() ], definitions()}. authentication_api() -> @@ -166,7 +173,7 @@ authentication_api() -> authentication_api2() -> Metadata = #{ - get => list_authenticator_api_spec(), + get => find_authenticator_api_spec(), put => update_authenticator_api_spec(), delete => delete_authenticator_api_spec() }, @@ -181,7 +188,7 @@ authentication_api3() -> authentication_api4() -> Metadata = #{ - get => list_authenticator_api_spec2(), + get => find_authenticator_api_spec2(), put => update_authenticator_api_spec2(), delete => delete_authenticator_api_spec2() }, @@ -199,6 +206,48 @@ move_api2() -> }, {"/listeners/:listener_id/authentication/:id/move", Metadata, move2}. +import_users_api() -> + Metadata = #{ + post => import_users_api_spec() + }, + {"/authentication/:id/import_users", Metadata, import_users}. + +import_users_api2() -> + Metadata = #{ + post => import_users_api_spec2() + }, + {"/listeners/:listener_id/authentication/:id/import_users", Metadata, import_users2}. + +users_api() -> + Metadata = #{ + post => create_user_api_spec(), + get => list_users_api_spec() + }, + {"/authentication/:id/users", Metadata, users}. + +users2_api() -> + Metadata = #{ + put => update_user_api_spec(), + get => find_user_api_spec(), + delete => delete_user_api_spec() + }, + {"/authentication/:id/users/:user_id", Metadata, users2}. + +users3_api() -> + Metadata = #{ + post => create_user_api_spec2(), + get => list_users_api_spec2() + }, + {"/listeners/:listener_id/authentication/:id/users", Metadata, users3}. + +users4_api() -> + Metadata = #{ + put => update_user_api_spec2(), + get => find_user_api_spec2(), + delete => delete_user_api_spec2() + }, + {"/listeners/:listener_id/authentication/:id/users/:user_id", Metadata, users4}. + create_authenticator_api_spec() -> #{ description => "Create a authenticator for global authentication", @@ -321,14 +370,14 @@ list_authenticators_api_spec2() -> ] }. -list_authenticator_api_spec() -> +find_authenticator_api_spec() -> #{ description => "Get authenticator by id", parameters => [ #{ name => id, in => path, - description => "ID of authenticator", + description => "Authenticator id", schema => #{ type => string }, @@ -370,14 +419,14 @@ list_authenticator_api_spec() -> } }. -list_authenticator_api_spec2() -> - Spec = list_authenticator_api_spec(), +find_authenticator_api_spec2() -> + Spec = find_authenticator_api_spec(), Spec#{ parameters => [ #{ name => listener_id, in => path, - description => "ID of listener", + description => "Listener id", schema => #{ type => string }, @@ -386,7 +435,7 @@ list_authenticator_api_spec2() -> #{ name => id, in => path, - description => "ID of authenticator", + description => "Authenticator id", schema => #{ type => string }, @@ -402,7 +451,7 @@ update_authenticator_api_spec() -> #{ name => id, in => path, - description => "ID of authenticator", + description => "Authenticator id", schema => #{ type => string }, @@ -482,7 +531,7 @@ update_authenticator_api_spec2() -> #{ name => listener_id, in => path, - description => "ID of listener", + description => "Listener id", schema => #{ type => string }, @@ -491,7 +540,7 @@ update_authenticator_api_spec2() -> #{ name => id, in => path, - description => "ID of authenticator", + description => "Authenticator id", schema => #{ type => string }, @@ -507,7 +556,7 @@ delete_authenticator_api_spec() -> #{ name => id, in => path, - description => "ID of authenticator", + description => "Authenticator id", schema => #{ type => string }, @@ -529,7 +578,7 @@ delete_authenticator_api_spec2() -> #{ name => listener_id, in => path, - description => "ID of listener", + description => "Listener id", schema => #{ type => string }, @@ -538,7 +587,7 @@ delete_authenticator_api_spec2() -> #{ name => id, in => path, - description => "ID of authenticator", + description => "Authenticator id", schema => #{ type => string }, @@ -554,7 +603,7 @@ move_authenticator_api_spec() -> #{ name => id, in => path, - description => "ID of authenticator", + description => "Authenticator id", schema => #{ type => string }, @@ -609,7 +658,7 @@ move_authenticator_api_spec2() -> #{ name => listener_id, in => path, - description => "ID of listener", + description => "Listener id", schema => #{ type => string }, @@ -618,7 +667,7 @@ move_authenticator_api_spec2() -> #{ name => id, in => path, - description => "ID of authenticator", + description => "Authenticator id", schema => #{ type => string }, @@ -627,181 +676,117 @@ move_authenticator_api_spec2() -> ] }. -import_users_api() -> - Metadata = #{ - post => #{ - description => "Import users from json/csv file", - parameters => [ - #{ - name => id, - in => path, - description => "ID of authenticator", +import_users_api_spec() -> + #{ + description => "Import users from json/csv file", + parameters => [ + #{ + name => id, + in => path, + description => "Authenticator id", + schema => #{ + type => string + }, + required => true + } + ], + requestBody => #{ + content => #{ + 'application/json' => #{ schema => #{ - type => string - }, - required => true - } - ], - requestBody => #{ - content => #{ - 'application/json' => #{ - schema => #{ - type => object, - required => [filename], - properties => #{ - filename => #{ - type => string - } + type => object, + required => [filename], + properties => #{ + filename => #{ + type => string } } } } - }, - responses => #{ - <<"204">> => #{ - description => <<"No Content">> - }, - <<"400">> => ?ERR_RESPONSE(<<"Bad Request">>), - <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) } + }, + responses => #{ + <<"204">> => #{ + description => <<"No Content">> + }, + <<"400">> => ?ERR_RESPONSE(<<"Bad Request">>), + <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) } - }, - {"/authentication/:id/import_users", Metadata, import_users}. + }. -users_api() -> - Metadata = #{ - post => #{ - description => "Add user", - parameters => [ - #{ - name => id, - in => path, - description => "ID of authenticator", +import_users_api_spec2() -> + Spec = import_users_api_spec(), + Spec#{ + parameters => [ + #{ + name => listener_id, + in => path, + description => "Listener id", + schema => #{ + type => string + }, + required => true + }, + #{ + name => id, + in => path, + description => "Authenticator id", + schema => #{ + type => string + }, + required => true + } + ] + }. + +create_user_api_spec() -> + #{ + description => "Add user", + parameters => [ + #{ + name => id, + in => path, + description => "Authenticator id", + schema => #{ + type => string + }, + required => true + } + ], + requestBody => #{ + content => #{ + 'application/json' => #{ schema => #{ - type => string - }, - required => true + type => object, + required => [user_id, password], + properties => #{ + user_id => #{ + type => string + }, + password => #{ + type => string + }, + is_superuser => #{ + type => boolean, + default => false + } + } + } } - ], - requestBody => #{ + } + }, + responses => #{ + <<"201">> => #{ + description => <<"Created">>, content => #{ 'application/json' => #{ schema => #{ type => object, - required => [user_id, password], properties => #{ user_id => #{ type => string }, - password => #{ - type => string - }, - superuser => #{ - type => boolean, - default => false - } - } - } - } - } - }, - responses => #{ - <<"201">> => #{ - description => <<"Created">>, - content => #{ - 'application/json' => #{ - schema => #{ - type => object, - properties => #{ - user_id => #{ - type => string - }, - superuser => #{ - type => boolean - } - } - } - } - } - }, - <<"400">> => ?ERR_RESPONSE(<<"Bad Request">>), - <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) - } - }, - get => #{ - description => "List users", - parameters => [ - #{ - name => id, - in => path, - description => "ID of authenticator", - schema => #{ - type => string - }, - required => true - } - ], - responses => #{ - <<"200">> => #{ - description => <<"OK">>, - content => #{ - 'application/json' => #{ - schema => #{ - type => array, - items => #{ - type => object, - properties => #{ - user_id => #{ - type => string - }, - superuser => #{ - type => boolean - } - } - } - } - } - } - }, - <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) - } - } - }, - {"/authentication/:id/users", Metadata, users}. - -users2_api() -> - Metadata = #{ - patch => #{ - description => "Update user", - parameters => [ - #{ - name => id, - in => path, - description => "ID of authenticator", - schema => #{ - type => string - }, - required => true - }, - #{ - name => user_id, - in => path, - schema => #{ - type => string - }, - required => true - } - ], - requestBody => #{ - content => #{ - 'application/json' => #{ - schema => #{ - type => object, - properties => #{ - password => #{ - type => string - }, - superuser => #{ + is_superuser => #{ type => boolean } } @@ -809,108 +794,350 @@ users2_api() -> } } }, - responses => #{ - <<"200">> => #{ - description => <<"OK">>, - content => #{ - 'application/json' => #{ - schema => #{ - type => array, - items => #{ - type => object, - properties => #{ - user_id => #{ - type => string - }, - superuser => #{ - type => boolean - } - } - } - } - } - } - }, - <<"400">> => ?ERR_RESPONSE(<<"Bad Request">>), - <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) - } - }, - get => #{ - description => "Get user info", - parameters => [ - #{ - name => id, - in => path, - description => "ID of authenticator", - schema => #{ - type => string - }, - required => true - }, - #{ - name => user_id, - in => path, - schema => #{ - type => string - }, - required => true - } - ], - responses => #{ - <<"200">> => #{ - description => <<"OK">>, - content => #{ - 'application/json' => #{ - schema => #{ - type => array, - items => #{ - type => object, - properties => #{ - user_id => #{ - type => string - }, - superuser => #{ - type => boolean - } - } - } - } - } - } - }, - <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) - } - }, - delete => #{ - description => "Delete user", - parameters => [ - #{ - name => id, - in => path, - description => "ID of authenticator", - schema => #{ - type => string - }, - required => true - }, - #{ - name => user_id, - in => path, - schema => #{ - type => string - }, - required => true - } - ], - responses => #{ - <<"204">> => #{ - description => <<"No Content">> - }, - <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) - } + <<"400">> => ?ERR_RESPONSE(<<"Bad Request">>), + <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) } - }, - {"/authentication/:id/users/:user_id", Metadata, users2}. + }. + +create_user_api_spec2() -> + Spec = create_user_api_spec(), + Spec#{ + parameters => [ + #{ + name => listener_id, + in => path, + description => "Listener id", + schema => #{ + type => string + }, + required => true + }, + #{ + name => id, + in => path, + description => "Authenticator id", + schema => #{ + type => string + }, + required => true + } + ] + }. + +list_users_api_spec() -> + #{ + description => "List users", + parameters => [ + #{ + name => id, + in => path, + description => "Authenticator id", + schema => #{ + type => string + }, + required => true + } + ], + responses => #{ + <<"200">> => #{ + description => <<"OK">>, + content => #{ + 'application/json' => #{ + schema => #{ + type => array, + items => #{ + type => object, + properties => #{ + user_id => #{ + type => string + }, + is_superuser => #{ + type => boolean + } + } + } + } + } + } + }, + <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) + } + }. + +list_users_api_spec2() -> + Spec = list_users_api_spec(), + Spec#{ + parameters => [ + #{ + name => listener_id, + in => path, + description => "Listener id", + schema => #{ + type => string + }, + required => true + }, + #{ + name => id, + in => path, + description => "Authenticator id", + schema => #{ + type => string + }, + required => true + } + ] + }. + +update_user_api_spec() -> + #{ + description => "Update user", + parameters => [ + #{ + name => id, + in => path, + description => "Authenticator id", + schema => #{ + type => string + }, + required => true + }, + #{ + name => user_id, + in => path, + description => "User id", + schema => #{ + type => string + }, + required => true + } + ], + requestBody => #{ + content => #{ + 'application/json' => #{ + schema => #{ + type => object, + properties => #{ + password => #{ + type => string + }, + is_superuser => #{ + type => boolean + } + } + } + } + } + }, + responses => #{ + <<"200">> => #{ + description => <<"OK">>, + content => #{ + 'application/json' => #{ + schema => #{ + type => array, + items => #{ + type => object, + properties => #{ + user_id => #{ + type => string + }, + is_superuser => #{ + type => boolean + } + } + } + } + } + } + }, + <<"400">> => ?ERR_RESPONSE(<<"Bad Request">>), + <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) + } + }. + +update_user_api_spec2() -> + Spec = update_user_api_spec(), + Spec#{ + parameters => [ + #{ + name => listener_id, + in => path, + description => "Listener id", + schema => #{ + type => string + }, + required => true + }, + #{ + name => id, + in => path, + description => "Authenticator id", + schema => #{ + type => string + }, + required => true + }, + #{ + name => user_id, + in => path, + description => "User id", + schema => #{ + type => string + }, + required => true + } + ] + }. + +find_user_api_spec() -> + #{ + description => "Get user info", + parameters => [ + #{ + name => id, + in => path, + description => "Authenticator id", + schema => #{ + type => string + }, + required => true + }, + #{ + name => user_id, + in => path, + description => "User id", + schema => #{ + type => string + }, + required => true + } + ], + responses => #{ + <<"200">> => #{ + description => <<"OK">>, + content => #{ + 'application/json' => #{ + schema => #{ + type => array, + items => #{ + type => object, + properties => #{ + user_id => #{ + type => string + }, + is_superuser => #{ + type => boolean + } + } + } + } + } + } + }, + <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) + } + }. + +find_user_api_spec2() -> + Spec = find_user_api_spec(), + Spec#{ + parameters => [ + #{ + name => listener_id, + in => path, + description => "Listener id", + schema => #{ + type => string + }, + required => true + }, + #{ + name => id, + in => path, + description => "Authenticator id", + schema => #{ + type => string + }, + required => true + }, + #{ + name => user_id, + in => path, + description => "User id", + schema => #{ + type => string + }, + required => true + } + ] + }. + +delete_user_api_spec() -> + #{ + description => "Delete user", + parameters => [ + #{ + name => id, + in => path, + description => "Authenticator id", + schema => #{ + type => string + }, + required => true + }, + #{ + name => user_id, + in => path, + description => "User id", + schema => #{ + type => string + }, + required => true + } + ], + responses => #{ + <<"204">> => #{ + description => <<"No Content">> + }, + <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) + } + }. + +delete_user_api_spec2() -> + Spec = delete_user_api_spec(), + Spec#{ + parameters => [ + #{ + name => listener_id, + in => path, + description => "Listener id", + schema => #{ + type => string + }, + required => true + }, + #{ + name => id, + in => path, + description => "Authenticator id", + schema => #{ + type => string + }, + required => true + }, + #{ + name => user_id, + in => path, + description => "User id", + schema => #{ + type => string + }, + required => true + } + ] + }. + definitions() -> AuthenticatorConfigDef = #{ @@ -1172,6 +1399,10 @@ definitions() -> type => string, example => <<"salt">> }, + is_superuser_field => #{ + type => string, + example => <<"is_superuser">> + }, password_hash_algorithm => #{ type => string, enum => [<<"plain">>, <<"md5">>, <<"sha">>, <<"sha256">>, <<"sha512">>, <<"bcrypt">>], @@ -1536,71 +1767,54 @@ move2(post, #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}, b move2(post, #{bindings := #{listener_id := _, id := _}, body := _}) -> serialize_error({missing_parameter, position}). -import_users(post, #{bindings := #{id := AuthenticatorID}, body := Body}) -> - case Body of - #{<<"filename">> := Filename} -> - case ?AUTHN:import_users(?GLOBAL, AuthenticatorID, Filename) of - ok -> {204}; - {error, Reason} -> serialize_error(Reason) - end; - _ -> - serialize_error({missing_parameter, filename}) - end. +import_users(post, #{bindings := #{id := AuthenticatorID}, body := #{<<"filename">> := Filename}}) -> + case ?AUTHN:import_users(?GLOBAL, AuthenticatorID, Filename) of + ok -> {204}; + {error, Reason} -> serialize_error(Reason) + end; +import_users(post, #{bindings := #{id := _}, body := _}) -> + serialize_error({missing_parameter, filename}). + +import_users2(post, #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}, body := #{<<"filename">> := Filename}}) -> + case ?AUTHN:import_users(ListenerID, AuthenticatorID, Filename) of + ok -> {204}; + {error, Reason} -> serialize_error(Reason) + end; +import_users2(post, #{bindings := #{listener_id := _, id := _}, body := _}) -> + serialize_error({missing_parameter, filename}). users(post, #{bindings := #{id := AuthenticatorID}, body := UserInfo}) -> - case UserInfo of - #{ <<"user_id">> := UserID, <<"password">> := Password} -> - Superuser = maps:get(<<"superuser">>, UserInfo, false), - case ?AUTHN:add_user(?GLOBAL, AuthenticatorID, #{ user_id => UserID - , password => Password - , superuser => Superuser}) of - {ok, User} -> - {201, User}; - {error, Reason} -> - serialize_error(Reason) - end; - #{<<"user_id">> := _} -> - serialize_error({missing_parameter, password}); - _ -> - serialize_error({missing_parameter, user_id}) - end; + add_user(?GLOBAL, AuthenticatorID, UserInfo); users(get, #{bindings := #{id := AuthenticatorID}}) -> - case ?AUTHN:list_users(?GLOBAL, AuthenticatorID) of - {ok, Users} -> - {200, Users}; - {error, Reason} -> - serialize_error(Reason) - end. + list_users(?GLOBAL, AuthenticatorID). -users2(patch, #{bindings := #{id := AuthenticatorID, - user_id := UserID}, - body := UserInfo}) -> - NUserInfo = maps:with([<<"password">>, <<"superuser">>], UserInfo), - case NUserInfo =:= #{} of - true -> - serialize_error({missing_parameter, password}); - false -> - case ?AUTHN:update_user(?GLOBAL, AuthenticatorID, UserID, UserInfo) of - {ok, User} -> - {200, User}; - {error, Reason} -> - serialize_error(Reason) - end - end; +users2(put, #{bindings := #{id := AuthenticatorID, + user_id := UserID}, body := UserInfo}) -> + update_user(?GLOBAL, AuthenticatorID, UserID, UserInfo); users2(get, #{bindings := #{id := AuthenticatorID, user_id := UserID}}) -> - case ?AUTHN:lookup_user(?GLOBAL, AuthenticatorID, UserID) of - {ok, User} -> - {200, User}; - {error, Reason} -> - serialize_error(Reason) - end; + find_user(?GLOBAL, AuthenticatorID, UserID); users2(delete, #{bindings := #{id := AuthenticatorID, user_id := UserID}}) -> - case ?AUTHN:delete_user(?GLOBAL, AuthenticatorID, UserID) of - ok -> - {204}; - {error, Reason} -> - serialize_error(Reason) - end. + delete_user(?GLOBAL, AuthenticatorID, UserID). + +users3(post, #{bindings := #{listener_id := ListenerID, + id := AuthenticatorID}, body := UserInfo}) -> + add_user(ListenerID, AuthenticatorID, UserInfo); +users3(get, #{bindings := #{listener_id := ListenerID, + id := AuthenticatorID}}) -> + list_users(ListenerID, AuthenticatorID). + +users4(put, #{bindings := #{listener_id := ListenerID, + id := AuthenticatorID, + user_id := UserID}, body := UserInfo}) -> + update_user(ListenerID, AuthenticatorID, UserID, UserInfo); +users4(get, #{bindings := #{listener_id := ListenerID, + id := AuthenticatorID, + user_id := UserID}}) -> + find_user(ListenerID, AuthenticatorID, UserID); +users4(delete, #{bindings := #{listener_id := ListenerID, + id := AuthenticatorID, + user_id := UserID}}) -> + delete_user(ListenerID, AuthenticatorID, UserID). %%------------------------------------------------------------------------------ %% Internal functions @@ -1615,7 +1829,8 @@ find_listener(ListenerID) -> {ok, {Type, Name}} end. -create_authenticator(ConfKeyPath, ChainName, Config) -> +create_authenticator(ConfKeyPath, ChainName0, Config) -> + ChainName = to_atom(ChainName0), case update_config(ConfKeyPath, {create_authenticator, ChainName, Config}) of {ok, #{post_config_update := #{?AUTHN := #{id := ID}}, raw_config := AuthenticatorsConfig}} -> @@ -1640,7 +1855,8 @@ list_authenticator(ConfKeyPath, AuthenticatorID) -> serialize_error(Reason) end. -update_authenticator(ConfKeyPath, ChainName, AuthenticatorID, Config) -> +update_authenticator(ConfKeyPath, ChainName0, AuthenticatorID, Config) -> + ChainName = to_atom(ChainName0), case update_config(ConfKeyPath, {update_authenticator, ChainName, AuthenticatorID, Config}) of {ok, #{post_config_update := #{?AUTHN := #{id := ID}}, @@ -1651,7 +1867,8 @@ update_authenticator(ConfKeyPath, ChainName, AuthenticatorID, Config) -> serialize_error(Reason) end. -delete_authenticator(ConfKeyPath, ChainName, AuthenticatorID) -> +delete_authenticator(ConfKeyPath, ChainName0, AuthenticatorID) -> + ChainName = to_atom(ChainName0), case update_config(ConfKeyPath, {delete_authenticator, ChainName, AuthenticatorID}) of {ok, _} -> {204}; @@ -1659,7 +1876,8 @@ delete_authenticator(ConfKeyPath, ChainName, AuthenticatorID) -> serialize_error(Reason) end. -move_authenitcator(ConfKeyPath, ChainName, AuthenticatorID, Position) -> +move_authenitcator(ConfKeyPath, ChainName0, AuthenticatorID, Position) -> + ChainName = to_atom(ChainName0), case update_config(ConfKeyPath, {move_authenticator, ChainName, AuthenticatorID, Position}) of {ok, _} -> {204}; @@ -1667,6 +1885,63 @@ move_authenitcator(ConfKeyPath, ChainName, AuthenticatorID, Position) -> serialize_error(Reason) end. +add_user(ChainName0, AuthenticatorID, #{<<"user_id">> := UserID, <<"password">> := Password} = UserInfo) -> + ChainName = to_atom(ChainName0), + IsSuperuser = maps:get(<<"is_superuser">>, UserInfo, false), + case ?AUTHN:add_user(ChainName, AuthenticatorID, #{ user_id => UserID + , password => Password + , is_superuser => IsSuperuser}) of + {ok, User} -> + {201, User}; + {error, Reason} -> + serialize_error(Reason) + end; +add_user(_, _, #{<<"user_id">> := _}) -> + serialize_error({missing_parameter, password}); +add_user(_, _, _) -> + serialize_error({missing_parameter, user_id}). + +update_user(ChainName0, AuthenticatorID, UserID, UserInfo) -> + ChainName = to_atom(ChainName0), + case maps:with([<<"password">>, <<"is_superuser">>], UserInfo) =:= #{} of + true -> + serialize_error({missing_parameter, password}); + false -> + case ?AUTHN:update_user(ChainName, AuthenticatorID, UserID, UserInfo) of + {ok, User} -> + {200, User}; + {error, Reason} -> + serialize_error(Reason) + end + end. + +find_user(ChainName0, AuthenticatorID, UserID) -> + ChainName = to_atom(ChainName0), + case ?AUTHN:lookup_user(ChainName, AuthenticatorID, UserID) of + {ok, User} -> + {200, User}; + {error, Reason} -> + serialize_error(Reason) + end. + +delete_user(ChainName0, AuthenticatorID, UserID) -> + ChainName = to_atom(ChainName0), + case ?AUTHN:delete_user(ChainName, AuthenticatorID, UserID) of + ok -> + {204}; + {error, Reason} -> + serialize_error(Reason) + end. + +list_users(ChainName0, AuthenticatorID) -> + ChainName = to_atom(ChainName0), + case ?AUTHN:list_users(ChainName, AuthenticatorID) of + {ok, Users} -> + {200, Users}; + {error, Reason} -> + serialize_error(Reason) + end. + update_config(Path, ConfigRequest) -> emqx:update_config(Path, ConfigRequest, #{rawconf_with_defaults => true}). @@ -1692,9 +1967,11 @@ serialize_error({not_found, {authenticator, ID}}) -> serialize_error({not_found, {listener, ID}}) -> {404, #{code => <<"NOT_FOUND">>, message => list_to_binary(io_lib:format("Listener '~s' does not exist", [ID]))}}; -serialize_error(name_has_be_used) -> +serialize_error({already_exists, {authenticator, ID}}) -> {409, #{code => <<"ALREADY_EXISTS">>, - message => <<"Name has be used">>}}; + message => list_to_binary( + io_lib:format("Authenticator '~s' already exist", [ID]) + )}}; serialize_error({missing_parameter, Name}) -> {400, #{code => <<"MISSING_PARAMETER">>, message => list_to_binary( @@ -1707,9 +1984,14 @@ serialize_error({invalid_parameter, Name}) -> )}}; serialize_error(Reason) -> {400, #{code => <<"BAD_REQUEST">>, - message => list_to_binary(io_lib:format("Todo: ~p", [Reason]))}}. + message => list_to_binary(io_lib:format("~p", [Reason]))}}. to_list(M) when is_map(M) -> [M]; to_list(L) when is_list(L) -> - L. \ No newline at end of file + L. + +to_atom(B) when is_binary(B) -> + binary_to_atom(B); +to_atom(A) when is_atom(A) -> + A. \ No newline at end of file diff --git a/apps/emqx_authn/src/emqx_authn_app.erl b/apps/emqx_authn/src/emqx_authn_app.erl index 58470289a..016decdd2 100644 --- a/apps/emqx_authn/src/emqx_authn_app.erl +++ b/apps/emqx_authn/src/emqx_authn_app.erl @@ -53,7 +53,7 @@ remove_providers() -> initialize() -> ?AUTHN:initialize_authentication(?GLOBAL, emqx:get_raw_config([authentication], [])), lists:foreach(fun({ListenerID, ListenerConfig}) -> - ?AUTHN:initialize_authentication(atom_to_binary(ListenerID), maps:get(authentication, ListenerConfig, [])) + ?AUTHN:initialize_authentication(ListenerID, maps:get(authentication, ListenerConfig, [])) end, emqx_listeners:list()), ok. diff --git a/apps/emqx_authn/src/enhanced_authn/emqx_enhanced_authn_scram_mnesia.erl b/apps/emqx_authn/src/enhanced_authn/emqx_enhanced_authn_scram_mnesia.erl index aa21c0484..e0f37a50d 100644 --- a/apps/emqx_authn/src/enhanced_authn/emqx_enhanced_authn_scram_mnesia.erl +++ b/apps/emqx_authn/src/enhanced_authn/emqx_enhanced_authn_scram_mnesia.erl @@ -53,7 +53,7 @@ , stored_key , server_key , salt - , superuser + , is_superuser }). %%------------------------------------------------------------------------------ @@ -147,9 +147,9 @@ add_user(#{user_id := UserID, fun() -> case mnesia:read(?TAB, {UserGroup, UserID}, write) of [] -> - Superuser = maps:get(superuser, UserInfo, false), - add_user(UserID, Password, Superuser, State), - {ok, #{user_id => UserID, superuser => Superuser}}; + IsSuperuser = maps:get(is_superuser, UserInfo, false), + add_user(UserID, Password, IsSuperuser, State), + {ok, #{user_id => UserID, is_superuser => IsSuperuser}}; [_] -> {error, already_exist} end @@ -173,8 +173,8 @@ update_user(UserID, User, case mnesia:read(?TAB, {UserGroup, UserID}, write) of [] -> {error, not_found}; - [#user_info{superuser = Superuser} = UserInfo] -> - UserInfo1 = UserInfo#user_info{superuser = maps:get(superuser, User, Superuser)}, + [#user_info{is_superuser = IsSuperuser} = UserInfo] -> + UserInfo1 = UserInfo#user_info{is_superuser = maps:get(is_superuser, User, IsSuperuser)}, UserInfo2 = case maps:get(password, User, undefined) of undefined -> UserInfo1; @@ -229,36 +229,36 @@ check_client_first_message(Bin, _Cache, #{iteration_count := IterationCount} = S {error, not_authorized} end. -check_client_final_message(Bin, #{superuser := Superuser} = Cache, #{algorithm := Alg}) -> +check_client_final_message(Bin, #{is_superuser := IsSuperuser} = Cache, #{algorithm := Alg}) -> case esasl_scram:check_client_final_message( Bin, Cache#{algorithm => Alg} ) of {ok, ServerFinalMessage} -> - {ok, #{superuser => Superuser}, ServerFinalMessage}; + {ok, #{is_superuser => IsSuperuser}, ServerFinalMessage}; {error, _Reason} -> {error, not_authorized} end. -add_user(UserID, Password, Superuser, State) -> +add_user(UserID, Password, IsSuperuser, State) -> {StoredKey, ServerKey, Salt} = esasl_scram:generate_authentication_info(Password, State), - UserInfo = #user_info{user_id = UserID, - stored_key = StoredKey, - server_key = ServerKey, - salt = Salt, - superuser = Superuser}, + UserInfo = #user_info{user_id = UserID, + stored_key = StoredKey, + server_key = ServerKey, + salt = Salt, + is_superuser = IsSuperuser}, mnesia:write(?TAB, UserInfo, write). retrieve(UserID, #{user_group := UserGroup}) -> case mnesia:dirty_read(?TAB, {UserGroup, UserID}) of - [#user_info{stored_key = StoredKey, - server_key = ServerKey, - salt = Salt, - superuser = Superuser}] -> + [#user_info{stored_key = StoredKey, + server_key = ServerKey, + salt = Salt, + is_superuser = IsSuperuser}] -> {ok, #{stored_key => StoredKey, server_key => ServerKey, salt => Salt, - superuser => Superuser}}; + is_superuser => IsSuperuser}}; [] -> {error, not_found} end. @@ -273,5 +273,5 @@ trans(Fun, Args) -> {aborted, Reason} -> {error, Reason} end. -serialize_user_info(#user_info{user_id = {_, UserID}, superuser = Superuser}) -> - #{user_id => UserID, superuser => Superuser}. +serialize_user_info(#user_info{user_id = {_, UserID}, is_superuser = IsSuperuser}) -> + #{user_id => UserID, is_superuser => IsSuperuser}. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl index 19417218d..5495b139a 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl @@ -161,16 +161,16 @@ authenticate(Credential, #{'_unique' := Unique, try Request = generate_request(Credential, State), case emqx_resource:query(Unique, {Method, Request, RequestTimeout}) of - {ok, 204, _Headers} -> {ok, #{superuser => false}}; + {ok, 204, _Headers} -> {ok, #{is_superuser => false}}; {ok, 200, Headers, Body} -> ContentType = proplists:get_value(<<"content-type">>, Headers, <<"application/json">>), case safely_parse_body(ContentType, Body) of {ok, NBody} -> %% TODO: Return by user property - {ok, #{superuser => maps:get(<<"superuser">>, NBody, false), + {ok, #{is_superuser => maps:get(<<"is_superuser">>, NBody, false), user_property => NBody}}; {error, _Reason} -> - {ok, #{superuser => false}} + {ok, #{is_superuser => false}} end; {error, _Reason} -> ignore diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl index e55b58795..774d75157 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl @@ -249,7 +249,7 @@ verify(JWS, [JWK | More], VerifyClaims) -> Claims = emqx_json:decode(Payload, [return_maps]), case verify_claims(Claims, VerifyClaims) of ok -> - {ok, #{superuser => maps:get(<<"superuser">>, Claims, false)}}; + {ok, #{is_superuser => maps:get(<<"is_superuser">>, Claims, false)}}; {error, Reason} -> {error, Reason} end; diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl index f41edab8b..b69d613f8 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl @@ -51,7 +51,7 @@ { user_id :: {user_group(), user_id()} , password_hash :: binary() , salt :: binary() - , superuser :: boolean() + , is_superuser :: boolean() }). -reflect_type([ user_id_type/0 ]). @@ -158,13 +158,13 @@ authenticate(#{password := Password} = Credential, case mnesia:dirty_read(?TAB, {UserGroup, UserID}) of [] -> ignore; - [#user_info{password_hash = PasswordHash, salt = Salt0, superuser = Superuser}] -> + [#user_info{password_hash = PasswordHash, salt = Salt0, is_superuser = IsSuperuser}] -> Salt = case Algorithm of bcrypt -> PasswordHash; _ -> Salt0 end, case PasswordHash =:= hash(Algorithm, Password, Salt) of - true -> {ok, #{superuser => Superuser}}; + true -> {ok, #{is_superuser => IsSuperuser}}; false -> {error, bad_username_or_password} end end. @@ -197,9 +197,9 @@ add_user(#{user_id := UserID, case mnesia:read(?TAB, {UserGroup, UserID}, write) of [] -> {PasswordHash, Salt} = hash(Password, State), - Superuser = maps:get(superuser, UserInfo, false), - insert_user(UserGroup, UserID, PasswordHash, Salt, Superuser), - {ok, #{user_id => UserID, superuser => Superuser}}; + IsSuperuser = maps:get(is_superuser, UserInfo, false), + insert_user(UserGroup, UserID, PasswordHash, Salt, IsSuperuser), + {ok, #{user_id => UserID, is_superuser => IsSuperuser}}; [_] -> {error, already_exist} end @@ -225,8 +225,8 @@ update_user(UserID, UserInfo, {error, not_found}; [#user_info{ password_hash = PasswordHash , salt = Salt - , superuser = Superuser}] -> - NSuperuser = maps:get(superuser, UserInfo, Superuser), + , is_superuser = IsSuperuser}] -> + NSuperuser = maps:get(is_superuser, UserInfo, IsSuperuser), {NPasswordHash, NSalt} = case maps:get(password, UserInfo, undefined) of undefined -> {PasswordHash, Salt}; @@ -234,7 +234,7 @@ update_user(UserID, UserInfo, hash(Password, State) end, insert_user(UserGroup, UserID, NPasswordHash, NSalt, NSuperuser), - {ok, #{user_id => UserID, superuser => NSuperuser}} + {ok, #{user_id => UserID, is_superuser => NSuperuser}} end end). @@ -290,8 +290,8 @@ import(UserGroup, [#{<<"user_id">> := UserID, <<"password_hash">> := PasswordHash} = UserInfo | More]) when is_binary(UserID) andalso is_binary(PasswordHash) -> Salt = maps:get(<<"salt">>, UserInfo, <<>>), - Superuser = maps:get(<<"superuser">>, UserInfo, false), - insert_user(UserGroup, UserID, PasswordHash, Salt, Superuser), + IsSuperuser = maps:get(<<"is_superuser">>, UserInfo, false), + insert_user(UserGroup, UserID, PasswordHash, Salt, IsSuperuser), import(UserGroup, More); import(_UserGroup, [_ | _More]) -> {error, bad_format}. @@ -305,8 +305,8 @@ import(UserGroup, File, Seq) -> {ok, #{user_id := UserID, password_hash := PasswordHash} = UserInfo} -> Salt = maps:get(salt, UserInfo, <<>>), - Superuser = maps:get(superuser, UserInfo, false), - insert_user(UserGroup, UserID, PasswordHash, Salt, Superuser), + IsSuperuser = maps:get(is_superuser, UserInfo, false), + insert_user(UserGroup, UserID, PasswordHash, Salt, IsSuperuser), import(UserGroup, File, Seq); {error, Reason} -> {error, Reason} @@ -341,10 +341,10 @@ get_user_info_by_seq([PasswordHash | More1], [<<"password_hash">> | More2], Acc) get_user_info_by_seq(More1, More2, Acc#{password_hash => PasswordHash}); get_user_info_by_seq([Salt | More1], [<<"salt">> | More2], Acc) -> get_user_info_by_seq(More1, More2, Acc#{salt => Salt}); -get_user_info_by_seq([<<"true">> | More1], [<<"superuser">> | More2], Acc) -> - get_user_info_by_seq(More1, More2, Acc#{superuser => true}); -get_user_info_by_seq([<<"false">> | More1], [<<"superuser">> | More2], Acc) -> - get_user_info_by_seq(More1, More2, Acc#{superuser => false}); +get_user_info_by_seq([<<"true">> | More1], [<<"is_superuser">> | More2], Acc) -> + get_user_info_by_seq(More1, More2, Acc#{is_superuser => true}); +get_user_info_by_seq([<<"false">> | More1], [<<"is_superuser">> | More2], Acc) -> + get_user_info_by_seq(More1, More2, Acc#{is_superuser => false}); get_user_info_by_seq(_, _, _) -> {error, bad_format}. @@ -368,11 +368,11 @@ hash(Password, #{password_hash_algorithm := Algorithm} = State) -> PasswordHash = hash(Algorithm, Password, Salt), {PasswordHash, Salt}. -insert_user(UserGroup, UserID, PasswordHash, Salt, Superuser) -> +insert_user(UserGroup, UserID, PasswordHash, Salt, IsSuperuser) -> UserInfo = #user_info{user_id = {UserGroup, UserID}, password_hash = PasswordHash, salt = Salt, - superuser = Superuser}, + is_superuser = IsSuperuser}, mnesia:write(?TAB, UserInfo, write). delete_user2(UserInfo) -> @@ -400,5 +400,5 @@ to_binary(B) when is_binary(B) -> to_binary(L) when is_list(L) -> iolist_to_binary(L). -serialize_user_info(#user_info{user_id = {_, UserID}, superuser = Superuser}) -> - #{user_id => UserID, superuser => Superuser}. +serialize_user_info(#user_info{user_id = {_, UserID}, is_superuser = IsSuperuser}) -> + #{user_id => UserID, is_superuser => IsSuperuser}. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl index f35be985a..9d77c673c 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl @@ -64,6 +64,7 @@ common_fields() -> , {selector, fun selector/1} , {password_hash_field, fun password_hash_field/1} , {salt_field, fun salt_field/1} + , {is_superuser_field, fun is_superuser_field/1} , {password_hash_algorithm, fun password_hash_algorithm/1} , {salt_position, fun salt_position/1} ] ++ emqx_authn_schema:common_fields(). @@ -84,6 +85,10 @@ salt_field(type) -> binary(); salt_field(nullable) -> true; salt_field(_) -> undefined. +is_superuser_field(type) -> binary(); +is_superuser_field(nullable) -> true; +is_superuser_field(_) -> undefined. + password_hash_algorithm(type) -> {enum, [plain, md5, sha, sha256, sha512, bcrypt]}; password_hash_algorithm(default) -> sha256; password_hash_algorithm(_) -> undefined. @@ -109,6 +114,7 @@ create(#{ selector := Selector State = maps:with([ collection , password_hash_field , salt_field + , is_superuser_field , password_hash_algorithm , salt_position , '_unique'], Config), @@ -149,7 +155,7 @@ authenticate(#{password := Password} = Credential, Doc -> case check_password(Password, Doc, State) of ok -> - {ok, #{superuser => superuser(Doc, State)}}; + {ok, #{is_superuser => is_superuser(Doc, State)}}; {error, {cannot_find_password_hash_field, PasswordHashField}} -> ?LOG(error, "['~s'] Can't find password hash field: ~s", [Unique, PasswordHashField]), {error, bad_username_or_password}; @@ -230,9 +236,9 @@ check_password(Password, end end. -superuser(Doc, #{superuser_field := SuperuserField}) -> - maps:get(SuperuserField, Doc, false); -superuser(_, _) -> +is_superuser(Doc, #{is_superuser_field := IsSuperuserField}) -> + maps:get(IsSuperuserField, Doc, false); +is_superuser(_, _) -> false. hash(Algorithm, Password, Salt, prefix) -> diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl index 67ccbf7ae..991bb6aee 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl @@ -123,7 +123,7 @@ authenticate(#{password := Password} = Credential, Selected = maps:from_list(lists:zip(Columns, Rows)), case check_password(Password, Selected, State) of ok -> - {ok, #{superuser => maps:get(<<"superuser">>, Selected, false)}}; + {ok, #{is_superuser => maps:get(<<"is_superuser">>, Selected, false)}}; {error, Reason} -> {error, Reason} end; diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl index 7676f338d..c497074de 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl @@ -113,7 +113,7 @@ authenticate(#{password := Password} = Credential, Selected = maps:from_list(lists:zip(NColumns, Rows)), case check_password(Password, Selected, State) of ok -> - {ok, #{superuser => maps:get(<<"superuser">>, Selected, false)}}; + {ok, #{is_superuser => maps:get(<<"is_superuser">>, Selected, false)}}; {error, Reason} -> {error, Reason} end; diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl index 18840fdea..949aeeaea 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl @@ -135,7 +135,7 @@ authenticate(#{password := Password} = Credential, Selected = merge(Fields, Values), case check_password(Password, Selected, State) of ok -> - {ok, #{superuser => maps:get("superuser", Selected, false)}}; + {ok, #{is_superuser => maps:get("is_superuser", Selected, false)}}; {error, Reason} -> {error, Reason} end; @@ -180,7 +180,7 @@ check_fields(["password_hash" | More], false) -> check_fields(More, true); check_fields(["salt" | More], HasPassHash) -> check_fields(More, HasPassHash); -check_fields(["superuser" | More], HasPassHash) -> +check_fields(["is_superuser" | More], HasPassHash) -> check_fields(More, HasPassHash); check_fields([Field | _], _) -> error({unsupported_field, Field}). diff --git a/apps/emqx_authn/test/data/user-credentials.csv b/apps/emqx_authn/test/data/user-credentials.csv index 0548308b7..cbadaefbc 100644 --- a/apps/emqx_authn/test/data/user-credentials.csv +++ b/apps/emqx_authn/test/data/user-credentials.csv @@ -1,3 +1,3 @@ -user_id,password_hash,salt,superuser +user_id,password_hash,salt,is_superuser myuser3,b6c743545a7817ae8c8f624371d5f5f0373234bb0ff36b8ffbf19bce0e06ab75,de1024f462fb83910fd13151bd4bd235,true myuser4,ee68c985a69208b6eda8c6c9b4c7c2d2b15ee2352cdd64a903171710a99182e8,ad773b5be9dd0613fe6c2f4d8c403139,false diff --git a/apps/emqx_authn/test/data/user-credentials.json b/apps/emqx_authn/test/data/user-credentials.json index e54501233..94375df22 100644 --- a/apps/emqx_authn/test/data/user-credentials.json +++ b/apps/emqx_authn/test/data/user-credentials.json @@ -3,12 +3,12 @@ "user_id":"myuser1", "password_hash":"c5e46903df45e5dc096dc74657610dbee8deaacae656df88a1788f1847390242", "salt": "e378187547bf2d6f0545a3f441aa4d8a", - "superuser": true + "is_superuser": true }, { "user_id":"myuser2", "password_hash":"f4d17f300b11e522fd33f497c11b126ef1ea5149c74d2220f9a16dc876d4567b", "salt": "6d3f9bd5b54d94b98adbcfe10b6d181f", - "superuser": false + "is_superuser": false } ] diff --git a/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl b/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl index 9d8b1d9fc..16c04771d 100644 --- a/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl @@ -52,13 +52,13 @@ all() -> % JWS = generate_jws('hmac-based', Payload, <<"abcdef">>), % ClientInfo = #{username => <<"myuser">>, % password => JWS}, -% ?assertEqual({stop, {ok, #{superuser => false}}}, ?AUTH:authenticate(ClientInfo, ignored)), +% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo, ignored)), -% Payload1 = #{<<"username">> => <<"myuser">>, <<"superuser">> => true}, +% Payload1 = #{<<"username">> => <<"myuser">>, <<"is_superuser">> => true}, % JWS1 = generate_jws('hmac-based', Payload1, <<"abcdef">>), % ClientInfo1 = #{username => <<"myuser">>, % password => JWS1}, -% ?assertEqual({stop, {ok, #{superuser => true}}}, ?AUTH:authenticate(ClientInfo1, ignored)), +% ?assertEqual({stop, {ok, #{is_superuser => true}}}, ?AUTH:authenticate(ClientInfo1, ignored)), % BadJWS = generate_jws('hmac-based', Payload, <<"bad_secret">>), % ClientInfo2 = ClientInfo#{password => BadJWS}, @@ -68,11 +68,11 @@ all() -> % Config2 = Config#{secret => base64:encode(<<"abcdef">>), % secret_base64_encoded => true}, % ?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, ID, Config2)), -% ?assertEqual({stop, {ok, #{superuser => false}}}, ?AUTH:authenticate(ClientInfo, ignored)), +% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo, ignored)), % Config3 = Config#{verify_claims => [{<<"username">>, <<"${mqtt-username}">>}]}, % ?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, ID, Config3)), -% ?assertEqual({stop, {ok, #{superuser => false}}}, ?AUTH:authenticate(ClientInfo, ignored)), +% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo, ignored)), % ?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo#{username => <<"otheruser">>}, ok)), % %% Expiration @@ -86,14 +86,14 @@ all() -> % , <<"exp">> => erlang:system_time(second) + 60}, % JWS4 = generate_jws('hmac-based', Payload4, <<"abcdef">>), % ClientInfo4 = ClientInfo#{password => JWS4}, -% ?assertEqual({stop, {ok, #{superuser => false}}}, ?AUTH:authenticate(ClientInfo4, ignored)), +% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo4, ignored)), % %% Issued At % Payload5 = #{ <<"username">> => <<"myuser">> % , <<"iat">> => erlang:system_time(second) - 60}, % JWS5 = generate_jws('hmac-based', Payload5, <<"abcdef">>), % ClientInfo5 = ClientInfo#{password => JWS5}, -% ?assertEqual({stop, {ok, #{superuser => false}}}, ?AUTH:authenticate(ClientInfo5, ignored)), +% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo5, ignored)), % Payload6 = #{ <<"username">> => <<"myuser">> % , <<"iat">> => erlang:system_time(second) + 60}, @@ -106,7 +106,7 @@ all() -> % , <<"nbf">> => erlang:system_time(second) - 60}, % JWS7 = generate_jws('hmac-based', Payload7, <<"abcdef">>), % ClientInfo7 = ClientInfo#{password => JWS7}, -% ?assertEqual({stop, {ok, #{superuser => false}}}, ?AUTH:authenticate(ClientInfo7, ignored)), +% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo7, ignored)), % Payload8 = #{ <<"username">> => <<"myuser">> % , <<"nbf">> => erlang:system_time(second) + 60}, @@ -134,7 +134,7 @@ all() -> % JWS = generate_jws('public-key', Payload, PrivateKey), % ClientInfo = #{username => <<"myuser">>, % password => JWS}, -% ?assertEqual({stop, {ok, #{superuser => false}}}, ?AUTH:authenticate(ClientInfo, ignored)), +% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo, ignored)), % ?assertEqual({stop, {error, not_authorized}}, ?AUTH:authenticate(ClientInfo#{password => <<"badpassword">>}, ignored)), % ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)), diff --git a/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl b/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl index 4bc6961dd..959cf0323 100644 --- a/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl @@ -56,9 +56,9 @@ all() -> % ClientInfo = #{zone => external, % username => <<"myuser">>, % password => <<"mypass">>}, -% ?assertEqual({stop, {ok, #{superuser => false}}}, ?AUTH:authenticate(ClientInfo, ignored)), +% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo, ignored)), % ?AUTH:enable(), -% ?assertEqual({ok, #{superuser => false}}, emqx_access_control:authenticate(ClientInfo)), +% ?assertEqual({ok, #{is_superuser => false}}, emqx_access_control:authenticate(ClientInfo)), % ClientInfo2 = ClientInfo#{username => <<"baduser">>}, % ?assertEqual({stop, {error, not_authorized}}, ?AUTH:authenticate(ClientInfo2, ignored)), @@ -71,10 +71,10 @@ all() -> % UserInfo2 = UserInfo#{password => <<"mypass2">>}, % ?assertMatch({ok, #{user_id := <<"myuser">>}}, ?AUTH:update_user(?CHAIN, ID, <<"myuser">>, UserInfo2)), % ClientInfo4 = ClientInfo#{password => <<"mypass2">>}, -% ?assertEqual({stop, {ok, #{superuser => false}}}, ?AUTH:authenticate(ClientInfo4, ignored)), +% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo4, ignored)), -% ?assertMatch({ok, #{user_id := <<"myuser">>}}, ?AUTH:update_user(?CHAIN, ID, <<"myuser">>, #{superuser => true})), -% ?assertEqual({stop, {ok, #{superuser => true}}}, ?AUTH:authenticate(ClientInfo4, ignored)), +% ?assertMatch({ok, #{user_id := <<"myuser">>}}, ?AUTH:update_user(?CHAIN, ID, <<"myuser">>, #{is_superuser => true})), +% ?assertEqual({stop, {ok, #{is_superuser => true}}}, ?AUTH:authenticate(ClientInfo4, ignored)), % ?assertEqual(ok, ?AUTH:delete_user(?CHAIN, ID, <<"myuser">>)), % ?assertEqual({error, not_found}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser">>)), @@ -107,15 +107,15 @@ all() -> % ClientInfo1 = #{username => <<"myuser1">>, % password => <<"mypassword1">>}, -% ?assertEqual({stop, {ok, #{superuser => true}}}, ?AUTH:authenticate(ClientInfo1, ignored)), +% ?assertEqual({stop, {ok, #{is_superuser => true}}}, ?AUTH:authenticate(ClientInfo1, ignored)), % ClientInfo2 = ClientInfo1#{username => <<"myuser2">>, % password => <<"mypassword2">>}, -% ?assertEqual({stop, {ok, #{superuser => false}}}, ?AUTH:authenticate(ClientInfo2, ignored)), +% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo2, ignored)), % ClientInfo3 = ClientInfo1#{username => <<"myuser3">>, % password => <<"mypassword3">>}, -% ?assertEqual({stop, {ok, #{superuser => true}}}, ?AUTH:authenticate(ClientInfo3, ignored)), +% ?assertEqual({stop, {ok, #{is_superuser => true}}}, ?AUTH:authenticate(ClientInfo3, ignored)), % ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)), % ok. @@ -152,12 +152,12 @@ all() -> % ClientInfo1 = #{username => <<"myuser">>, % clientid => <<"myclient">>, % password => <<"mypass1">>}, -% ?assertEqual({stop, {ok, #{superuser => false}}}, ?AUTH:authenticate(ClientInfo1, ignored)), +% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo1, ignored)), % ?assertEqual(ok, ?AUTH:move_authenticator(?CHAIN, ID2, top)), % ?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo1, ignored)), % ClientInfo2 = ClientInfo1#{password => <<"mypass2">>}, -% ?assertEqual({stop, {ok, #{superuser => false}}}, ?AUTH:authenticate(ClientInfo2, ignored)), +% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo2, ignored)), % ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID1)), % ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID2)),