فهرست منبع

refactor: do not destory resource when update authn/authz resource

JimMoen 3 سال پیش
والد
کامیت
87af77ec35

+ 23 - 0
apps/emqx_authn/src/emqx_authn_utils.erl

@@ -20,6 +20,8 @@
 -include_lib("emqx_authn.hrl").
 
 -export([
+    create_resource/3,
+    update_resource/3,
     check_password_from_selected_map/3,
     parse_deep/1,
     parse_str/1,
@@ -47,6 +49,27 @@
 %% APIs
 %%------------------------------------------------------------------------------
 
+create_resource(ResourceId, Module, Config) ->
+    {ok, _Data} = emqx_resource:create_local(
+        ResourceId,
+        ?RESOURCE_GROUP,
+        Module,
+        Config,
+        #{}
+    ).
+
+update_resource(Module, Config, ResourceId) ->
+    %% recreate before maybe stop
+    %% resource will auto start during recreate
+    Result = emqx_resource:recreate_local(ResourceId, Module, Config),
+    case Config of
+        #{enable := true} ->
+            Result;
+        #{enable := false} ->
+            ok = emqx_resource:stop(ResourceId),
+            Result
+    end.
+
 check_password_from_selected_map(_Algorithm, _Selected, undefined) ->
     {error, bad_username_or_password};
 check_password_from_selected_map(

+ 36 - 34
apps/emqx_authn/src/simple_authn/emqx_authn_http.erl

@@ -158,45 +158,24 @@ refs() ->
 create(_AuthenticatorID, Config) ->
     create(Config).
 
-create(
-    #{
-        method := Method,
-        url := RawUrl,
-        headers := Headers,
-        request_timeout := RequestTimeout
-    } = Config
-) ->
-    {BaseUrl0, Path, Query} = parse_url(RawUrl),
-    {ok, BaseUrl} = emqx_http_lib:uri_parse(BaseUrl0),
+create(Config0) ->
     ResourceId = emqx_authn_utils:make_resource_id(?MODULE),
-    State = #{
-        method => Method,
-        path => Path,
-        headers => ensure_header_name_type(Headers),
-        base_path_templete => emqx_authn_utils:parse_str(Path),
-        base_query_template => emqx_authn_utils:parse_deep(
-            cow_qs:parse_qs(to_bin(Query))
-        ),
-        body_template => emqx_authn_utils:parse_deep(maps:get(body, Config, #{})),
-        request_timeout => RequestTimeout,
-        resource_id => ResourceId
-    },
-    {ok, _Data} = emqx_resource:create_local(
+    {Config, State} = parse_config(Config0),
+    {ok, _Data} = emqx_authn_utils:create_resource(
         ResourceId,
-        ?RESOURCE_GROUP,
         emqx_connector_http,
-        Config#{
-            base_url => BaseUrl,
-            pool_type => random
-        },
-        #{}
+        Config
     ),
-    {ok, State}.
+    {ok, State#{resource_id => ResourceId}}.
 
-update(Config, State) ->
-    {ok, NewState} = create(Config),
-    ok = destroy(State),
-    {ok, NewState}.
+update(Config0, #{resource_id := ResourceId} = _State) ->
+    {Config, NState} = parse_config(Config0),
+    case emqx_authn_utils:update_resource(emqx_connector_http, Config, ResourceId) of
+        {error, Reason} ->
+            error({load_config_error, Reason});
+        {ok, _} ->
+            {ok, NState#{resource_id => ResourceId}}
+    end.
 
 authenticate(#{auth_method := _}, _) ->
     ignore;
@@ -325,6 +304,29 @@ parse_url(Url) ->
             throw({invalid_url, Url})
     end.
 
+parse_config(
+    #{
+        method := Method,
+        url := RawUrl,
+        headers := Headers,
+        request_timeout := RequestTimeout
+    } = Config
+) ->
+    {BaseUrl0, Path, Query} = parse_url(RawUrl),
+    {ok, BaseUrl} = emqx_http_lib:uri_parse(BaseUrl0),
+    State = #{
+        method => Method,
+        path => Path,
+        headers => ensure_header_name_type(Headers),
+        base_path_templete => emqx_authn_utils:parse_str(Path),
+        base_query_template => emqx_authn_utils:parse_deep(
+            cow_qs:parse_qs(to_bin(Query))
+        ),
+        body_template => emqx_authn_utils:parse_deep(maps:get(body, Config, #{})),
+        request_timeout => RequestTimeout
+    },
+    {Config#{base_url => BaseUrl, pool_type => random}, State}.
+
 generate_request(Credential, #{
     method := Method,
     headers := Headers0,

+ 33 - 32
apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl

@@ -126,39 +126,28 @@ refs() ->
 create(_AuthenticatorID, Config) ->
     create(Config).
 
-create(#{filter := Filter} = Config) ->
-    FilterTemplate = emqx_authn_utils:parse_deep(Filter),
-    State = maps:with(
-        [
-            collection,
-            password_hash_field,
-            salt_field,
-            is_superuser_field,
-            password_hash_algorithm,
-            salt_position
-        ],
-        Config
-    ),
-    #{password_hash_algorithm := Algorithm} = State,
-    ok = emqx_authn_password_hashing:init(Algorithm),
+create(Config0) ->
     ResourceId = emqx_authn_utils:make_resource_id(?MODULE),
-    NState = State#{
-        filter_template => FilterTemplate,
-        resource_id => ResourceId
-    },
-    {ok, _Data} = emqx_resource:create_local(
+    {Config, State} = parse_config(Config0),
+    {ok, _Data} = emqx_authn_utils:create_resource(
         ResourceId,
-        ?RESOURCE_GROUP,
         emqx_connector_mongo,
-        Config,
-        #{}
+        Config
     ),
-    {ok, NState}.
+    {ok, State#{resource_id => ResourceId}}.
 
-update(Config, State) ->
-    {ok, NewState} = create(Config),
-    ok = destroy(State),
-    {ok, NewState}.
+update(Config0, #{resource_id := ResourceId} = _State) ->
+    {Config, NState} = parse_config(Config0),
+    case emqx_authn_utils:update_resource(emqx_connector_mongo, Config, ResourceId) of
+        {error, Reason} ->
+            error({load_config_error, Reason});
+        {ok, _} ->
+            {ok, NState#{resource_id => ResourceId}}
+    end.
+
+destroy(#{resource_id := ResourceId}) ->
+    _ = emqx_resource:remove_local(ResourceId),
+    ok.
 
 authenticate(#{auth_method := _}, _) ->
     ignore;
@@ -201,14 +190,26 @@ authenticate(
             end
     end.
 
-destroy(#{resource_id := ResourceId}) ->
-    _ = emqx_resource:remove_local(ResourceId),
-    ok.
-
 %%------------------------------------------------------------------------------
 %% Internal functions
 %%------------------------------------------------------------------------------
 
+parse_config(#{filter := Filter} = Config) ->
+    FilterTemplate = emqx_authn_utils:parse_deep(Filter),
+    State = maps:with(
+        [
+            collection,
+            password_hash_field,
+            salt_field,
+            is_superuser_field,
+            password_hash_algorithm,
+            salt_position
+        ],
+        Config
+    ),
+    ok = emqx_authn_password_hashing:init(maps:get(password_hash_algorithm, State)),
+    {Config, State#{filter_template => FilterTemplate}}.
+
 check_password(undefined, _Selected, _State) ->
     {error, bad_username_or_password};
 check_password(

+ 32 - 31
apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl

@@ -83,35 +83,24 @@ refs() ->
 create(_AuthenticatorID, Config) ->
     create(Config).
 
-create(
-    #{
-        password_hash_algorithm := Algorithm,
-        query := Query0,
-        query_timeout := QueryTimeout
-    } = Config
-) ->
-    ok = emqx_authn_password_hashing:init(Algorithm),
-    {PrepareSql, TmplToken} = emqx_authn_utils:parse_sql(Query0, '?'),
+create(Config0) ->
     ResourceId = emqx_authn_utils:make_resource_id(?MODULE),
-    State = #{
-        password_hash_algorithm => Algorithm,
-        tmpl_token => TmplToken,
-        query_timeout => QueryTimeout,
-        resource_id => ResourceId
-    },
-    {ok, _Data} = emqx_resource:create_local(
-        ResourceId,
-        ?RESOURCE_GROUP,
-        emqx_connector_mysql,
-        Config#{prepare_statement => #{?PREPARE_KEY => PrepareSql}},
-        #{}
-    ),
-    {ok, State}.
-
-update(Config, State) ->
-    {ok, NewState} = create(Config),
-    ok = destroy(State),
-    {ok, NewState}.
+    {Config, State} = parse_config(Config0),
+    {ok, _Data} = emqx_authn_utils:create_resource(ResourceId, emqx_connector_mysql, Config),
+    {ok, State#{resource_id => ResourceId}}.
+
+update(Config0, #{resource_id := ResourceId} = _State) ->
+    {Config, NState} = parse_config(Config0),
+    case emqx_authn_utils:update_resource(emqx_connector_mysql, Config, ResourceId) of
+        {error, Reason} ->
+            error({load_config_error, Reason});
+        {ok, _} ->
+            {ok, NState#{resource_id => ResourceId}}
+    end.
+
+destroy(#{resource_id := ResourceId}) ->
+    _ = emqx_resource:remove_local(ResourceId),
+    ok.
 
 authenticate(#{auth_method := _}, _) ->
     ignore;
@@ -152,6 +141,18 @@ authenticate(
             ignore
     end.
 
-destroy(#{resource_id := ResourceId}) ->
-    _ = emqx_resource:remove_local(ResourceId),
-    ok.
+parse_config(
+    #{
+        password_hash_algorithm := Algorithm,
+        query := Query0,
+        query_timeout := QueryTimeout
+    } = Config
+) ->
+    ok = emqx_authn_password_hashing:init(Algorithm),
+    {PrepareSql, TmplToken} = emqx_authn_utils:parse_sql(Query0, '?'),
+    State = #{
+        password_hash_algorithm => Algorithm,
+        tmpl_token => TmplToken,
+        query_timeout => QueryTimeout
+    },
+    {Config#{prepare_statement => #{?PREPARE_KEY => PrepareSql}}, State}.

+ 31 - 25
apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl

@@ -82,33 +82,28 @@ refs() ->
 create(_AuthenticatorID, Config) ->
     create(Config).
 
-create(
-    #{
-        query := Query0,
-        password_hash_algorithm := Algorithm
-    } = Config
-) ->
-    ok = emqx_authn_password_hashing:init(Algorithm),
-    {Query, PlaceHolders} = emqx_authn_utils:parse_sql(Query0, '$n'),
+create(Config0) ->
     ResourceId = emqx_authn_utils:make_resource_id(?MODULE),
-    State = #{
-        placeholders => PlaceHolders,
-        password_hash_algorithm => Algorithm,
-        resource_id => ResourceId
-    },
-    {ok, _Data} = emqx_resource:create_local(
+    {Config, State} = parse_config(Config0, ResourceId),
+    {ok, _Data} = emqx_authn_utils:create_resource(
         ResourceId,
-        ?RESOURCE_GROUP,
         emqx_connector_pgsql,
-        Config#{prepare_statement => #{ResourceId => Query}},
-        #{}
+        Config
     ),
-    {ok, State}.
+    {ok, State#{resource_id => ResourceId}}.
 
-update(Config, State) ->
-    {ok, NewState} = create(Config),
-    ok = destroy(State),
-    {ok, NewState}.
+update(Config0, #{resource_id := ResourceId} = _State) ->
+    {Config, NState} = parse_config(Config0, ResourceId),
+    case emqx_authn_utils:update_resource(emqx_connector_pgsql, Config, ResourceId) of
+        {error, Reason} ->
+            error({load_config_error, Reason});
+        {ok, _} ->
+            {ok, NState#{resource_id => ResourceId}}
+    end.
+
+destroy(#{resource_id := ResourceId}) ->
+    _ = emqx_resource:remove_local(ResourceId),
+    ok.
 
 authenticate(#{auth_method := _}, _) ->
     ignore;
@@ -147,6 +142,17 @@ authenticate(
             ignore
     end.
 
-destroy(#{resource_id := ResourceId}) ->
-    _ = emqx_resource:remove_local(ResourceId),
-    ok.
+parse_config(
+    #{
+        query := Query0,
+        password_hash_algorithm := Algorithm
+    } = Config,
+    ResourceId
+) ->
+    ok = emqx_authn_password_hashing:init(Algorithm),
+    {Query, PlaceHolders} = emqx_authn_utils:parse_sql(Query0, '$n'),
+    State = #{
+        placeholders => PlaceHolders,
+        password_hash_algorithm => Algorithm
+    },
+    {Config#{prepare_statement => #{ResourceId => Query}}, State}.

+ 43 - 44
apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl

@@ -96,51 +96,33 @@ refs() ->
 create(_AuthenticatorID, Config) ->
     create(Config).
 
-create(
-    #{
-        cmd := Cmd,
-        password_hash_algorithm := Algorithm
-    } = Config
-) ->
-    ok = emqx_authn_password_hashing:init(Algorithm),
-    try
-        NCmd = parse_cmd(Cmd),
-        ok = emqx_authn_utils:ensure_apps_started(Algorithm),
-        State = maps:with(
-            [password_hash_algorithm, salt_position],
-            Config
-        ),
-        ResourceId = emqx_authn_utils:make_resource_id(?MODULE),
-        NState = State#{
-            cmd => NCmd,
-            resource_id => ResourceId
-        },
-        {ok, _Data} = emqx_resource:create_local(
-            ResourceId,
-            ?RESOURCE_GROUP,
-            emqx_connector_redis,
-            Config,
-            #{}
-        ),
-        {ok, NState}
-    catch
-        error:{unsupported_cmd, _Cmd} ->
-            {error, {unsupported_cmd, Cmd}};
-        error:missing_password_hash ->
-            {error, missing_password_hash};
-        error:{unsupported_fields, Fields} ->
-            {error, {unsupported_fields, Fields}}
+create(Config0) ->
+    ResourceId = emqx_authn_utils:make_resource_id(?MODULE),
+    case parse_config(Config0) of
+        {error, _} = Res ->
+            Res;
+        {Config, State} ->
+            {ok, _Data} = emqx_authn_utils:create_resource(
+                ResourceId,
+                emqx_connector_redis,
+                Config
+            ),
+            {ok, State#{resource_id => ResourceId}}
     end.
 
-update(Config, State) ->
-    case create(Config) of
-        {ok, NewState} ->
-            ok = destroy(State),
-            {ok, NewState};
+update(Config0, #{resource_id := ResourceId} = _State) ->
+    {Config, NState} = parse_config(Config0),
+    case emqx_authn_utils:update_resource(emqx_connector_redis, Config, ResourceId) of
         {error, Reason} ->
-            {error, Reason}
+            error({load_config_error, Reason});
+        {ok, _} ->
+            {ok, NState#{resource_id => ResourceId}}
     end.
 
+destroy(#{resource_id := ResourceId}) ->
+    _ = emqx_resource:remove_local(ResourceId),
+    ok.
+
 authenticate(#{auth_method := _}, _) ->
     ignore;
 authenticate(
@@ -190,14 +172,31 @@ authenticate(
             ignore
     end.
 
-destroy(#{resource_id := ResourceId}) ->
-    _ = emqx_resource:remove_local(ResourceId),
-    ok.
-
 %%------------------------------------------------------------------------------
 %% Internal functions
 %%------------------------------------------------------------------------------
 
+parse_config(
+    #{
+        cmd := Cmd,
+        password_hash_algorithm := Algorithm
+    } = Config
+) ->
+    try
+        NCmd = parse_cmd(Cmd),
+        ok = emqx_authn_password_hashing:init(Algorithm),
+        ok = emqx_authn_utils:ensure_apps_started(Algorithm),
+        State = maps:with([password_hash_algorithm, salt_position], Config),
+        {Config, State#{cmd => NCmd}}
+    catch
+        error:{unsupported_cmd, _Cmd} ->
+            {error, {unsupported_cmd, Cmd}};
+        error:missing_password_hash ->
+            {error, missing_password_hash};
+        error:{unsupported_fields, Fields} ->
+            {error, {unsupported_fields, Fields}}
+    end.
+
 %% Only support HGET and HMGET
 parse_cmd(Cmd) ->
     case string:tokens(Cmd, " ") of

+ 24 - 15
apps/emqx_authz/src/emqx_authz.erl

@@ -63,16 +63,20 @@
 %% Initialize authz backend.
 %% Populate the passed configuration map with necessary data,
 %% like `ResourceID`s
--callback init(source()) -> source().
+-callback create(source()) -> source().
 
-%% Get authz text description.
--callback description() -> string().
+%% Update authz backend.
+%% Change configuration, or simply enable/disable
+-callback update(source()) -> source().
 
 %% Destroy authz backend.
 %% Make cleanup of all allocated data.
 %% An authz backend will not be used after `destroy`.
 -callback destroy(source()) -> ok.
 
+%% Get authz text description.
+-callback description() -> string().
+
 %% Authorize client action.
 -callback authorize(
     emqx_types:clientinfo(),
@@ -81,6 +85,10 @@
     source()
 ) -> match_result().
 
+-optional_callbacks([
+    update/1
+]).
+
 -spec register_metrics() -> ok.
 register_metrics() ->
     lists:foreach(fun emqx_metrics:ensure/1, ?METRICS).
@@ -90,7 +98,7 @@ init() ->
     emqx_conf:add_handler(?CONF_KEY_PATH, ?MODULE),
     Sources = emqx_conf:get(?CONF_KEY_PATH, []),
     ok = check_dup_types(Sources),
-    NSources = init_sources(Sources),
+    NSources = create_sources(Sources),
     ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [NSources]}, -1).
 
 deinit() ->
@@ -170,7 +178,7 @@ do_post_config_update({?CMD_MOVE, _Type, _Where} = Cmd, _Sources) ->
     InitedSources = lookup(),
     do_move(Cmd, InitedSources);
 do_post_config_update({?CMD_PREPEND, RawNewSource}, Sources) ->
-    InitedNewSource = init_source(get_source_by_type(type(RawNewSource), Sources)),
+    InitedNewSource = create_source(get_source_by_type(type(RawNewSource), Sources)),
     %% create metrics
     TypeName = type(RawNewSource),
     ok = emqx_metrics_worker:create_metrics(
@@ -181,14 +189,13 @@ do_post_config_update({?CMD_PREPEND, RawNewSource}, Sources) ->
     ),
     [InitedNewSource] ++ lookup();
 do_post_config_update({?CMD_APPEND, RawNewSource}, Sources) ->
-    InitedNewSource = init_source(get_source_by_type(type(RawNewSource), Sources)),
+    InitedNewSource = create_source(get_source_by_type(type(RawNewSource), Sources)),
     lookup() ++ [InitedNewSource];
 do_post_config_update({{?CMD_REPLACE, Type}, RawNewSource}, Sources) ->
     OldSources = lookup(),
     {OldSource, Front, Rear} = take(Type, OldSources),
     NewSource = get_source_by_type(type(RawNewSource), Sources),
-    ok = ensure_resource_deleted(OldSource),
-    InitedSources = init_source(NewSource),
+    InitedSources = update_source(type(RawNewSource), OldSource, NewSource),
     Front ++ [InitedSources] ++ Rear;
 do_post_config_update({{?CMD_DELETE, Type}, _RawNewSource}, _Sources) ->
     OldInitedSources = lookup(),
@@ -203,7 +210,7 @@ do_post_config_update({?CMD_REPLACE, _RawNewSources}, Sources) ->
     OldInitedSources = lookup(),
     lists:foreach(fun ensure_resource_deleted/1, OldInitedSources),
     lists:foreach(fun clear_certs/1, OldInitedSources),
-    init_sources(Sources).
+    create_sources(Sources).
 
 %% @doc do source move
 do_move({?CMD_MOVE, Type, ?CMD_MOVE_FRONT}, Sources) ->
@@ -251,20 +258,22 @@ check_dup_types([Source | Sources], Checked) ->
             check_dup_types(Sources, [Type | Checked])
     end.
 
-init_sources(Sources) ->
+create_sources(Sources) ->
     {_Enabled, Disabled} = lists:partition(fun(#{enable := Enable}) -> Enable end, Sources),
     case Disabled =/= [] of
         true -> ?SLOG(info, #{msg => "disabled_sources_ignored", sources => Disabled});
         false -> ok
     end,
     ok = lists:foreach(fun init_metrics/1, Sources),
-    lists:map(fun init_source/1, Sources).
+    lists:map(fun create_source/1, Sources).
 
-init_source(#{enable := false} = Source) ->
-    Source;
-init_source(#{type := Type} = Source) ->
+create_source(#{type := Type} = Source) ->
+    Module = authz_module(Type),
+    Module:create(Source).
+
+update_source(Type, OldSource, NewSource) ->
     Module = authz_module(Type),
-    Module:init(Source).
+    Module:update(maps:merge(OldSource, NewSource)).
 
 init_metrics(Source) ->
     TypeName = type(Source),

+ 1 - 0
apps/emqx_authz/src/emqx_authz_api_sources.erl

@@ -317,6 +317,7 @@ lookup_from_local_node(Type) ->
             end;
         _ ->
             Metrics = emqx_metrics_worker:get_metrics(authz_metrics, Type),
+            %% for authz file/authz mnesia
             {ok, {NodeId, connected, Metrics, #{}}}
     catch
         _:Reason -> {error, {NodeId, list_to_binary(io_lib:format("~p", [Reason]))}}

+ 6 - 2
apps/emqx_authz/src/emqx_authz_file.erl

@@ -29,7 +29,8 @@
 %% APIs
 -export([
     description/0,
-    init/1,
+    create/1,
+    update/1,
     destroy/1,
     authorize/4
 ]).
@@ -37,7 +38,7 @@
 description() ->
     "AuthZ with static rules".
 
-init(#{path := Path} = Source) ->
+create(#{path := Path} = Source) ->
     Rules =
         case file:consult(Path) of
             {ok, Terms} ->
@@ -55,6 +56,9 @@ init(#{path := Path} = Source) ->
         end,
     Source#{annotations => #{rules => Rules}}.
 
+update(#{path := _Path} = Source) ->
+    create(Source).
+
 destroy(_Source) -> ok.
 
 authorize(Client, PubSub, Topic, #{annotations := #{rules := Rules}}) ->

+ 13 - 4
apps/emqx_authz/src/emqx_authz_http.erl

@@ -26,7 +26,8 @@
 %% AuthZ Callbacks
 -export([
     description/0,
-    init/1,
+    create/1,
+    update/1,
     destroy/1,
     authorize/4,
     parse_url/1
@@ -50,10 +51,18 @@
 description() ->
     "AuthZ with http".
 
-init(Config) ->
+create(Config) ->
     NConfig = parse_config(Config),
-    {ok, Id} = emqx_authz_utils:create_resource(emqx_connector_http, NConfig),
-    NConfig#{annotations => #{id => Id}}.
+    ResourceId = emqx_authn_utils:make_resource_id(?MODULE),
+    {ok, _Data} = emqx_authz_utils:create_resource(ResourceId, emqx_connector_http, NConfig),
+    NConfig#{annotations => #{id => ResourceId}}.
+
+update(Config) ->
+    NConfig = parse_config(Config),
+    case emqx_authz_utils:update_resource(emqx_connector_http, NConfig) of
+        {error, Reason} -> error({load_config_error, Reason});
+        {ok, Id} -> NConfig#{annotations => #{id => Id}}
+    end.
 
 destroy(#{annotations := #{id := Id}}) ->
     ok = emqx_resource:remove_local(Id).

+ 6 - 2
apps/emqx_authz/src/emqx_authz_jwt.erl

@@ -28,7 +28,8 @@
 %% APIs
 -export([
     description/0,
-    init/1,
+    create/1,
+    update/1,
     destroy/1,
     authorize/4
 ]).
@@ -46,7 +47,10 @@
 description() ->
     "AuthZ with JWT".
 
-init(#{acl_claim_name := _AclClaimName} = Source) ->
+create(#{acl_claim_name := _AclClaimName} = Source) ->
+    Source.
+
+update(#{acl_claim_name := _AclClaimName} = Source) ->
     Source.
 
 destroy(_Source) -> ok.

+ 5 - 2
apps/emqx_authz/src/emqx_authz_mnesia.erl

@@ -46,7 +46,8 @@
 %% AuthZ Callbacks
 -export([
     description/0,
-    init/1,
+    create/1,
+    update/1,
     destroy/1,
     authorize/4
 ]).
@@ -88,7 +89,9 @@ mnesia(boot) ->
 description() ->
     "AuthZ with Mnesia".
 
-init(Source) -> Source.
+create(Source) -> Source.
+
+update(Source) -> Source.
 
 destroy(_Source) -> ok.
 

+ 16 - 10
apps/emqx_authz/src/emqx_authz_mongodb.erl

@@ -26,7 +26,8 @@
 %% AuthZ Callbacks
 -export([
     description/0,
-    init/1,
+    create/1,
+    update/1,
     destroy/1,
     authorize/4
 ]).
@@ -45,15 +46,20 @@
 description() ->
     "AuthZ with MongoDB".
 
-init(#{filter := Filter} = Source) ->
-    {ok, Id} = emqx_authz_utils:create_resource(emqx_connector_mongo, Source),
-    Source#{
-        annotations => #{id => Id},
-        filter_template => emqx_authz_utils:parse_deep(
-            Filter,
-            ?PLACEHOLDERS
-        )
-    }.
+create(#{filter := Filter} = Source) ->
+    ResourceId = emqx_authz_utils:make_resource_id(?MODULE),
+    {ok, _Data} = emqx_authz_utils:create_resource(ResourceId, emqx_connector_mongo, Source),
+    FilterTemp = emqx_authz_utils:parse_deep(Filter, ?PLACEHOLDERS),
+    Source#{annotations => #{id => ResourceId}, filter_template => FilterTemp}.
+
+update(#{filter := Filter} = Source) ->
+    FilterTemp = emqx_authz_utils:parse_deep(Filter, ?PLACEHOLDERS),
+    case emqx_authz_utils:update_resource(emqx_connector_mongo, Source) of
+        {error, Reason} ->
+            error({load_config_error, Reason});
+        {ok, Id} ->
+            Source#{annotations => #{id => Id}, filter_template => FilterTemp}
+    end.
 
 destroy(#{annotations := #{id := Id}}) ->
     ok = emqx_resource:remove_local(Id).

+ 16 - 4
apps/emqx_authz/src/emqx_authz_mysql.erl

@@ -28,7 +28,8 @@
 %% AuthZ Callbacks
 -export([
     description/0,
-    init/1,
+    create/1,
+    update/1,
     destroy/1,
     authorize/4
 ]).
@@ -49,11 +50,22 @@
 description() ->
     "AuthZ with Mysql".
 
-init(#{query := SQL} = Source0) ->
+create(#{query := SQL} = Source0) ->
     {PrepareSQL, TmplToken} = emqx_authz_utils:parse_sql(SQL, '?', ?PLACEHOLDERS),
+    ResourceId = emqx_authz_utils:make_resource_id(?MODULE),
     Source = Source0#{prepare_statement => #{?PREPARE_KEY => PrepareSQL}},
-    {ok, Id} = emqx_authz_utils:create_resource(emqx_connector_mysql, Source),
-    Source#{annotations => #{id => Id, tmpl_oken => TmplToken}}.
+    {ok, _Data} = emqx_authz_utils:create_resource(ResourceId, emqx_connector_mysql, Source),
+    Source#{annotations => #{id => ResourceId, tmpl_oken => TmplToken}}.
+
+update(#{query := SQL} = Source0) ->
+    {PrepareSQL, TmplToken} = emqx_authz_utils:parse_sql(SQL, '?', ?PLACEHOLDERS),
+    Source = Source0#{prepare_statement => #{?PREPARE_KEY => PrepareSQL}},
+    case emqx_authz_utils:update_resource(emqx_connector_mysql, Source) of
+        {error, Reason} ->
+            error({load_config_error, Reason});
+        {ok, Id} ->
+            Source#{annotations => #{id => Id, tmpl_oken => TmplToken}}
+    end.
 
 destroy(#{annotations := #{id := Id}}) ->
     ok = emqx_resource:remove_local(Id).

+ 21 - 18
apps/emqx_authz/src/emqx_authz_postgresql.erl

@@ -26,7 +26,8 @@
 %% AuthZ Callbacks
 -export([
     description/0,
-    init/1,
+    create/1,
+    update/1,
     destroy/1,
     authorize/4
 ]).
@@ -47,27 +48,29 @@
 description() ->
     "AuthZ with PostgreSQL".
 
-init(#{query := SQL0} = Source) ->
-    {SQL, PlaceHolders} = emqx_authz_utils:parse_sql(
-        SQL0,
-        '$n',
-        ?PLACEHOLDERS
-    ),
+create(#{query := SQL0} = Source) ->
+    {SQL, PlaceHolders} = emqx_authz_utils:parse_sql(SQL0, '$n', ?PLACEHOLDERS),
     ResourceID = emqx_authz_utils:make_resource_id(emqx_connector_pgsql),
-    {ok, _Data} = emqx_resource:create_local(
+    {ok, _Data} = emqx_authz_utils:create_resource(
         ResourceID,
-        ?RESOURCE_GROUP,
         emqx_connector_pgsql,
-        Source#{prepare_statement => #{ResourceID => SQL}},
-        #{}
+        Source#{prepare_statement => #{ResourceID => SQL}}
     ),
-    Source#{
-        annotations =>
-            #{
-                id => ResourceID,
-                placeholders => PlaceHolders
-            }
-    }.
+    Source#{annotations => #{id => ResourceID, placeholders => PlaceHolders}}.
+
+update(#{query := SQL0, annotations := #{id := ResourceID}} = Source) ->
+    {SQL, PlaceHolders} = emqx_authz_utils:parse_sql(SQL0, '$n', ?PLACEHOLDERS),
+    case
+        emqx_authz_utils:update_resource(
+            emqx_connector_pgsql,
+            Source#{prepare_statement => #{ResourceID => SQL}}
+        )
+    of
+        {error, Reason} ->
+            error({load_config_error, Reason});
+        {ok, Id} ->
+            Source#{annotations => #{id => Id, placeholders => PlaceHolders}}
+    end.
 
 destroy(#{annotations := #{id := Id}}) ->
     ok = emqx_resource:remove_local(Id).

+ 16 - 7
apps/emqx_authz/src/emqx_authz_redis.erl

@@ -26,7 +26,8 @@
 %% AuthZ Callbacks
 -export([
     description/0,
-    init/1,
+    create/1,
+    update/1,
     destroy/1,
     authorize/4
 ]).
@@ -47,14 +48,22 @@
 description() ->
     "AuthZ with Redis".
 
-init(#{cmd := CmdStr} = Source) ->
+create(#{cmd := CmdStr} = Source) ->
     Cmd = tokens(CmdStr),
+    ResourceId = emqx_authz_utils:make_resource_id(?MODULE),
     CmdTemplate = emqx_authz_utils:parse_deep(Cmd, ?PLACEHOLDERS),
-    {ok, Id} = emqx_authz_utils:create_resource(emqx_connector_redis, Source),
-    Source#{
-        annotations => #{id => Id},
-        cmd_template => CmdTemplate
-    }.
+    {ok, _Data} = emqx_authz_utils:create_resource(ResourceId, emqx_connector_redis, Source),
+    Source#{annotations => #{id => ResourceId}, cmd_template => CmdTemplate}.
+
+update(#{cmd := CmdStr} = Source) ->
+    Cmd = tokens(CmdStr),
+    CmdTemplate = emqx_authz_utils:parse_deep(Cmd, ?PLACEHOLDERS),
+    case emqx_authz_utils:update_resource(emqx_connector_redis, Source) of
+        {error, Reason} ->
+            error({load_config_error, Reason});
+        {ok, Id} ->
+            Source#{annotations => #{id => Id}, cmd_template => CmdTemplate}
+    end.
 
 destroy(#{annotations := #{id := Id}}) ->
     ok = emqx_resource:remove_local(Id).

+ 28 - 4
apps/emqx_authz/src/emqx_authz_utils.erl

@@ -23,6 +23,8 @@
     cleanup_resources/0,
     make_resource_id/1,
     create_resource/2,
+    create_resource/3,
+    update_resource/2,
     update_config/2,
     parse_deep/2,
     parse_str/2,
@@ -37,15 +39,37 @@
 %%------------------------------------------------------------------------------
 
 create_resource(Module, Config) ->
-    ResourceID = make_resource_id(Module),
+    ResourceId = make_resource_id(Module),
+    create_resource(ResourceId, Module, Config).
+
+create_resource(ResourceId, Module, Config) ->
     {ok, _Data} = emqx_resource:create_local(
-        ResourceID,
+        ResourceId,
         ?RESOURCE_GROUP,
         Module,
         Config,
         #{}
-    ),
-    {ok, ResourceID}.
+    ).
+
+update_resource(Module, #{annotations := #{id := ResourceId}} = Source) ->
+    Result =
+        case
+            emqx_resource:recreate_local(
+                ResourceId,
+                Module,
+                Source
+            )
+        of
+            {ok, _} -> {ok, ResourceId};
+            {error, Reason} -> {error, Reason}
+        end,
+    case Source of
+        #{enable := true} ->
+            Result;
+        #{enable := false} ->
+            ok = emqx_resource:stop(ResourceId),
+            Result
+    end.
 
 cleanup_resources() ->
     lists:foreach(