feat: add authn, authz status api
This commit is contained in:
parent
e6fcef16ba
commit
d1857ba454
|
@ -49,8 +49,10 @@
|
|||
|
||||
-export([ authenticators/2
|
||||
, authenticator/2
|
||||
, authenticator_status/2
|
||||
, listener_authenticators/2
|
||||
, listener_authenticator/2
|
||||
, listener_authenticator_status/2
|
||||
, authenticator_move/2
|
||||
, listener_authenticator_move/2
|
||||
, authenticator_import_users/2
|
||||
|
@ -88,6 +90,7 @@ api_spec() ->
|
|||
|
||||
paths() -> [ "/authentication"
|
||||
, "/authentication/:id"
|
||||
, "/authentication/:id/status"
|
||||
, "/authentication/:id/move"
|
||||
, "/authentication/:id/import_users"
|
||||
, "/authentication/:id/users"
|
||||
|
@ -95,6 +98,7 @@ paths() -> [ "/authentication"
|
|||
|
||||
, "/listeners/:listener_id/authentication"
|
||||
, "/listeners/:listener_id/authentication/:id"
|
||||
, "/listeners/:listener_id/authentication/:id/status"
|
||||
, "/listeners/:listener_id/authentication/:id/move"
|
||||
, "/listeners/:listener_id/authentication/:id/import_users"
|
||||
, "/listeners/:listener_id/authentication/:id/users"
|
||||
|
@ -213,6 +217,22 @@ schema("/authentication/:id") ->
|
|||
}
|
||||
};
|
||||
|
||||
schema("/authentication/:id/status") ->
|
||||
#{
|
||||
'operationId' => authenticator_status,
|
||||
get => #{
|
||||
tags => ?API_TAGS_GLOBAL,
|
||||
description => <<"Get authenticator status from global authentication chain">>,
|
||||
parameters => [param_auth_id()],
|
||||
responses => #{
|
||||
200 => emqx_dashboard_swagger:schema_with_examples(
|
||||
hoconsc:ref(emqx_authn_schema, "metrics_status_fields"),
|
||||
status_metrics_example()),
|
||||
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
schema("/listeners/:listener_id/authentication") ->
|
||||
#{
|
||||
'operationId' => listener_authenticators,
|
||||
|
@ -285,6 +305,21 @@ schema("/listeners/:listener_id/authentication/:id") ->
|
|||
}
|
||||
};
|
||||
|
||||
schema("/listeners/:listener_id/authentication/:id/status") ->
|
||||
#{
|
||||
'operationId' => listener_authenticator_status,
|
||||
get => #{
|
||||
tags => ?API_TAGS_SINGLE,
|
||||
description => <<"Get authenticator status from listener authentication chain">>,
|
||||
parameters => [param_listener_id(), param_auth_id()],
|
||||
responses => #{
|
||||
200 => emqx_dashboard_swagger:schema_with_examples(
|
||||
hoconsc:ref(emqx_authn_schema, "metrics_status_fields"),
|
||||
status_metrics_example()),
|
||||
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
schema("/authentication/:id/move") ->
|
||||
#{
|
||||
|
@ -560,6 +595,9 @@ authenticator(put, #{bindings := #{id := AuthenticatorID}, body := Config}) ->
|
|||
authenticator(delete, #{bindings := #{id := AuthenticatorID}}) ->
|
||||
delete_authenticator([authentication], ?GLOBAL, AuthenticatorID).
|
||||
|
||||
authenticator_status(get, #{bindings := #{id := AuthenticatorID}}) ->
|
||||
lookup_from_all_nodes(?GLOBAL, AuthenticatorID).
|
||||
|
||||
listener_authenticators(post, #{bindings := #{listener_id := ListenerID}, body := Config}) ->
|
||||
with_listener(ListenerID,
|
||||
fun(Type, Name, ChainName) ->
|
||||
|
@ -599,6 +637,13 @@ listener_authenticator(delete,
|
|||
AuthenticatorID)
|
||||
end).
|
||||
|
||||
listener_authenticator_status(get,
|
||||
#{bindings := #{listener_id := ListenerID, id := AuthenticatorID}}) ->
|
||||
with_listener(ListenerID,
|
||||
fun(_, _, ChainName) ->
|
||||
lookup_from_all_nodes(ChainName, AuthenticatorID)
|
||||
end).
|
||||
|
||||
authenticator_move(post,
|
||||
#{bindings := #{id := AuthenticatorID},
|
||||
body := #{<<"position">> := Position}}) ->
|
||||
|
@ -797,16 +842,16 @@ lookup_from_all_nodes(ChainName, AuthenticatorID) ->
|
|||
MKMap = fun (Name) -> fun ({Key, Val}) -> #{ node => Key, Name => Val } end end,
|
||||
HelpFun = fun (M, Name) -> lists:map(MKMap(Name), maps:to_list(M)) end,
|
||||
case AggregateStatus of
|
||||
empty_metrics_and_status -> {ok, #{}};
|
||||
_ -> {ok, #{node_status => HelpFun(StatusMap, status),
|
||||
node_metrics => HelpFun(maps:map(Fun, MetricsMap), metrics),
|
||||
status => AggregateStatus,
|
||||
metrics => restructure_map(AggregateMetrics)
|
||||
}
|
||||
empty_metrics_and_status -> serialize_error(unsupported_operation);
|
||||
_ -> {200, #{node_status => HelpFun(StatusMap, status),
|
||||
node_metrics => HelpFun(maps:map(Fun, MetricsMap), metrics),
|
||||
status => AggregateStatus,
|
||||
metrics => restructure_map(AggregateMetrics)
|
||||
}
|
||||
}
|
||||
end;
|
||||
{error, ErrL} ->
|
||||
{error, error_msg('INTERNAL_ERROR', ErrL)}
|
||||
serialize_error(ErrL)
|
||||
end.
|
||||
|
||||
aggregate_status([]) -> empty_metrics_and_status;
|
||||
|
@ -865,12 +910,6 @@ restructure_map(#{counters := #{failed := Failed, matched := Match, success := S
|
|||
restructure_map(Error) ->
|
||||
Error.
|
||||
|
||||
error_msg(Code, Msg) ->
|
||||
#{code => Code, message => bin(io_lib:format("~p", [Msg]))}.
|
||||
|
||||
bin(S) when is_list(S) ->
|
||||
list_to_binary(S).
|
||||
|
||||
is_ok(ResL) ->
|
||||
case lists:filter(fun({ok, _}) -> false; (_) -> true end, ResL) of
|
||||
[] -> {ok, [Res || {ok, Res} <- ResL]};
|
||||
|
@ -1169,6 +1208,31 @@ authenticator_examples() ->
|
|||
}
|
||||
}.
|
||||
|
||||
status_metrics_example() ->
|
||||
#{ metrics => #{ matched => 0,
|
||||
success => 0,
|
||||
failed => 0,
|
||||
rate => 0.0,
|
||||
rate_last5m => 0.0,
|
||||
rate_max => 0.0
|
||||
},
|
||||
node_metrics => [ #{node => node(),
|
||||
metrics => #{ matched => 0,
|
||||
success => 0,
|
||||
failed => 0,
|
||||
rate => 0.0,
|
||||
rate_last5m => 0.0,
|
||||
rate_max => 0.0
|
||||
}
|
||||
}
|
||||
],
|
||||
status => connected,
|
||||
node_status => [ #{node => node(),
|
||||
status => connected
|
||||
}
|
||||
]
|
||||
}.
|
||||
|
||||
request_user_create_examples() ->
|
||||
#{
|
||||
regular_user => #{
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
-module(emqx_authn_schema).
|
||||
|
||||
-include_lib("typerefl/include/types.hrl").
|
||||
-import(hoconsc, [mk/2, ref/2]).
|
||||
|
||||
-export([ common_fields/0
|
||||
, roots/0
|
||||
|
@ -29,7 +30,6 @@
|
|||
|
||||
roots() -> [].
|
||||
|
||||
fields(_) -> [].
|
||||
|
||||
common_fields() ->
|
||||
[ {enable, fun enable/1}
|
||||
|
@ -59,3 +59,40 @@ mechanism(Name) ->
|
|||
backend(Name) ->
|
||||
hoconsc:mk(hoconsc:enum([Name]),
|
||||
#{required => true}).
|
||||
|
||||
fields("metrics_status_fields") ->
|
||||
[ {"metrics", mk(ref(?MODULE, "metrics"), #{desc => "The metrics of the resource"})}
|
||||
, {"node_metrics", mk(hoconsc:array(ref(?MODULE, "node_metrics")),
|
||||
#{ desc => "The metrics of the resource for each node"
|
||||
})}
|
||||
, {"status", mk(status(), #{desc => "The status of the resource"})}
|
||||
, {"node_status", mk(hoconsc:array(ref(?MODULE, "node_status")),
|
||||
#{ desc => "The status of the resource for each node"
|
||||
})}
|
||||
];
|
||||
|
||||
fields("metrics") ->
|
||||
[ {"matched", mk(integer(), #{desc => "Count of this resource is queried"})}
|
||||
, {"success", mk(integer(), #{desc => "Count of query success"})}
|
||||
, {"failed", mk(integer(), #{desc => "Count of query failed"})}
|
||||
, {"rate", mk(float(), #{desc => "The rate of matched, times/second"})}
|
||||
, {"rate_max", mk(float(), #{desc => "The max rate of matched, times/second"})}
|
||||
, {"rate_last5m", mk(float(),
|
||||
#{desc => "The average rate of matched in the last 5 minutes, times/second"})}
|
||||
];
|
||||
|
||||
fields("node_metrics") ->
|
||||
[ node_name()
|
||||
, {"metrics", mk(ref(?MODULE, "metrics"), #{})}
|
||||
];
|
||||
|
||||
fields("node_status") ->
|
||||
[ node_name()
|
||||
, {"status", mk(status(), #{})}
|
||||
].
|
||||
|
||||
status() ->
|
||||
hoconsc:enum([connected, disconnected, connecting]).
|
||||
|
||||
node_name() ->
|
||||
{"node", mk(binary(), #{desc => "The node name", example => "emqx@127.0.0.1"})}.
|
||||
|
|
|
@ -167,30 +167,34 @@ test_authenticator(PathPrefix) ->
|
|||
{ok, 200, _} = request(
|
||||
get,
|
||||
uri(PathPrefix ++ [?CONF_NS, "password_based:http"])),
|
||||
%% {ok, RList} = emqx_json:safe_decode(Res),
|
||||
%% Snd = fun ({_, Val}) -> Val end,
|
||||
%% LookupVal = fun LookupV(List, RestJson) ->
|
||||
%% case List of
|
||||
%% [Name] -> Snd(lists:keyfind(Name, 1, RestJson));
|
||||
%% [Name | NS] -> LookupV(NS, Snd(lists:keyfind(Name, 1, RestJson)))
|
||||
%% end
|
||||
%% end,
|
||||
%% LookFun = fun (List) -> LookupVal(List, RList) end,
|
||||
%% MetricsList = [{<<"failed">>, 0},
|
||||
%% {<<"matched">>, 0},
|
||||
%% {<<"rate">>, 0.0},
|
||||
%% {<<"rate_last5m">>, 0.0},
|
||||
%% {<<"rate_max">>, 0.0},
|
||||
%% {<<"success">>, 0}],
|
||||
%% EqualFun = fun ({M, V}) ->
|
||||
%% ?assertEqual(V, LookFun([<<"metrics">>,
|
||||
%% M]
|
||||
%% )
|
||||
%% ) end,
|
||||
%% lists:map(EqualFun, MetricsList),
|
||||
%% ?assertEqual(<<"connected">>,
|
||||
%% LookFun([<<"status">>
|
||||
%% ])),
|
||||
|
||||
{ok, 200, Res} = request(
|
||||
get,
|
||||
uri(PathPrefix ++ [?CONF_NS, "password_based:http", "status"])),
|
||||
{ok, RList} = emqx_json:safe_decode(Res),
|
||||
Snd = fun ({_, Val}) -> Val end,
|
||||
LookupVal = fun LookupV(List, RestJson) ->
|
||||
case List of
|
||||
[Name] -> Snd(lists:keyfind(Name, 1, RestJson));
|
||||
[Name | NS] -> LookupV(NS, Snd(lists:keyfind(Name, 1, RestJson)))
|
||||
end
|
||||
end,
|
||||
LookFun = fun (List) -> LookupVal(List, RList) end,
|
||||
MetricsList = [{<<"failed">>, 0},
|
||||
{<<"matched">>, 0},
|
||||
{<<"rate">>, 0.0},
|
||||
{<<"rate_last5m">>, 0.0},
|
||||
{<<"rate_max">>, 0.0},
|
||||
{<<"success">>, 0}],
|
||||
EqualFun = fun ({M, V}) ->
|
||||
?assertEqual(V, LookFun([<<"metrics">>,
|
||||
M]
|
||||
)
|
||||
) end,
|
||||
lists:map(EqualFun, MetricsList),
|
||||
?assertEqual(<<"connected">>,
|
||||
LookFun([<<"status">>
|
||||
])),
|
||||
{ok, 404, _} = request(
|
||||
get,
|
||||
uri(PathPrefix ++ [?CONF_NS, "password_based:redis"])),
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
|
||||
-export([ get_raw_sources/0
|
||||
, get_raw_source/1
|
||||
, source_status/2
|
||||
, lookup_from_local_node/1
|
||||
, lookup_from_all_nodes/1
|
||||
]).
|
||||
|
@ -74,6 +75,7 @@ api_spec() ->
|
|||
paths() ->
|
||||
[ "/authorization/sources"
|
||||
, "/authorization/sources/:type"
|
||||
, "/authorization/sources/:type/status"
|
||||
, "/authorization/sources/:type/move"].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -148,6 +150,19 @@ schema("/authorization/sources/:type") ->
|
|||
}
|
||||
}
|
||||
};
|
||||
schema("/authorization/sources/:type/status") ->
|
||||
#{ 'operationId' => source_status
|
||||
, get =>
|
||||
#{ description => <<"Get a authorization source">>
|
||||
, parameters => parameters_field()
|
||||
, responses =>
|
||||
#{ 200 => emqx_dashboard_swagger:schema_with_examples(
|
||||
hoconsc:ref(emqx_authn_schema, "metrics_status_fields"),
|
||||
status_metrics_example())
|
||||
, 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad request">>)
|
||||
}
|
||||
}
|
||||
};
|
||||
schema("/authorization/sources/:type/move") ->
|
||||
#{ 'operationId' => move_source
|
||||
, post =>
|
||||
|
@ -250,6 +265,21 @@ source(put, #{bindings := #{type := Type}, body := Body}) when is_map(Body) ->
|
|||
source(delete, #{bindings := #{type := Type}}) ->
|
||||
update_config({?CMD_DELETE, Type}, #{}).
|
||||
|
||||
source_status(get, #{bindings := #{type := Type}}) ->
|
||||
BinType = atom_to_binary(Type, utf8),
|
||||
case get_raw_source(BinType) of
|
||||
[] -> {400, #{code => <<"BAD_REQUEST">>,
|
||||
message => <<"Not found", BinType/binary>>}};
|
||||
[#{<<"type">> := <<"file">>}] ->
|
||||
{400, #{code => <<"BAD_REQUEST">>,
|
||||
message => <<"Not Support Status">>}};
|
||||
[_] ->
|
||||
case emqx_authz:lookup(Type) of
|
||||
#{annotations := #{id := ResourceId }} -> lookup_from_all_nodes(ResourceId);
|
||||
_ -> {400, #{code => <<"BAD_REQUEST">>, message => <<"Resource Disable">>}}
|
||||
end
|
||||
end.
|
||||
|
||||
move_source(Method, #{bindings := #{type := Type} = Bindings } = Req)
|
||||
when is_atom(Type) ->
|
||||
move_source(Method, Req#{bindings => Bindings#{type => atom_to_binary(Type, utf8)}});
|
||||
|
@ -301,8 +331,9 @@ lookup_from_all_nodes(ResourceId) ->
|
|||
MKMap = fun (Name) -> fun ({Key, Val}) -> #{ node => Key, Name => Val } end end,
|
||||
HelpFun = fun (M, Name) -> lists:map(MKMap(Name), maps:to_list(M)) end,
|
||||
case AggregateStatus of
|
||||
empty_metrics_and_status -> {ok, #{}};
|
||||
_ -> {ok, #{node_status => HelpFun(StatusMap, status),
|
||||
empty_metrics_and_status -> {400, #{code => <<"BAD_REQUEST">>,
|
||||
message => <<"Resource Not Support Status">>}};
|
||||
_ -> {200, #{node_status => HelpFun(StatusMap, status),
|
||||
node_metrics => HelpFun(maps:map(Fun, MetricsMap), metrics),
|
||||
status => AggregateStatus,
|
||||
metrics => restructure_map(AggregateMetrics)
|
||||
|
@ -310,7 +341,8 @@ lookup_from_all_nodes(ResourceId) ->
|
|||
}
|
||||
end;
|
||||
{error, ErrL} ->
|
||||
{error, error_msg('INTERNAL_ERROR', ErrL)}
|
||||
{400, #{code => <<"BAD_REQUEST">>,
|
||||
message => bin_t(io_lib:format("~p", [ErrL]))}}
|
||||
end.
|
||||
|
||||
aggregate_status([]) -> empty_metrics_and_status;
|
||||
|
@ -369,9 +401,6 @@ restructure_map(#{counters := #{failed := Failed, matched := Match, success := S
|
|||
restructure_map(Error) ->
|
||||
Error.
|
||||
|
||||
error_msg(Code, Msg) ->
|
||||
#{code => Code, message => bin_t(io_lib:format("~p", [Msg]))}.
|
||||
|
||||
bin_t(S) when is_list(S) ->
|
||||
list_to_binary(S).
|
||||
|
||||
|
@ -507,3 +536,28 @@ authz_sources_types(Type) ->
|
|||
emqx_authz_api_schema:authz_sources_types(Type).
|
||||
|
||||
bin(Term) -> erlang:iolist_to_binary(io_lib:format("~p", [Term])).
|
||||
|
||||
status_metrics_example() ->
|
||||
#{ metrics => #{ matched => 0,
|
||||
success => 0,
|
||||
failed => 0,
|
||||
rate => 0.0,
|
||||
rate_last5m => 0.0,
|
||||
rate_max => 0.0
|
||||
},
|
||||
node_metrics => [ #{node => node(),
|
||||
metrics => #{ matched => 0,
|
||||
success => 0,
|
||||
failed => 0,
|
||||
rate => 0.0,
|
||||
rate_last5m => 0.0,
|
||||
rate_max => 0.0
|
||||
}
|
||||
}
|
||||
],
|
||||
status => connected,
|
||||
node_status => [ #{node => node(),
|
||||
status => connected
|
||||
}
|
||||
]
|
||||
}.
|
||||
|
|
|
@ -176,32 +176,32 @@ t_api(_) ->
|
|||
[?SOURCE2, ?SOURCE3, ?SOURCE4, ?SOURCE5, ?SOURCE6]),
|
||||
{ok, 204, _} = request(post, uri(["authorization", "sources"]), ?SOURCE1),
|
||||
|
||||
%% Snd = fun ({_, Val}) -> Val end,
|
||||
%% LookupVal = fun LookupV(List, RestJson) ->
|
||||
%% case List of
|
||||
%% [Name] -> Snd(lists:keyfind(Name, 1, RestJson));
|
||||
%% [Name | NS] -> LookupV(NS, Snd(lists:keyfind(Name, 1, RestJson)))
|
||||
%% end
|
||||
%% end,
|
||||
%% EqualFun = fun (RList) ->
|
||||
%% fun ({M, V}) ->
|
||||
%% ?assertEqual(V,
|
||||
%% LookupVal([<<"metrics">>, M],
|
||||
%% RList)
|
||||
%% )
|
||||
%% end
|
||||
%% end,
|
||||
%% AssertFun =
|
||||
%% fun (ResultJson) ->
|
||||
%% {ok, RList} = emqx_json:safe_decode(ResultJson),
|
||||
%% MetricsList = [{<<"failed">>, 0},
|
||||
%% {<<"matched">>, 0},
|
||||
%% {<<"rate">>, 0.0},
|
||||
%% {<<"rate_last5m">>, 0.0},
|
||||
%% {<<"rate_max">>, 0.0},
|
||||
%% {<<"success">>, 0}],
|
||||
%% lists:map(EqualFun(RList), MetricsList)
|
||||
%% end,
|
||||
Snd = fun ({_, Val}) -> Val end,
|
||||
LookupVal = fun LookupV(List, RestJson) ->
|
||||
case List of
|
||||
[Name] -> Snd(lists:keyfind(Name, 1, RestJson));
|
||||
[Name | NS] -> LookupV(NS, Snd(lists:keyfind(Name, 1, RestJson)))
|
||||
end
|
||||
end,
|
||||
EqualFun = fun (RList) ->
|
||||
fun ({M, V}) ->
|
||||
?assertEqual(V,
|
||||
LookupVal([<<"metrics">>, M],
|
||||
RList)
|
||||
)
|
||||
end
|
||||
end,
|
||||
AssertFun =
|
||||
fun (ResultJson) ->
|
||||
{ok, RList} = emqx_json:safe_decode(ResultJson),
|
||||
MetricsList = [{<<"failed">>, 0},
|
||||
{<<"matched">>, 0},
|
||||
{<<"rate">>, 0.0},
|
||||
{<<"rate_last5m">>, 0.0},
|
||||
{<<"rate_max">>, 0.0},
|
||||
{<<"success">>, 0}],
|
||||
lists:map(EqualFun(RList), MetricsList)
|
||||
end,
|
||||
|
||||
{ok, 200, Result2} = request(get, uri(["authorization", "sources"]), []),
|
||||
Sources = get_sources(Result2),
|
||||
|
@ -238,7 +238,8 @@ t_api(_) ->
|
|||
<<"verify">> => <<"verify_none">>
|
||||
}}),
|
||||
{ok, 200, Result4} = request(get, uri(["authorization", "sources", "mongodb"]), []),
|
||||
%% AssertFun(Result4),
|
||||
{ok, 200, Status4} = request(get, uri(["authorization", "sources", "mongodb", "status"]), []),
|
||||
AssertFun(Status4),
|
||||
?assertMatch(#{<<"type">> := <<"mongodb">>,
|
||||
<<"ssl">> := #{<<"enable">> := <<"true">>,
|
||||
<<"cacertfile">> := ?MATCH_CERT,
|
||||
|
@ -261,7 +262,8 @@ t_api(_) ->
|
|||
<<"verify">> => <<"verify_none">>
|
||||
}}),
|
||||
{ok, 200, Result5} = request(get, uri(["authorization", "sources", "mongodb"]), []),
|
||||
%% AssertFun(Result5),
|
||||
{ok, 200, Status5} = request(get, uri(["authorization", "sources", "mongodb", "status"]), []),
|
||||
AssertFun(Status5),
|
||||
?assertMatch(#{<<"type">> := <<"mongodb">>,
|
||||
<<"ssl">> := #{<<"enable">> := <<"true">>,
|
||||
<<"cacertfile">> := ?MATCH_CERT,
|
||||
|
|
Loading…
Reference in New Issue