feat(authz): more better update function
Signed-off-by: zhanghongtong <rory-z@outlook.com>
This commit is contained in:
parent
c26ec5c0dd
commit
4bb1e9c964
|
@ -20,11 +20,16 @@
|
||||||
-include("emqx_authz.hrl").
|
-include("emqx_authz.hrl").
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
-endif.
|
||||||
|
|
||||||
-export([ register_metrics/0
|
-export([ register_metrics/0
|
||||||
, init/0
|
, init/0
|
||||||
, init_rule/1
|
, init_rule/1
|
||||||
, lookup/0
|
, lookup/0
|
||||||
|
, lookup/1
|
||||||
, update/2
|
, update/2
|
||||||
, authorize/5
|
, authorize/5
|
||||||
, match/4
|
, match/4
|
||||||
|
@ -45,8 +50,13 @@ init() ->
|
||||||
ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [NRules]}, -1).
|
ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [NRules]}, -1).
|
||||||
|
|
||||||
lookup() ->
|
lookup() ->
|
||||||
{_M, _F, A}= find_action_in_hooks(),
|
{_M, _F, [A]}= find_action_in_hooks(),
|
||||||
A.
|
A.
|
||||||
|
lookup(Id) ->
|
||||||
|
case find_rule_by_id(Id, lookup()) of
|
||||||
|
{error, Reason} -> {error, Reason};
|
||||||
|
{_, Rule} -> Rule
|
||||||
|
end.
|
||||||
|
|
||||||
update(Cmd, Rules) ->
|
update(Cmd, Rules) ->
|
||||||
emqx_config:update(emqx_authz_schema, ?CONF_KEY_PATH, {Cmd, Rules}).
|
emqx_config:update(emqx_authz_schema, ?CONF_KEY_PATH, {Cmd, Rules}).
|
||||||
|
@ -56,6 +66,13 @@ pre_config_update({head, Rules}, OldConf) when is_list(Rules), is_list(OldConf)
|
||||||
Rules ++ OldConf;
|
Rules ++ OldConf;
|
||||||
pre_config_update({tail, Rules}, OldConf) when is_list(Rules), is_list(OldConf) ->
|
pre_config_update({tail, Rules}, OldConf) when is_list(Rules), is_list(OldConf) ->
|
||||||
OldConf ++ Rules;
|
OldConf ++ Rules;
|
||||||
|
pre_config_update({{replace_once, Id}, Rule}, OldConf) when is_map(Rule), is_list(OldConf) ->
|
||||||
|
{Index, _} = case find_rule_by_id(Id, lookup()) of
|
||||||
|
{error, Reason} -> error(Reason);
|
||||||
|
R -> R
|
||||||
|
end,
|
||||||
|
{OldConf1, OldConf2} = lists:split(Index, OldConf),
|
||||||
|
lists:droplast(OldConf1) ++ [Rule] ++ OldConf2;
|
||||||
pre_config_update({_, Rules}, _OldConf) when is_list(Rules)->
|
pre_config_update({_, Rules}, _OldConf) when is_list(Rules)->
|
||||||
%% overwrite the entire config!
|
%% overwrite the entire config!
|
||||||
Rules.
|
Rules.
|
||||||
|
@ -63,19 +80,38 @@ pre_config_update({_, Rules}, _OldConf) when is_list(Rules)->
|
||||||
post_config_update(_, undefined, _OldConf) ->
|
post_config_update(_, undefined, _OldConf) ->
|
||||||
ok;
|
ok;
|
||||||
post_config_update({head, Rules}, _NewRules, _OldConf) ->
|
post_config_update({head, Rules}, _NewRules, _OldConf) ->
|
||||||
InitedRules = [init_rule(Rule) || Rule <- check_rules(Rules)],
|
InitedRules = [init_rule(R) || R <- check_rules(Rules)],
|
||||||
ok = emqx_hooks:put('client.authorize', {?MODULE, authorize, [lists:append(InitedRules, lookup())]}, -1),
|
ok = emqx_hooks:put('client.authorize', {?MODULE, authorize, [InitedRules ++ lookup()]}, -1),
|
||||||
ok = emqx_authz_cache:drain_cache();
|
ok = emqx_authz_cache:drain_cache();
|
||||||
|
|
||||||
post_config_update({tail, Rules}, _NewRules, _OldConf) ->
|
post_config_update({tail, Rules}, _NewRules, _OldConf) ->
|
||||||
InitedRules = [init_rule(Rule) || Rule <- check_rules(Rules)],
|
InitedRules = [init_rule(R) || R <- check_rules(Rules)],
|
||||||
emqx_hooks:put('client.authorize', {?MODULE, authorize, [lists:append(InitedRules, lookup())]}, -1),
|
emqx_hooks:put('client.authorize', {?MODULE, authorize, [lookup() ++ InitedRules]}, -1),
|
||||||
ok = emqx_authz_cache:drain_cache();
|
ok = emqx_authz_cache:drain_cache();
|
||||||
|
|
||||||
|
post_config_update({{replace_once, Id}, Rule}, _NewRules, _OldConf) when is_map(Rule) ->
|
||||||
|
OldInitedRules = lookup(),
|
||||||
|
{Index, OldRule} = case find_rule_by_id(Id, OldInitedRules) of
|
||||||
|
{error, Reason} -> error(Reason);
|
||||||
|
R -> R
|
||||||
|
end,
|
||||||
|
case maps:get(type, OldRule, undefined) of
|
||||||
|
undefined -> ok;
|
||||||
|
_ ->
|
||||||
|
#{annotations := #{id := Id}} = OldRule,
|
||||||
|
ok = emqx_resource:remove(Id)
|
||||||
|
end,
|
||||||
|
{OldRules1, OldRules2 } = lists:split(Index, OldInitedRules),
|
||||||
|
InitedRules = [init_rule(R#{annotations => #{id => Id}}) || R <- check_rules([Rule])],
|
||||||
|
ok = emqx_hooks:put('client.authorize', {?MODULE, authorize, [lists:droplast(OldRules1) ++ InitedRules ++ OldRules2]}, -1),
|
||||||
|
ok = emqx_authz_cache:drain_cache();
|
||||||
|
|
||||||
post_config_update(_, NewRules, _OldConf) ->
|
post_config_update(_, NewRules, _OldConf) ->
|
||||||
%% overwrite the entire config!
|
%% overwrite the entire config!
|
||||||
OldInitedRules = lookup(),
|
OldInitedRules = lookup(),
|
||||||
InitedRules = [init_rule(Rule) || Rule <- NewRules],
|
InitedRules = [init_rule(Rule) || Rule <- NewRules],
|
||||||
ok = emqx_hooks:put('client.authorize', {?MODULE, authorize, [InitedRules]}, -1),
|
ok = emqx_hooks:put('client.authorize', {?MODULE, authorize, [InitedRules]}, -1),
|
||||||
lists:foreach(fun (#{type := _Type, enable := true, metadata := #{id := Id}}) ->
|
lists:foreach(fun (#{type := _Type, enable := true, annotations := #{id := Id}}) ->
|
||||||
ok = emqx_resource:remove(Id);
|
ok = emqx_resource:remove(Id);
|
||||||
(_) -> ok
|
(_) -> ok
|
||||||
end, OldInitedRules),
|
end, OldInitedRules),
|
||||||
|
@ -91,6 +127,14 @@ check_rules(RawRules) ->
|
||||||
#{authorization := #{rules := Rules}} = hocon_schema:richmap_to_map(CheckConf),
|
#{authorization := #{rules := Rules}} = hocon_schema:richmap_to_map(CheckConf),
|
||||||
Rules.
|
Rules.
|
||||||
|
|
||||||
|
find_rule_by_id(Id, Rules) -> find_rule_by_id(Id, Rules, 1).
|
||||||
|
find_rule_by_id(_RuleId, [], _N) -> {error, not_found_rule};
|
||||||
|
find_rule_by_id(RuleId, [ Rule = #{annotations := #{id := Id}} | Tail], N) ->
|
||||||
|
case RuleId =:= Id of
|
||||||
|
true -> {N, Rule};
|
||||||
|
false -> find_rule_by_id(RuleId, Tail, N + 1)
|
||||||
|
end.
|
||||||
|
|
||||||
find_action_in_hooks() ->
|
find_action_in_hooks() ->
|
||||||
Callbacks = emqx_hooks:lookup('client.authorize'),
|
Callbacks = emqx_hooks:lookup('client.authorize'),
|
||||||
[Action] = [Action || {callback,{?MODULE, authorize, _} = Action, _, _} <- Callbacks ],
|
[Action] = [Action || {callback,{?MODULE, authorize, _} = Action, _, _} <- Callbacks ],
|
||||||
|
@ -99,6 +143,19 @@ find_action_in_hooks() ->
|
||||||
gen_id(Type) ->
|
gen_id(Type) ->
|
||||||
iolist_to_binary([io_lib:format("~s_~s",[?APP, Type]), "_", integer_to_list(erlang:system_time())]).
|
iolist_to_binary([io_lib:format("~s_~s",[?APP, Type]), "_", integer_to_list(erlang:system_time())]).
|
||||||
|
|
||||||
|
create_resource(#{type := DB,
|
||||||
|
config := Config,
|
||||||
|
annotations := #{id := ResourceID}}) ->
|
||||||
|
case emqx_resource:update(
|
||||||
|
ResourceID,
|
||||||
|
list_to_existing_atom(io_lib:format("~s_~s",[emqx_connector, DB])),
|
||||||
|
Config,
|
||||||
|
[])
|
||||||
|
of
|
||||||
|
{ok, _} -> ResourceID;
|
||||||
|
{error, already_created} -> ResourceID;
|
||||||
|
{error, Reason} -> {error, Reason}
|
||||||
|
end;
|
||||||
create_resource(#{type := DB,
|
create_resource(#{type := DB,
|
||||||
config := Config}) ->
|
config := Config}) ->
|
||||||
ResourceID = gen_id(DB),
|
ResourceID = gen_id(DB),
|
||||||
|
@ -116,13 +173,19 @@ create_resource(#{type := DB,
|
||||||
init_rule(#{topics := Topics,
|
init_rule(#{topics := Topics,
|
||||||
action := Action,
|
action := Action,
|
||||||
permission := Permission,
|
permission := Permission,
|
||||||
principal := Principal
|
principal := Principal,
|
||||||
} = Rule) when ?ALLOW_DENY(Permission), ?PUBSUB(Action), is_list(Topics) ->
|
annotations := #{id := Id}
|
||||||
|
} = Rule) when ?ALLOW_DENY(Permission), ?PUBSUB(Action), is_list(Topics) ->
|
||||||
Rule#{annotations =>
|
Rule#{annotations =>
|
||||||
#{id => gen_id(simple),
|
#{id => Id,
|
||||||
principal => compile_principal(Principal),
|
principal => compile_principal(Principal),
|
||||||
topics => [compile_topic(Topic) || Topic <- Topics]}
|
topics => [compile_topic(Topic) || Topic <- Topics]}
|
||||||
};
|
};
|
||||||
|
init_rule(#{topics := Topics,
|
||||||
|
action := Action,
|
||||||
|
permission := Permission
|
||||||
|
} = Rule) when ?ALLOW_DENY(Permission), ?PUBSUB(Action), is_list(Topics) ->
|
||||||
|
init_rule(Rule#{annotations =>#{id => gen_id(simple)}});
|
||||||
|
|
||||||
init_rule(#{principal := Principal,
|
init_rule(#{principal := Principal,
|
||||||
enable := true,
|
enable := true,
|
||||||
|
@ -132,7 +195,7 @@ init_rule(#{principal := Principal,
|
||||||
NConfig = maps:merge(Config, #{base_url => maps:remove(query, Url)}),
|
NConfig = maps:merge(Config, #{base_url => maps:remove(query, Url)}),
|
||||||
case create_resource(Rule#{config := NConfig}) of
|
case create_resource(Rule#{config := NConfig}) of
|
||||||
{error, Reason} -> error({load_config_error, Reason});
|
{error, Reason} -> error({load_config_error, Reason});
|
||||||
Id -> Rule#{annotations =>
|
Id -> Rule#{annotations =>
|
||||||
#{id => Id,
|
#{id => Id,
|
||||||
principal => compile_principal(Principal)
|
principal => compile_principal(Principal)
|
||||||
}
|
}
|
||||||
|
@ -146,7 +209,7 @@ init_rule(#{principal := Principal,
|
||||||
DB =:= mongo ->
|
DB =:= mongo ->
|
||||||
case create_resource(Rule) of
|
case create_resource(Rule) of
|
||||||
{error, Reason} -> error({load_config_error, Reason});
|
{error, Reason} -> error({load_config_error, Reason});
|
||||||
Id -> Rule#{annotations =>
|
Id -> Rule#{annotations =>
|
||||||
#{id => Id,
|
#{id => Id,
|
||||||
principal => compile_principal(Principal)
|
principal => compile_principal(Principal)
|
||||||
}
|
}
|
||||||
|
@ -162,7 +225,7 @@ init_rule(#{principal := Principal,
|
||||||
Mod = list_to_existing_atom(io_lib:format("~s_~s",[?APP, DB])),
|
Mod = list_to_existing_atom(io_lib:format("~s_~s",[?APP, DB])),
|
||||||
case create_resource(Rule) of
|
case create_resource(Rule) of
|
||||||
{error, Reason} -> error({load_config_error, Reason});
|
{error, Reason} -> error({load_config_error, Reason});
|
||||||
Id -> Rule#{annotations =>
|
Id -> Rule#{annotations =>
|
||||||
#{id => Id,
|
#{id => Id,
|
||||||
principal => compile_principal(Principal),
|
principal => compile_principal(Principal),
|
||||||
sql => Mod:parse_query(SQL)
|
sql => Mod:parse_query(SQL)
|
||||||
|
|
|
@ -20,13 +20,17 @@
|
||||||
|
|
||||||
-include("emqx_authz.hrl").
|
-include("emqx_authz.hrl").
|
||||||
|
|
||||||
|
-define(EXAMPLE_RETURNED_RULE1,
|
||||||
|
#{principal => <<"all">>,
|
||||||
|
permission => <<"allow">>,
|
||||||
|
action => <<"all">>,
|
||||||
|
topics => [<<"#">>],
|
||||||
|
annotations => #{id => 1}
|
||||||
|
}).
|
||||||
|
|
||||||
|
|
||||||
-define(EXAMPLE_RETURNED_RULES,
|
-define(EXAMPLE_RETURNED_RULES,
|
||||||
#{rules => [ #{principal => <<"all">>,
|
#{rules => [?EXAMPLE_RETURNED_RULE1
|
||||||
permission => <<"allow">>,
|
|
||||||
action => <<"all">>,
|
|
||||||
topics => [<<"#">>],
|
|
||||||
metadata => #{id => 1}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
@ -37,10 +41,12 @@
|
||||||
|
|
||||||
-export([ api_spec/0
|
-export([ api_spec/0
|
||||||
, authorization/2
|
, authorization/2
|
||||||
|
, authorization_once/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
api_spec() ->
|
api_spec() ->
|
||||||
{[ authorization_api()
|
{[ authorization_api(),
|
||||||
|
authorization_api2()
|
||||||
], definitions()}.
|
], definitions()}.
|
||||||
|
|
||||||
definitions() -> emqx_authz_api_schema:definitions().
|
definitions() -> emqx_authz_api_schema:definitions().
|
||||||
|
@ -99,13 +105,79 @@ authorization_api() ->
|
||||||
},
|
},
|
||||||
{"/authorization", Metadata, authorization}.
|
{"/authorization", Metadata, authorization}.
|
||||||
|
|
||||||
|
authorization_api2() ->
|
||||||
|
Metadata = #{
|
||||||
|
get => #{
|
||||||
|
description => "List authorization rules",
|
||||||
|
parameters => [
|
||||||
|
#{
|
||||||
|
name => id,
|
||||||
|
in => path,
|
||||||
|
schema => #{
|
||||||
|
type => string
|
||||||
|
},
|
||||||
|
required => true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
responses => #{
|
||||||
|
<<"200">> => #{
|
||||||
|
description => <<"OK">>,
|
||||||
|
content => #{
|
||||||
|
'application/json' => #{
|
||||||
|
schema => minirest:ref(<<"returned_rules">>),
|
||||||
|
examples => #{
|
||||||
|
rules => #{
|
||||||
|
summary => <<"Rules">>,
|
||||||
|
value => jsx:encode(?EXAMPLE_RETURNED_RULE1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
<<"404">> => #{description => <<"Not Found">>}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
put => #{
|
||||||
|
description => "Update rule",
|
||||||
|
parameters => [
|
||||||
|
#{
|
||||||
|
name => id,
|
||||||
|
in => path,
|
||||||
|
schema => #{
|
||||||
|
type => string
|
||||||
|
},
|
||||||
|
required => true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
requestBody => #{
|
||||||
|
content => #{
|
||||||
|
'application/json' => #{
|
||||||
|
schema => minirest:ref(<<"rules">>),
|
||||||
|
examples => #{
|
||||||
|
simple_rule => #{
|
||||||
|
summary => <<"Rules">>,
|
||||||
|
value => jsx:encode(?EXAMPLE_RULE1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responses => #{
|
||||||
|
<<"201">> => #{description => <<"Created">>},
|
||||||
|
<<"400">> => #{description => <<"Bad Request">>}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{"/authorization/:id", Metadata, authorization_once}.
|
||||||
|
|
||||||
|
|
||||||
authorization(get, _Request) ->
|
authorization(get, _Request) ->
|
||||||
Rules = lists:foldl(fun (#{type := _Type, enable := true, metadata := #{id := Id} = MataData} = Rule, AccIn) ->
|
Rules = lists:foldl(fun (#{type := _Type, enable := true, annotations := #{id := Id} = Annotations} = Rule, AccIn) ->
|
||||||
NRule = case emqx_resource:health_check(Id) of
|
NRule = case emqx_resource:health_check(Id) of
|
||||||
ok ->
|
ok ->
|
||||||
Rule#{metadata => MataData#{status => healthy}};
|
Rule#{annotations => Annotations#{status => healthy}};
|
||||||
_ ->
|
_ ->
|
||||||
Rule#{metadata => MataData#{status => unhealthy}}
|
Rule#{annotations => Annotations#{status => unhealthy}}
|
||||||
end,
|
end,
|
||||||
lists:append(AccIn, [NRule]);
|
lists:append(AccIn, [NRule]);
|
||||||
(Rule, AccIn) ->
|
(Rule, AccIn) ->
|
||||||
|
@ -120,4 +192,29 @@ authorization(post, Request) ->
|
||||||
{error, Reason} -> {400, #{messgae => atom_to_binary(Reason)}}
|
{error, Reason} -> {400, #{messgae => atom_to_binary(Reason)}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
authorization_once(get, Request) ->
|
||||||
|
Id = cowboy_req:binding(id, Request),
|
||||||
|
case emqx_authz:lookup(Id) of
|
||||||
|
{error, Reason} -> {404, #{messgae => atom_to_binary(Reason)}};
|
||||||
|
Rule ->
|
||||||
|
case maps:get(type, Rule, undefined) of
|
||||||
|
undefined -> {200, Rule};
|
||||||
|
_ ->
|
||||||
|
case emqx_resource:health_check(Id) of
|
||||||
|
ok ->
|
||||||
|
{200, Rule#{annotations => #{status => healthy}}};
|
||||||
|
_ ->
|
||||||
|
{200, Rule#{annotations => #{status => unhealthy}}}
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
authorization_once(put, Request) ->
|
||||||
|
RuleId = cowboy_req:binding(id, Request),
|
||||||
|
{ok, Body, _} = cowboy_req:read_body(Request),
|
||||||
|
RawConfig = jsx:decode(Body, [return_maps]),
|
||||||
|
case emqx_authz:update({replace_once, RuleId}, RawConfig) of
|
||||||
|
ok -> {200};
|
||||||
|
{error, Reason} -> {400, #{messgae => atom_to_binary(Reason)}}
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
-include_lib("common_test/include/ct.hrl").
|
-include_lib("common_test/include/ct.hrl").
|
||||||
|
|
||||||
|
-define(CONF_DEFAULT, <<"authorization: {rules: []}">>).
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
emqx_ct:all(?MODULE).
|
emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
@ -29,82 +31,104 @@ groups() ->
|
||||||
[].
|
[].
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
|
ok = emqx_config:init_load(emqx_authz_schema, ?CONF_DEFAULT),
|
||||||
ok = emqx_ct_helpers:start_apps([emqx_authz]),
|
ok = emqx_ct_helpers:start_apps([emqx_authz]),
|
||||||
ok = emqx_config:update([zones, default, authorization, cache, enable], false),
|
ok = emqx_config:update([zones, default, authorization, cache, enable], false),
|
||||||
ok = emqx_config:update([zones, default, authorization, enable], true),
|
ok = emqx_config:update([zones, default, authorization, enable], true),
|
||||||
emqx_authz:update(replace, []),
|
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
end_per_suite(_Config) ->
|
end_per_suite(_Config) ->
|
||||||
emqx_ct_helpers:stop_apps([emqx_authz]).
|
ok = emqx_authz:update(replace, []),
|
||||||
|
emqx_ct_helpers:stop_apps([emqx_authz]),
|
||||||
|
ok.
|
||||||
|
|
||||||
-define(RULE1, #{principal => all,
|
-define(RULE1, #{<<"principal">> => <<"all">>,
|
||||||
topics => [<<"#">>],
|
<<"topics">> => [<<"#">>],
|
||||||
action => all,
|
<<"action">> => <<"all">>,
|
||||||
permission => deny}
|
<<"permission">> => <<"deny">>}
|
||||||
).
|
).
|
||||||
-define(RULE2, #{principal =>
|
-define(RULE2, #{<<"principal">> =>
|
||||||
#{ipaddress => <<"127.0.0.1">>},
|
#{<<"ipaddress">> => <<"127.0.0.1">>},
|
||||||
topics =>
|
<<"topics">> =>
|
||||||
[#{eq => <<"#">>},
|
[#{<<"eq">> => <<"#">>},
|
||||||
#{eq => <<"+">>}
|
#{<<"eq">> => <<"+">>}
|
||||||
] ,
|
] ,
|
||||||
action => all,
|
<<"action">> => <<"all">>,
|
||||||
permission => allow}
|
<<"permission">> => <<"allow">>}
|
||||||
).
|
).
|
||||||
-define(RULE3,#{principal =>
|
-define(RULE3,#{<<"principal">> =>
|
||||||
#{'and' => [#{username => "^test?"},
|
#{<<"and">> => [#{<<"username">> => <<"^test?">>},
|
||||||
#{clientid => "^test?"}
|
#{<<"clientid">> => <<"^test?">>}
|
||||||
]},
|
]},
|
||||||
topics => [<<"test">>],
|
<<"topics">> => [<<"test">>],
|
||||||
action => publish,
|
<<"action">> => <<"publish">>,
|
||||||
permission => allow}
|
<<"permission">> => <<"allow">>}
|
||||||
).
|
).
|
||||||
-define(RULE4,#{principal =>
|
-define(RULE4,#{<<"principal">> =>
|
||||||
#{'or' => [#{username => <<"^test">>},
|
#{<<"or">> => [#{<<"username">> => <<"^test">>},
|
||||||
#{clientid => <<"test?">>}
|
#{<<"clientid">> => <<"test?">>}
|
||||||
]},
|
]},
|
||||||
topics => [<<"%u">>,<<"%c">>],
|
<<"topics">> => [<<"%u">>,<<"%c">>],
|
||||||
action => publish,
|
<<"action">> => <<"publish">>,
|
||||||
permission => deny}
|
<<"permission">> => <<"deny">>}
|
||||||
).
|
).
|
||||||
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Testcases
|
%% Testcases
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
t_init_rule(_) ->
|
|
||||||
?assertMatch(#{annotations := #{id := _ID,
|
t_update_rule(_) ->
|
||||||
principal := all,
|
ok = emqx_authz:update(replace, [?RULE2]),
|
||||||
topics := [['#']]}
|
ok = emqx_authz:update(head, [?RULE1]),
|
||||||
}, emqx_authz:init_rule(?RULE1)),
|
ok = emqx_authz:update(tail, [?RULE3]),
|
||||||
?assertMatch(#{annotations := #{principal :=
|
|
||||||
#{ipaddress := {{127,0,0,1},{127,0,0,1},32}},
|
Lists1 = emqx_authz:check_rules([?RULE1, ?RULE2, ?RULE3]),
|
||||||
topics := [#{eq := ['#']},
|
?assertMatch(Lists1, emqx_config:get([authorization, rules], [])),
|
||||||
#{eq := ['+']}],
|
|
||||||
id := _ID}
|
[#{annotations := #{id := Id1,
|
||||||
}, emqx_authz:init_rule(?RULE2)),
|
principal := all,
|
||||||
?assertMatch(#{annotations :=
|
topics := [['#']]}
|
||||||
#{principal :=
|
},
|
||||||
|
#{annotations := #{id := Id2,
|
||||||
|
principal := #{ipaddress := {{127,0,0,1},{127,0,0,1},32}},
|
||||||
|
topics := [#{eq := ['#']}, #{eq := ['+']}]}
|
||||||
|
},
|
||||||
|
#{annotations := #{id := Id3,
|
||||||
|
principal :=
|
||||||
#{'and' := [#{username := {re_pattern, _, _, _, _}},
|
#{'and' := [#{username := {re_pattern, _, _, _, _}},
|
||||||
#{clientid := {re_pattern, _, _, _, _}}
|
#{clientid := {re_pattern, _, _, _, _}}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
topics := [[<<"test">>]],
|
topics := [[<<"test">>]]}
|
||||||
id := _ID}
|
}
|
||||||
}, emqx_authz:init_rule(?RULE3)),
|
] = emqx_authz:lookup(),
|
||||||
?assertMatch(#{annotations :=
|
|
||||||
#{principal :=
|
ok = emqx_authz:update({replace_once, Id3}, ?RULE4),
|
||||||
#{'or' := [#{username := {re_pattern, _, _, _, _}},
|
Lists2 = emqx_authz:check_rules([?RULE1, ?RULE2, ?RULE4]),
|
||||||
#{clientid := {re_pattern, _, _, _, _}}
|
?assertMatch(Lists2, emqx_config:get([authorization, rules], [])),
|
||||||
]
|
|
||||||
},
|
[#{annotations := #{id := Id1,
|
||||||
topics := [#{pattern := [<<"%u">>]},
|
principal := all,
|
||||||
#{pattern := [<<"%c">>]}
|
topics := [['#']]}
|
||||||
],
|
},
|
||||||
id := _ID}
|
#{annotations := #{id := Id2,
|
||||||
}, emqx_authz:init_rule(?RULE4)),
|
principal := #{ipaddress := {{127,0,0,1},{127,0,0,1},32}},
|
||||||
ok.
|
topics := [#{eq := ['#']},
|
||||||
|
#{eq := ['+']}]}
|
||||||
|
},
|
||||||
|
#{annotations := #{id := Id3,
|
||||||
|
principal :=
|
||||||
|
#{'or' := [#{username := {re_pattern, _, _, _, _}},
|
||||||
|
#{clientid := {re_pattern, _, _, _, _}}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
topics := [#{pattern := [<<"%u">>]},
|
||||||
|
#{pattern := [<<"%c">>]}
|
||||||
|
]}
|
||||||
|
}
|
||||||
|
] = emqx_authz:lookup(),
|
||||||
|
|
||||||
|
ok = emqx_authz:update(replace, []).
|
||||||
|
|
||||||
t_authz(_) ->
|
t_authz(_) ->
|
||||||
ClientInfo1 = #{clientid => <<"test">>,
|
ClientInfo1 = #{clientid => <<"test">>,
|
||||||
|
@ -132,10 +156,10 @@ t_authz(_) ->
|
||||||
listener => mqtt_tcp
|
listener => mqtt_tcp
|
||||||
},
|
},
|
||||||
|
|
||||||
Rules1 = [emqx_authz:init_rule(Rule) || Rule <- [?RULE1, ?RULE2]],
|
Rules1 = [emqx_authz:init_rule(Rule) || Rule <- emqx_authz:check_rules([?RULE1, ?RULE2])],
|
||||||
Rules2 = [emqx_authz:init_rule(Rule) || Rule <- [?RULE2, ?RULE1]],
|
Rules2 = [emqx_authz:init_rule(Rule) || Rule <- emqx_authz:check_rules([?RULE2, ?RULE1])],
|
||||||
Rules3 = [emqx_authz:init_rule(Rule) || Rule <- [?RULE3, ?RULE4]],
|
Rules3 = [emqx_authz:init_rule(Rule) || Rule <- emqx_authz:check_rules([?RULE3, ?RULE4])],
|
||||||
Rules4 = [emqx_authz:init_rule(Rule) || Rule <- [?RULE4, ?RULE1]],
|
Rules4 = [emqx_authz:init_rule(Rule) || Rule <- emqx_authz:check_rules([?RULE4, ?RULE1])],
|
||||||
|
|
||||||
?assertEqual({stop, deny},
|
?assertEqual({stop, deny},
|
||||||
emqx_authz:authorize(ClientInfo1, subscribe, <<"#">>, deny, [])),
|
emqx_authz:authorize(ClientInfo1, subscribe, <<"#">>, deny, [])),
|
||||||
|
|
|
@ -18,33 +18,26 @@
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
-compile(export_all).
|
-compile(export_all).
|
||||||
|
|
||||||
% -include("emqx_authz.hrl").
|
-include("emqx_authz.hrl").
|
||||||
% -include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
% -include_lib("common_test/include/ct.hrl").
|
-include_lib("common_test/include/ct.hrl").
|
||||||
|
|
||||||
% -import(emqx_ct_http, [ request_api/3
|
-import(emqx_ct_http, [ request_api/3
|
||||||
% , request_api/5
|
, request_api/5
|
||||||
% , get_http_data/1
|
, get_http_data/1
|
||||||
% , create_default_app/0
|
, create_default_app/0
|
||||||
% , delete_default_app/0
|
, delete_default_app/0
|
||||||
% , default_auth_header/0
|
, default_auth_header/0
|
||||||
% ]).
|
]).
|
||||||
|
|
||||||
% -define(HOST, "http://127.0.0.1:8081/").
|
-define(HOST, "http://127.0.0.1:8081/").
|
||||||
% -define(API_VERSION, "v4").
|
-define(API_VERSION, "v5").
|
||||||
% -define(BASE_PATH, "api").
|
-define(BASE_PATH, "api/authorization").
|
||||||
|
|
||||||
-define(CONF_DEFAULT, <<"""
|
-define(CONF_DEFAULT, <<"authorization: {rules: []}">>).
|
||||||
authorization:{
|
|
||||||
rules: [
|
|
||||||
]
|
|
||||||
}
|
|
||||||
""">>).
|
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
%% TODO: V5 API
|
emqx_ct:all(?MODULE).
|
||||||
%% emqx_ct:all(?MODULE).
|
|
||||||
[t_api_unit_test].
|
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
[].
|
[].
|
||||||
|
@ -52,12 +45,15 @@ groups() ->
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
ok = emqx_config:init_load(emqx_authz_schema, ?CONF_DEFAULT),
|
ok = emqx_config:init_load(emqx_authz_schema, ?CONF_DEFAULT),
|
||||||
ok = emqx_ct_helpers:start_apps([emqx_authz]),
|
ok = emqx_ct_helpers:start_apps([emqx_authz]),
|
||||||
|
ok = emqx_config:update([zones, default, authorization, cache, enable], false),
|
||||||
|
ok = emqx_config:update([zones, default, authorization, enable], true),
|
||||||
|
|
||||||
%create_default_app(),
|
create_default_app(),
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
end_per_suite(_Config) ->
|
end_per_suite(_Config) ->
|
||||||
ok = emqx_authz:update(replace, []),
|
ok = emqx_authz:update(replace, []),
|
||||||
|
delete_default_app(),
|
||||||
emqx_ct_helpers:stop_apps([emqx_authz]),
|
emqx_ct_helpers:stop_apps([emqx_authz]),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
@ -77,29 +73,12 @@ end_per_suite(_Config) ->
|
||||||
% set_special_configs(_App) ->
|
% set_special_configs(_App) ->
|
||||||
% ok.
|
% ok.
|
||||||
|
|
||||||
% %%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
% %% Testcases
|
%% Testcases
|
||||||
% %%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
t_api_unit_test(_Config) ->
|
% t_api_unit_test(_Config) ->
|
||||||
%% TODO: Decode from JSON or HOCON, instead of hand-crafting decode result
|
% %% TODO: Decode from JSON or HOCON, instead of hand-crafting decode result
|
||||||
Rule1 = #{<<"principal">> =>
|
|
||||||
#{<<"and">> => [#{<<"username">> => <<"^test?">>},
|
|
||||||
#{<<"clientid">> => <<"^test?">>}
|
|
||||||
]},
|
|
||||||
<<"action">> => <<"subscribe">>,
|
|
||||||
<<"topics">> => [<<"%u">>],
|
|
||||||
<<"permission">> => <<"allow">>
|
|
||||||
},
|
|
||||||
ok = emqx_authz_api:push_authz(#{}, Rule1),
|
|
||||||
[#{action := subscribe,
|
|
||||||
permission := allow,
|
|
||||||
principal :=
|
|
||||||
#{'and' := [#{username := <<"^test?">>},
|
|
||||||
#{clientid := <<"^test?">>}]},
|
|
||||||
topics := [<<"%u">>]}] = emqx_config:get([authorization, rules]).
|
|
||||||
|
|
||||||
% t_api(_Config) ->
|
|
||||||
% Rule1 = #{<<"principal">> =>
|
% Rule1 = #{<<"principal">> =>
|
||||||
% #{<<"and">> => [#{<<"username">> => <<"^test?">>},
|
% #{<<"and">> => [#{<<"username">> => <<"^test?">>},
|
||||||
% #{<<"clientid">> => <<"^test?">>}
|
% #{<<"clientid">> => <<"^test?">>}
|
||||||
|
@ -108,53 +87,89 @@ t_api_unit_test(_Config) ->
|
||||||
% <<"topics">> => [<<"%u">>],
|
% <<"topics">> => [<<"%u">>],
|
||||||
% <<"permission">> => <<"allow">>
|
% <<"permission">> => <<"allow">>
|
||||||
% },
|
% },
|
||||||
% {ok, _} = request_http_rest_add(["authz/push"], #{rules => [Rule1]}),
|
% ok = emqx_authz_api:push_authz(#{}, Rule1),
|
||||||
% {ok, Result1} = request_http_rest_lookup(["authz"]),
|
% [#{action := subscribe,
|
||||||
% ?assertMatch([Rule1 | _ ], get_http_data(Result1)),
|
% permission := allow,
|
||||||
|
% principal :=
|
||||||
|
% #{'and' := [#{username := <<"^test?">>},
|
||||||
|
% #{clientid := <<"^test?">>}]},
|
||||||
|
% topics := [<<"%u">>]}] = emqx_config:get([authorization, rules]).
|
||||||
|
|
||||||
% Rule2 = #{<<"principal">> => #{<<"ipaddress">> => <<"127.0.0.1">>},
|
t_post(_) ->
|
||||||
% <<"action">> => <<"publish">>,
|
Rules1 = request(get, uri(), []),
|
||||||
% <<"topics">> => [#{<<"eq">> => <<"#">>},
|
ct:print("============~p~n",[Rules1]),
|
||||||
% #{<<"eq">> => <<"+">>}
|
ok.
|
||||||
% ],
|
|
||||||
% <<"permission">> => <<"deny">>
|
|
||||||
% },
|
|
||||||
% {ok, _} = request_http_rest_add(["authz/append"], #{rules => [Rule2]}),
|
|
||||||
% {ok, Result2} = request_http_rest_lookup(["authz"]),
|
|
||||||
% ?assertEqual(Rule2#{<<"principal">> => #{<<"ipaddress">> => "127.0.0.1"}},
|
|
||||||
% lists:last(get_http_data(Result2))),
|
|
||||||
|
|
||||||
% {ok, _} = request_http_rest_update(["authz"], #{rules => []}),
|
t_api(_Config) ->
|
||||||
% {ok, Result3} = request_http_rest_lookup(["authz"]),
|
Rule1 = #{<<"principal">> =>
|
||||||
% ?assertEqual([], get_http_data(Result3)),
|
#{<<"and">> => [#{<<"username">> => <<"^test?">>},
|
||||||
% ok.
|
#{<<"clientid">> => <<"^test?">>}
|
||||||
|
]},
|
||||||
|
<<"action">> => <<"subscribe">>,
|
||||||
|
<<"topics">> => [<<"%u">>],
|
||||||
|
<<"permission">> => <<"allow">>
|
||||||
|
},
|
||||||
|
{ok, _} = request_http_rest_add(["authz/push"], #{rules => [Rule1]}),
|
||||||
|
{ok, Result1} = request_http_rest_lookup(["authz"]),
|
||||||
|
?assertMatch([Rule1 | _ ], get_http_data(Result1)),
|
||||||
|
|
||||||
% %%--------------------------------------------------------------------
|
Rule2 = #{<<"principal">> => #{<<"ipaddress">> => <<"127.0.0.1">>},
|
||||||
% %% HTTP Request
|
<<"action">> => <<"publish">>,
|
||||||
% %%--------------------------------------------------------------------
|
<<"topics">> => [#{<<"eq">> => <<"#">>},
|
||||||
|
#{<<"eq">> => <<"+">>}
|
||||||
|
],
|
||||||
|
<<"permission">> => <<"deny">>
|
||||||
|
},
|
||||||
|
{ok, _} = request_http_rest_add(["authz/append"], #{rules => [Rule2]}),
|
||||||
|
{ok, Result2} = request_http_rest_lookup(["authz"]),
|
||||||
|
?assertEqual(Rule2#{<<"principal">> => #{<<"ipaddress">> => "127.0.0.1"}},
|
||||||
|
lists:last(get_http_data(Result2))),
|
||||||
|
|
||||||
% request_http_rest_list(Path) ->
|
{ok, _} = request_http_rest_update(["authz"], #{rules => []}),
|
||||||
% request_api(get, uri(Path), default_auth_header()).
|
{ok, Result3} = request_http_rest_lookup(["authz"]),
|
||||||
|
?assertEqual([], get_http_data(Result3)),
|
||||||
|
ok.
|
||||||
|
|
||||||
% request_http_rest_lookup(Path) ->
|
%%--------------------------------------------------------------------
|
||||||
% request_api(get, uri([Path]), default_auth_header()).
|
%% HTTP Request
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
% request_http_rest_add(Path, Params) ->
|
request(Method, Url, Body) ->
|
||||||
% request_api(post, uri(Path), [], default_auth_header(), Params).
|
Request = case Body of
|
||||||
|
[] -> {Url, [{"username", "admin"}, {"password", "public"}]};
|
||||||
|
_ -> {Url, [{"username", "admin"}, {"password", "public"}], "application/json", Body}
|
||||||
|
end,
|
||||||
|
case httpc:request(Method, Request, [], [{body_format, binary}]) of
|
||||||
|
{error, socket_closed_remotely} ->
|
||||||
|
{error, socket_closed_remotely};
|
||||||
|
{ok, {{"HTTP/1.1", Code, _}, _Headers, Return} } ->
|
||||||
|
{ok, Code, Return};
|
||||||
|
{ok, {Reason, _, _}} ->
|
||||||
|
{error, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
% request_http_rest_update(Path, Params) ->
|
request_http_rest_list(Path) ->
|
||||||
% request_api(put, uri([Path]), [], default_auth_header(), Params).
|
request_api(get, uri(Path), default_auth_header()).
|
||||||
|
|
||||||
% request_http_rest_delete(Login) ->
|
request_http_rest_lookup(Path) ->
|
||||||
% request_api(delete, uri([Login]), default_auth_header()).
|
request_api(get, uri([Path]), default_auth_header()).
|
||||||
|
|
||||||
% uri() -> uri([]).
|
request_http_rest_add(Path, Params) ->
|
||||||
% uri(Parts) when is_list(Parts) ->
|
request_api(post, uri(Path), [], default_auth_header(), Params).
|
||||||
% NParts = [b2l(E) || E <- Parts],
|
|
||||||
% ?HOST ++ filename:join([?BASE_PATH, ?API_VERSION | NParts]).
|
|
||||||
|
|
||||||
% %% @private
|
request_http_rest_update(Path, Params) ->
|
||||||
% b2l(B) when is_binary(B) ->
|
request_api(put, uri([Path]), [], default_auth_header(), Params).
|
||||||
% binary_to_list(B);
|
|
||||||
% b2l(L) when is_list(L) ->
|
request_http_rest_delete(Login) ->
|
||||||
% L.
|
request_api(delete, uri([Login]), default_auth_header()).
|
||||||
|
|
||||||
|
uri() -> uri([]).
|
||||||
|
uri(Parts) when is_list(Parts) ->
|
||||||
|
NParts = [b2l(E) || E <- Parts],
|
||||||
|
?HOST ++ filename:join([?BASE_PATH, ?API_VERSION | NParts]).
|
||||||
|
|
||||||
|
%% @private
|
||||||
|
b2l(B) when is_binary(B) ->
|
||||||
|
binary_to_list(B);
|
||||||
|
b2l(L) when is_list(L) ->
|
||||||
|
L.
|
||||||
|
|
|
@ -31,6 +31,7 @@ groups() ->
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]),
|
meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]),
|
||||||
meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end),
|
meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end),
|
||||||
|
meck:expect(emqx_resource, remove, fun(_) -> ok end ),
|
||||||
|
|
||||||
ok = emqx_ct_helpers:start_apps([emqx_authz]),
|
ok = emqx_ct_helpers:start_apps([emqx_authz]),
|
||||||
ok = emqx_config:update([zones, default, authorization, cache, enable], false),
|
ok = emqx_config:update([zones, default, authorization, cache, enable], false),
|
||||||
|
|
|
@ -31,6 +31,7 @@ groups() ->
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]),
|
meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]),
|
||||||
meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ),
|
meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ),
|
||||||
|
meck:expect(emqx_resource, remove, fun(_) -> ok end ),
|
||||||
|
|
||||||
ok = emqx_ct_helpers:start_apps([emqx_authz]),
|
ok = emqx_ct_helpers:start_apps([emqx_authz]),
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ groups() ->
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]),
|
meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]),
|
||||||
meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ),
|
meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ),
|
||||||
|
meck:expect(emqx_resource, remove, fun(_) -> ok end ),
|
||||||
|
|
||||||
ok = emqx_ct_helpers:start_apps([emqx_authz]),
|
ok = emqx_ct_helpers:start_apps([emqx_authz]),
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ groups() ->
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]),
|
meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]),
|
||||||
meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ),
|
meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ),
|
||||||
|
meck:expect(emqx_resource, remove, fun(_) -> ok end ),
|
||||||
|
|
||||||
ok = emqx_ct_helpers:start_apps([emqx_authz]),
|
ok = emqx_ct_helpers:start_apps([emqx_authz]),
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue