feat(authz api): support file type for sources
This commit is contained in:
parent
8252771306
commit
07dcd9e705
|
@ -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}
|
||||
].
|
||||
|
|
|
@ -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} ->
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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).
|
||||
|
@ -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"]), []),
|
||||
|
|
Loading…
Reference in New Issue