236 lines
7.3 KiB
Erlang
236 lines
7.3 KiB
Erlang
%%--------------------------------------------------------------------
|
|
%% Copyright (c) 2021-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
|
%%
|
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
|
%% you may not use this file except in compliance with the License.
|
|
%% You may obtain a copy of the License at
|
|
%%
|
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
|
%%
|
|
%% Unless required by applicable law or agreed to in writing, software
|
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
%% See the License for the specific language governing permissions and
|
|
%% limitations under the License.
|
|
%%--------------------------------------------------------------------
|
|
|
|
-module(emqx_authn_schema).
|
|
|
|
-elvis([{elvis_style, invalid_dynamic_call, disable}]).
|
|
-include_lib("hocon/include/hoconsc.hrl").
|
|
-include("emqx_authn.hrl").
|
|
|
|
-export([
|
|
common_fields/0,
|
|
roots/0,
|
|
validations/0,
|
|
tags/0,
|
|
fields/1,
|
|
authenticator_type/0,
|
|
authenticator_type_without_scram/0,
|
|
root_type/0,
|
|
mechanism/1,
|
|
backend/1
|
|
]).
|
|
|
|
roots() -> [].
|
|
|
|
tags() ->
|
|
[<<"Authentication">>].
|
|
|
|
common_fields() ->
|
|
[{enable, fun enable/1}].
|
|
|
|
enable(type) -> boolean();
|
|
enable(default) -> true;
|
|
enable(desc) -> ?DESC(?FUNCTION_NAME);
|
|
enable(_) -> undefined.
|
|
|
|
authenticator_type() ->
|
|
hoconsc:union(union_member_selector(emqx_authn:providers())).
|
|
|
|
authenticator_type_without_scram() ->
|
|
Providers = lists:filtermap(
|
|
fun
|
|
({{scram, _Backend}, _Mod}) ->
|
|
false;
|
|
(_) ->
|
|
true
|
|
end,
|
|
emqx_authn:providers()
|
|
),
|
|
hoconsc:union(union_member_selector(Providers)).
|
|
|
|
config_refs(Providers) ->
|
|
lists:append([Module:refs() || {_, Module} <- Providers]).
|
|
|
|
union_member_selector(Providers) ->
|
|
Types = config_refs(Providers),
|
|
fun
|
|
(all_union_members) -> Types;
|
|
({value, Value}) -> select_union_member(Value, Providers)
|
|
end.
|
|
|
|
select_union_member(#{<<"mechanism">> := _} = Value, Providers0) ->
|
|
BackendVal = maps:get(<<"backend">>, Value, undefined),
|
|
MechanismVal = maps:get(<<"mechanism">>, Value),
|
|
BackendFilterFn = fun
|
|
({{_Mec, Backend}, _Mod}) ->
|
|
BackendVal =:= atom_to_binary(Backend);
|
|
(_) ->
|
|
BackendVal =:= undefined
|
|
end,
|
|
MechanismFilterFn = fun
|
|
({{Mechanism, _Backend}, _Mod}) ->
|
|
MechanismVal =:= atom_to_binary(Mechanism);
|
|
({Mechanism, _Mod}) ->
|
|
MechanismVal =:= atom_to_binary(Mechanism)
|
|
end,
|
|
case lists:filter(BackendFilterFn, Providers0) of
|
|
[] ->
|
|
throw(#{reason => "unknown_backend", backend => BackendVal});
|
|
Providers1 ->
|
|
case lists:filter(MechanismFilterFn, Providers1) of
|
|
[] ->
|
|
throw(#{
|
|
reason => "unsupported_mechanism",
|
|
mechanism => MechanismVal,
|
|
backend => BackendVal
|
|
});
|
|
[{_, Module}] ->
|
|
try_select_union_member(Module, Value)
|
|
end
|
|
end;
|
|
select_union_member(Value, _Providers) when is_map(Value) ->
|
|
throw(#{reason => "missing_mechanism_field"});
|
|
select_union_member(Value, _Providers) ->
|
|
throw(#{reason => "not_a_struct", value => Value}).
|
|
|
|
try_select_union_member(Module, Value) ->
|
|
%% some modules have `union_member_selector/1' exported to help selecting
|
|
%% the sub-types, they are:
|
|
%% emqx_authn_http
|
|
%% emqx_authn_jwt
|
|
%% emqx_authn_mongodb
|
|
%% emqx_authn_redis
|
|
try
|
|
Module:union_member_selector({value, Value})
|
|
catch
|
|
error:undef ->
|
|
%% otherwise expect only one member from this module
|
|
Module:refs()
|
|
end.
|
|
|
|
%% authn is a core functionality however implemented outside of emqx app
|
|
%% in emqx_schema, 'authentication' is a map() type which is to allow
|
|
%% EMQX more pluggable.
|
|
root_type() ->
|
|
hoconsc:array(authenticator_type()).
|
|
|
|
mechanism(Name) ->
|
|
?HOCON(
|
|
Name,
|
|
#{
|
|
required => true,
|
|
desc => ?DESC("mechanism")
|
|
}
|
|
).
|
|
|
|
backend(Name) ->
|
|
?HOCON(Name, #{
|
|
required => true,
|
|
desc => ?DESC("backend")
|
|
}).
|
|
|
|
fields("metrics_status_fields") ->
|
|
[
|
|
{"resource_metrics", ?HOCON(?R_REF("resource_metrics"), #{desc => ?DESC("metrics")})},
|
|
array("node_resource_metrics", "node_metrics"),
|
|
{"metrics", ?HOCON(?R_REF("metrics"), #{desc => ?DESC("metrics")})},
|
|
array("node_metrics"),
|
|
{"status", ?HOCON(cluster_status(), #{desc => ?DESC("status")})},
|
|
array("node_status"),
|
|
array("node_error")
|
|
];
|
|
fields("metrics") ->
|
|
[
|
|
{"nomatch", ?HOCON(integer(), #{desc => ?DESC("metrics_nomatch")})},
|
|
{"total", ?HOCON(integer(), #{desc => ?DESC("metrics_total")})},
|
|
{"success", ?HOCON(integer(), #{desc => ?DESC("metrics_success")})},
|
|
{"failed", ?HOCON(integer(), #{desc => ?DESC("metrics_failed")})},
|
|
{"rate", ?HOCON(float(), #{desc => ?DESC("metrics_rate")})},
|
|
{"rate_max", ?HOCON(float(), #{desc => ?DESC("metrics_rate_max")})},
|
|
{"rate_last5m", ?HOCON(float(), #{desc => ?DESC("metrics_rate_last5m")})}
|
|
];
|
|
fields("resource_metrics") ->
|
|
common_field();
|
|
fields("node_metrics") ->
|
|
[
|
|
node_name(),
|
|
{"metrics", ?HOCON(?R_REF("metrics"), #{desc => ?DESC("metrics")})}
|
|
];
|
|
fields("node_resource_metrics") ->
|
|
[
|
|
node_name(),
|
|
{"metrics", ?HOCON(?R_REF("resource_metrics"), #{desc => ?DESC("metrics")})}
|
|
];
|
|
fields("node_status") ->
|
|
[
|
|
node_name(),
|
|
{"status", ?HOCON(status(), #{desc => ?DESC("node_status")})}
|
|
];
|
|
fields("node_error") ->
|
|
[
|
|
node_name(),
|
|
{"error", ?HOCON(string(), #{desc => ?DESC("node_error")})}
|
|
].
|
|
|
|
common_field() ->
|
|
[
|
|
{"matched", ?HOCON(integer(), #{desc => ?DESC("matched")})},
|
|
{"success", ?HOCON(integer(), #{desc => ?DESC("success")})},
|
|
{"failed", ?HOCON(integer(), #{desc => ?DESC("failed")})},
|
|
{"rate", ?HOCON(float(), #{desc => ?DESC("rate")})},
|
|
{"rate_max", ?HOCON(float(), #{desc => ?DESC("rate_max")})},
|
|
{"rate_last5m", ?HOCON(float(), #{desc => ?DESC("rate_last5m")})}
|
|
].
|
|
|
|
status() ->
|
|
hoconsc:enum([connected, disconnected, connecting]).
|
|
|
|
cluster_status() ->
|
|
hoconsc:enum([connected, disconnected, connecting, inconsistent]).
|
|
|
|
node_name() ->
|
|
{"node", ?HOCON(binary(), #{desc => ?DESC("node"), example => "emqx@127.0.0.1"})}.
|
|
|
|
array(Name) ->
|
|
array(Name, Name).
|
|
|
|
array(Name, DescId) ->
|
|
{Name, ?HOCON(?R_REF(Name), #{desc => ?DESC(DescId)})}.
|
|
|
|
validations() ->
|
|
[
|
|
{check_http_ssl_opts, fun(Conf) ->
|
|
CheckFun = fun emqx_authn_http:check_ssl_opts/1,
|
|
validation(Conf, CheckFun)
|
|
end},
|
|
{check_http_headers, fun(Conf) ->
|
|
CheckFun = fun emqx_authn_http:check_headers/1,
|
|
validation(Conf, CheckFun)
|
|
end}
|
|
].
|
|
|
|
validation(Conf, CheckFun) when is_map(Conf) ->
|
|
validation(hocon_maps:get(?CONF_NS, Conf), CheckFun);
|
|
validation(undefined, _) ->
|
|
ok;
|
|
validation([], _) ->
|
|
ok;
|
|
validation([AuthN | Tail], CheckFun) ->
|
|
case CheckFun(#{?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY => AuthN}) of
|
|
ok -> validation(Tail, CheckFun);
|
|
Error -> Error
|
|
end.
|