feat(authz api): support file type for sources

This commit is contained in:
zhanghongtong 2021-09-02 09:57:26 +08:00 committed by Rory Z
parent 8252771306
commit 07dcd9e705
4 changed files with 140 additions and 18 deletions

View File

@ -41,7 +41,8 @@ definitions() ->
]
},
Sources = #{
oneOf => [ minirest:ref(<<"connector_redis">>)
oneOf => [ minirest:ref(<<"redis">>)
, minirest:ref(<<"file">>)
]
},
SSL = #{
@ -55,7 +56,7 @@ definitions() ->
verify => #{type => boolean, example => false}
}
},
ConnectorRedis = #{
Redis = #{
type => object,
required => [type, enable, config, cmd],
properties => #{
@ -123,8 +124,35 @@ definitions() ->
}
}
},
File = #{
type => object,
required => [type, enable, rules],
properties => #{
type => #{
type => string,
enum => [<<"redis">>],
example => <<"redis">>
},
enable => #{
type => boolean,
example => true
},
rules => #{
type => array,
items => #{
type => string,
example => <<"{allow,{username,\"^dashboard?\"},subscribe,[\"$SYS/#\"]}.">>
}
},
path => #{
type => string,
example => <<"/path/to/authorizaiton_rules.conf">>
}
}
},
[ #{<<"returned_sources">> => RetruenedSources}
, #{<<"sources">> => Sources}
, #{<<"ssl">> => SSL}
, #{<<"connector_redis">> => ConnectorRedis}
, #{<<"redis">> => Redis}
, #{<<"file">> => File}
].

View File

@ -23,18 +23,30 @@
-define(EXAMPLE_REDIS,
#{type=> redis,
enable => true,
config => #{server => <<"127.0.0.1:3306">>,
redis_type => single,
pool_size => 1,
auto_reconnect => true
},
cmd => <<"HGETALL mqtt_authz">>}).
-define(EXAMPLE_FILE,
#{type=> file,
enable => true,
rules => [<<"{allow,{username,\"^dashboard?\"},subscribe,[\"$SYS/#\"]}.">>,
<<"{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}.">>
]}).
-define(EXAMPLE_RETURNED_REDIS,
maps:put(annotations, #{status => healthy}, ?EXAMPLE_REDIS)
).
-define(EXAMPLE_RETURNED_FILE,
maps:put(annotations, #{status => healthy}, ?EXAMPLE_FILE)
).
-define(EXAMPLE_RETURNED,
#{sources => [?EXAMPLE_RETURNED_REDIS
#{sources => [ ?EXAMPLE_RETURNED_REDIS
, ?EXAMPLE_RETURNED_FILE
]
}).
@ -91,6 +103,10 @@ sources_api() ->
redis => #{
summary => <<"Redis">>,
value => jsx:encode(?EXAMPLE_REDIS)
},
file => #{
summary => <<"File">>,
value => jsx:encode(?EXAMPLE_FILE)
}
}
}
@ -113,7 +129,11 @@ sources_api() ->
examples => #{
redis => #{
summary => <<"Redis">>,
value => jsx:encode([?EXAMPLE_REDIS])
value => jsx:encode(?EXAMPLE_REDIS)
},
file => #{
summary => <<"File">>,
value => jsx:encode(?EXAMPLE_FILE)
}
}
}
@ -148,9 +168,13 @@ source_api() ->
'application/json' => #{
schema => minirest:ref(<<"returned_sources">>),
examples => #{
sources => #{
summary => <<"Sources">>,
redis => #{
summary => <<"Redis">>,
value => jsx:encode(?EXAMPLE_RETURNED_REDIS)
},
file => #{
summary => <<"File">>,
value => jsx:encode(?EXAMPLE_RETURNED_FILE)
}
}
}
@ -179,6 +203,10 @@ source_api() ->
redis => #{
summary => <<"Redis">>,
value => jsx:encode(?EXAMPLE_REDIS)
},
file => #{
summary => <<"File">>,
value => jsx:encode(?EXAMPLE_FILE)
}
}
}
@ -271,7 +299,16 @@ move_source_api() ->
{"/authorization/sources/:type/move", Metadata, move_source}.
sources(get, _) ->
Sources = lists:foldl(fun (#{type := _Type, enable := true, config := Config, annotations := #{id := Id}} = Source, AccIn) ->
Sources = lists:foldl(fun (#{enable := false} = Source, AccIn) ->
lists:append(AccIn, [Source#{annotations => #{status => unhealthy}}]);
(#{type := file, path := Path}, AccIn) ->
{ok, Rules} = file:consult(Path),
lists:append(AccIn, [#{type => file,
enable => true,
rules => [ io_lib:format("~p", [R])|| R <- Rules],
annotations => #{status => healthy}
}]);
(#{type := _Type, config := Config, annotations := #{id := Id}} = Source, AccIn) ->
NSource0 = case maps:get(server, Config, undefined) of
undefined -> Source;
Server ->
@ -293,15 +330,33 @@ sources(get, _) ->
lists:append(AccIn, [Source#{annotations => #{status => healthy}}])
end, [], emqx_authz:lookup()),
{200, #{sources => Sources}};
sources(post, #{body := Body}) ->
sources(post, #{body := #{<<"type">> := <<"file">>, <<"rules">> := Rules, <<"enable">> := Enable}}) when is_list(Rules) ->
Filename = filename:join([emqx:get_config([node, data_dir]), "authorization_rules.conf"]),
write_file(Filename, erlang:list_to_bitstring([<<Rule/binary, "\n">> || Rule <- Rules])),
case emqx_authz:update(head, [#{type => file, enable => Enable, path => Filename}]) of
{ok, _} -> {204};
{error, Reason} ->
{400, #{code => <<"BAD_REQUEST">>,
messgae => atom_to_binary(Reason)}}
end;
sources(post, #{body := Body}) when is_map(Body) ->
case emqx_authz:update(head, [save_cert(Body)]) of
{ok, _} -> {204};
{error, Reason} ->
{400, #{code => <<"BAD_REQUEST">>,
messgae => atom_to_binary(Reason)}}
end;
sources(put, #{body := Body}) ->
case emqx_authz:update(replace, save_cert(Body)) of
sources(put, #{body := Body}) when is_list(Body) ->
NBody = [ begin
case Source of
#{<<"type">> := <<"file">>, <<"rules">> := Rules, <<"enable">> := Enable} ->
Filename = filename:join([emqx:get_config([node, data_dir]), "authorization_rules.conf"]),
write_file(Filename, erlang:list_to_bitstring([<<Rule/binary, "\n">> || Rule <- Rules])),
#{type => file, enable => Enable, path => Filename};
_ -> save_cert(Source)
end
end || Source <- Body],
case emqx_authz:update(replace, NBody) of
{ok, _} -> {204};
{error, Reason} ->
{400, #{code => <<"BAD_REQUEST">>,
@ -311,8 +366,15 @@ sources(put, #{body := Body}) ->
source(get, #{bindings := #{type := Type}}) ->
case emqx_authz:lookup(Type) of
{error, Reason} -> {404, #{messgae => atom_to_binary(Reason)}};
#{enable := false} = Source -> {200, Source};
#{type := file} = Source -> {200, Source};
#{enable := false} = Source -> {200, Source#{annotations => #{status => unhealthy}}};
#{type := file, path := Path}->
{ok, Rules} = file:consult(Path),
{200, #{type => file,
enable => true,
rules => Rules,
annotations => #{status => healthy}
}
};
#{config := Config, annotations := #{id := Id}} = Source ->
NSource0 = case maps:get(server, Config, undefined) of
undefined -> Source;
@ -332,8 +394,16 @@ source(get, #{bindings := #{type := Type}}) ->
end,
{200, NSource2}
end;
source(put, #{bindings := #{type := Type}, body := Body}) ->
source(put, #{bindings := #{type := file}, body := #{<<"type">> := <<"file">>, <<"rules">> := Rules, <<"enable">> := Enable}}) ->
#{path := Path} = emqx_authz:lookup(file),
write_file(Path, erlang:list_to_bitstring([<<Rule/binary, "\n">> || Rule <- Rules])),
case emqx_authz:update({replace_once, file}, #{type => file, enable => Enable, path => Path}) of
{ok, _} -> {204};
{error, Reason} ->
{400, #{code => <<"BAD_REQUEST">>,
messgae => atom_to_binary(Reason)}}
end;
source(put, #{bindings := #{type := Type}, body := Body}) when is_map(Body) ->
case emqx_authz:update({replace_once, Type}, save_cert(Body)) of
{ok, _} -> {204};
{error, not_found_source} ->

View File

@ -114,6 +114,11 @@ init_per_testcase(_, Config) ->
<<"ssl">> => #{<<"enable">> => false}},
<<"cmd">> => <<"HGETALL mqtt_authz:%u">>
}).
-define(SOURCE6, #{<<"type">> => <<"file">>,
<<"enable">> => true,
<<"path">> => emqx_ct_helpers:deps_path(emqx_authz, "etc/authorization_rules.conf")
}).
%%------------------------------------------------------------------------------
%% Testcases
@ -125,12 +130,14 @@ t_update_source(_) ->
{ok, _} = emqx_authz:update(head, [?SOURCE1]),
{ok, _} = emqx_authz:update(tail, [?SOURCE4]),
{ok, _} = emqx_authz:update(tail, [?SOURCE5]),
{ok, _} = emqx_authz:update(tail, [?SOURCE6]),
?assertMatch([ #{type := http, enable := true}
, #{type := mongo, enable := true}
, #{type := mysql, enable := true}
, #{type := pgsql, enable := true}
, #{type := redis, enable := true}
, #{type := file, enable := true}
], emqx:get_config([authorization, sources], [])),
{ok, _} = emqx_authz:update({replace_once, http}, ?SOURCE1#{<<"enable">> := false}),
@ -138,23 +145,26 @@ t_update_source(_) ->
{ok, _} = emqx_authz:update({replace_once, mysql}, ?SOURCE3#{<<"enable">> := false}),
{ok, _} = emqx_authz:update({replace_once, pgsql}, ?SOURCE4#{<<"enable">> := false}),
{ok, _} = emqx_authz:update({replace_once, redis}, ?SOURCE5#{<<"enable">> := false}),
{ok, _} = emqx_authz:update({replace_once, file}, ?SOURCE6#{<<"enable">> := false}),
?assertMatch([ #{type := http, enable := false}
, #{type := mongo, enable := false}
, #{type := mysql, enable := false}
, #{type := pgsql, enable := false}
, #{type := redis, enable := false}
, #{type := file, enable := false}
], emqx:get_config([authorization, sources], [])),
{ok, _} = emqx_authz:update(replace, []).
t_move_source(_) ->
{ok, _} = emqx_authz:update(replace, [?SOURCE1, ?SOURCE2, ?SOURCE3, ?SOURCE4, ?SOURCE5]),
{ok, _} = emqx_authz:update(replace, [?SOURCE1, ?SOURCE2, ?SOURCE3, ?SOURCE4, ?SOURCE5, ?SOURCE6]),
?assertMatch([ #{type := http}
, #{type := mongo}
, #{type := mysql}
, #{type := pgsql}
, #{type := redis}
, #{type := file}
], emqx_authz:lookup()),
{ok, _} = emqx_authz:move(pgsql, <<"top">>),
@ -163,6 +173,7 @@ t_move_source(_) ->
, #{type := mongo}
, #{type := mysql}
, #{type := redis}
, #{type := file}
], emqx_authz:lookup()),
{ok, _} = emqx_authz:move(http, <<"bottom">>),
@ -170,6 +181,7 @@ t_move_source(_) ->
, #{type := mongo}
, #{type := mysql}
, #{type := redis}
, #{type := file}
, #{type := http}
], emqx_authz:lookup()),
@ -178,6 +190,7 @@ t_move_source(_) ->
, #{type := pgsql}
, #{type := mongo}
, #{type := redis}
, #{type := file}
, #{type := http}
], emqx_authz:lookup()),
@ -185,6 +198,7 @@ t_move_source(_) ->
?assertMatch([ #{type := mysql}
, #{type := pgsql}
, #{type := redis}
, #{type := file}
, #{type := http}
, #{type := mongo}
], emqx_authz:lookup()),

View File

@ -96,6 +96,13 @@
},
<<"cmd">> => <<"HGETALL mqtt_authz:%u">>
}).
-define(SOURCE6, #{<<"type">> => <<"file">>,
<<"enable">> => true,
<<"rules">> =>
[<<"{allow,{username,\"^dashboard?\"},subscribe,[\"$SYS/#\"]}.">>,
<<"{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}.">>
]
}).
all() ->
emqx_ct:all(?MODULE).
@ -119,7 +126,7 @@ init_per_suite(Config) ->
ok = emqx_config:init_load(emqx_authz_schema, ?CONF_DEFAULT),
ok = emqx_ct_helpers:start_apps([emqx_authz, emqx_dashboard], fun set_special_configs/1),
ok = emqx_ct_helpers:start_apps([emqx_authz, emqx_dashboard], fun set_special_configs/1),
{ok, _} = emqx:update_config([authorization, cache, enable], false),
{ok, _} = emqx:update_config([authorization, no_match], deny),
@ -183,7 +190,7 @@ t_api(_) ->
{ok, 200, Result2} = request(get, uri(["authorization", "sources"]), []),
?assertEqual(20, length(get_sources(Result2))),
{ok, 204, _} = request(put, uri(["authorization", "sources"]), [?SOURCE1, ?SOURCE2, ?SOURCE3, ?SOURCE4]),
{ok, 204, _} = request(put, uri(["authorization", "sources"]), [?SOURCE1, ?SOURCE2, ?SOURCE3, ?SOURCE4, ?SOURCE5, ?SOURCE6]),
{ok, 200, Result3} = request(get, uri(["authorization", "sources"]), []),
Sources = get_sources(Result3),
@ -191,7 +198,10 @@ t_api(_) ->
, #{<<"type">> := <<"mongo">>}
, #{<<"type">> := <<"mysql">>}
, #{<<"type">> := <<"pgsql">>}
, #{<<"type">> := <<"redis">>}
, #{<<"type">> := <<"file">>}
], Sources),
?assert(filelib:is_file(filename:join([emqx:get_config([node, data_dir]), "authorization_rules.conf"]))),
{ok, 204, _} = request(put, uri(["authorization", "sources", "http"]), ?SOURCE1#{<<"enable">> := false}),
{ok, 200, Result4} = request(get, uri(["authorization", "sources", "http"]), []),