diff --git a/apps/emqx_authz/include/emqx_authz.hrl b/apps/emqx_authz/include/emqx_authz.hrl index 967865868..b43a2cdab 100644 --- a/apps/emqx_authz/include/emqx_authz.hrl +++ b/apps/emqx_authz/include/emqx_authz.hrl @@ -37,6 +37,7 @@ -define(CMD_PREPEND, prepend). -define(CMD_APPEND, append). -define(CMD_MOVE, move). +-define(CMD_MERGE, merge). -define(CMD_MOVE_FRONT, front). -define(CMD_MOVE_REAR, rear). diff --git a/apps/emqx_authz/src/emqx_authz.erl b/apps/emqx_authz/src/emqx_authz.erl index b4c943072..7deda2726 100644 --- a/apps/emqx_authz/src/emqx_authz.erl +++ b/apps/emqx_authz/src/emqx_authz.erl @@ -24,11 +24,6 @@ -include_lib("emqx/include/emqx_hooks.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl"). --ifdef(TEST). --compile(export_all). --compile(nowarn_export_all). --endif. - -export([ register_metrics/0, init/0, @@ -37,6 +32,7 @@ lookup/1, move/2, update/2, + merge/1, authorize/5, %% for telemetry information get_enabled_authzs/0 @@ -45,6 +41,7 @@ -export([post_config_update/5, pre_config_update/3]). -export([acl_conf_file/0]). +-export([merge_sources/2, search/2]). %% Data backup -export([ @@ -128,6 +125,9 @@ lookup(Type) -> {Source, _Front, _Rear} = take(Type), Source. +merge(NewConf) -> + emqx_authz_utils:update_config(?ROOT_KEY, {?CMD_MERGE, NewConf}). + move(Type, ?CMD_MOVE_BEFORE(Before)) -> emqx_authz_utils:update_config( ?CONF_KEY_PATH, {?CMD_MOVE, type(Type), ?CMD_MOVE_BEFORE(type(Before))} @@ -158,9 +158,16 @@ pre_config_update(Path, Cmd, Sources) -> do_pre_config_update(?CONF_KEY_PATH, Cmd, Sources) -> do_pre_config_update(Cmd, Sources); +do_pre_config_update(?ROOT_KEY, {?CMD_MERGE, NewConf}, OldConf) -> + do_pre_config_merge(NewConf, OldConf); do_pre_config_update(?ROOT_KEY, NewConf, OldConf) -> do_pre_config_replace(NewConf, OldConf). +do_pre_config_merge(NewConf, OldConf) -> + MergeConf0 = emqx_utils_maps:deep_merge(OldConf, NewConf), + NewSources = merge_sources(NewConf, OldConf), + do_pre_config_replace(MergeConf0#{<<"sources">> := NewSources}, OldConf). + %% override the entire config when updating the root key %% emqx_conf:update(?ROOT_KEY, Conf); do_pre_config_replace(Conf, Conf) -> @@ -629,3 +636,77 @@ check_acl_file_rules(Path, Rules) -> after _ = file:delete(TmpPath) end. + +merge_sources(OldConf, NewConf) -> + Default = [emqx_authz_schema:default_authz()], + NewSources0 = maps:get(<<"sources">>, NewConf, Default), + OriginSources0 = maps:get(<<"sources">>, OldConf, Default), + {OriginSource, NewSources} = + lists:foldl( + fun(Old = #{<<"type">> := Type}, {OriginAcc, NewAcc}) -> + case search(Type, NewAcc) of + not_found -> + {[Old | OriginAcc], NewAcc}; + {New, NewAcc1} -> + MergeSource = emqx_utils_maps:deep_merge(Old, New), + {[MergeSource | OriginAcc], NewAcc1} + end + end, + {[], NewSources0}, + OriginSources0 + ), + lists:reverse(OriginSource) ++ NewSources. + +search(Type, Sources) -> + case lists:splitwith(fun(T) -> type(T) =/= type(Type) end, Sources) of + {_Front, []} -> not_found; + {Front, [Target | Rear]} -> {Target, Front ++ Rear} + end. + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-compile(nowarn_export_all). +-compile(export_all). + +merge_sources_test() -> + Default = [emqx_authz_schema:default_authz()], + Http = #{<<"type">> => <<"http">>, <<"enable">> => true}, + Mysql = #{<<"type">> => <<"mysql">>, <<"enable">> => true}, + Mongo = #{<<"type">> => <<"mongodb">>, <<"enable">> => true}, + Redis = #{<<"type">> => <<"redis">>, <<"enable">> => true}, + HttpDisable = Http#{<<"enable">> => false}, + MysqlDisable = Mysql#{<<"enable">> => false}, + MongoDisable = Mongo#{<<"enable">> => false}, + + %% has default source + ?assertEqual(Default, merge_sources(#{}, #{})), + ?assertEqual([], merge_sources(#{<<"sources">> => []}, #{<<"sources">> => []})), + ?assertEqual(Default, merge_sources(#{}, #{<<"sources">> => []})), + + %% add + ?assertEqual( + [Http, Mysql, Mongo, Redis], + merge_sources( + #{<<"sources">> => [Http, Mysql]}, + #{<<"sources">> => [Mongo, Redis]} + ) + ), + %% replace + ?assertEqual( + [HttpDisable, MysqlDisable], + merge_sources( + #{<<"sources">> => [Http, Mysql]}, + #{<<"sources">> => [HttpDisable, MysqlDisable]} + ) + ), + %% add + replace + change position + ?assertEqual( + [HttpDisable, Mysql, MongoDisable, Redis], + merge_sources( + #{<<"sources">> => [Http, Mysql, Mongo]}, + #{<<"sources">> => [MongoDisable, HttpDisable, Redis]} + ) + ), + ok. + +-endif.