feat: use union member selector for authn config schema
This commit is contained in:
parent
c6a78cbfda
commit
6aaff6211f
|
@ -45,24 +45,105 @@ enable(desc) -> ?DESC(?FUNCTION_NAME);
|
|||
enable(_) -> undefined.
|
||||
|
||||
authenticator_type() ->
|
||||
hoconsc:union(config_refs([Module || {_AuthnType, Module} <- emqx_authn:providers()])).
|
||||
hoconsc:union(union_member_selector(emqx_authn:providers())).
|
||||
|
||||
authenticator_type_without_scram() ->
|
||||
Providers = lists:filtermap(
|
||||
fun
|
||||
({{password_based, _Backend}, Mod}) ->
|
||||
{true, Mod};
|
||||
({jwt, Mod}) ->
|
||||
{true, Mod};
|
||||
({{scram, _Backend}, _Mod}) ->
|
||||
false
|
||||
false;
|
||||
(_) ->
|
||||
true
|
||||
end,
|
||||
emqx_authn:providers()
|
||||
),
|
||||
hoconsc:union(config_refs(Providers)).
|
||||
hoconsc:union(union_member_selector(Providers)).
|
||||
|
||||
config_refs(Modules) ->
|
||||
lists:append([Module:refs() || Module <- Modules]).
|
||||
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, Providers) ->
|
||||
select_union_member(Value, Providers, #{});
|
||||
select_union_member(_Value, _) ->
|
||||
throw(#{hint => "missing 'mechanism' field"}).
|
||||
|
||||
select_union_member(Value, [], ReasonsMap) when ReasonsMap =:= #{} ->
|
||||
BackendVal = maps:get(<<"backend">>, Value, undefined),
|
||||
MechanismVal = maps:get(<<"mechanism">>, Value),
|
||||
throw(#{
|
||||
backend => BackendVal,
|
||||
mechanism => MechanismVal,
|
||||
hint => "unknown_mechanism_or_backend"
|
||||
});
|
||||
select_union_member(_Value, [], ReasonsMap) ->
|
||||
throw(ReasonsMap);
|
||||
select_union_member(Value, [Provider | Providers], ReasonsMap) ->
|
||||
{Mechanism, Backend, Module} =
|
||||
case Provider of
|
||||
{{M, B}, Mod} -> {atom_to_binary(M), atom_to_binary(B), Mod};
|
||||
{M, Mod} when is_atom(M) -> {atom_to_binary(M), undefined, Mod}
|
||||
end,
|
||||
case do_select_union_member(Mechanism, Backend, Module, Value) of
|
||||
{ok, Type} ->
|
||||
[Type];
|
||||
{error, nomatch} ->
|
||||
%% obvious mismatch, do not complain
|
||||
%% e.g. when 'backend' is "http", but the value is "redis",
|
||||
%% then there is no need to record the error like
|
||||
%% "'http' is exepcted but got 'redis'"
|
||||
select_union_member(Value, Providers, ReasonsMap);
|
||||
{error, Reason} ->
|
||||
%% more interesting error message
|
||||
%% e.g. when 'backend' is "http", but there is no "method" field
|
||||
%% found so there is no way to tell if it's the 'get' type or 'post' type.
|
||||
%% hence the error message is like:
|
||||
%% #{emqx_auth_http => "'http' auth backend must have get|post as 'method'"}
|
||||
select_union_member(Value, Providers, ReasonsMap#{Module => Reason})
|
||||
end.
|
||||
|
||||
do_select_union_member(Mechanism, Backend, Module, Value) ->
|
||||
BackendVal = maps:get(<<"backend">>, Value, undefined),
|
||||
MechanismVal = maps:get(<<"mechanism">>, Value),
|
||||
case MechanismVal =:= Mechanism of
|
||||
true when Backend =:= undefined ->
|
||||
case BackendVal =:= undefined of
|
||||
true ->
|
||||
%% e.g. jwt has no 'backend'
|
||||
try_select_union_member(Module, Value);
|
||||
false ->
|
||||
{error, "unexpected 'backend' for " ++ binary_to_list(Mechanism)}
|
||||
end;
|
||||
true ->
|
||||
case Backend =:= BackendVal of
|
||||
true ->
|
||||
try_select_union_member(Module, Value);
|
||||
false ->
|
||||
%% 'backend' not matching
|
||||
{error, nomatch}
|
||||
end;
|
||||
false ->
|
||||
%% 'mechanism' not matching
|
||||
{error, nomatch}
|
||||
end.
|
||||
|
||||
try_select_union_member(Module, Value) ->
|
||||
try
|
||||
%% some modules have refs/1 exported to help selectin the sub-types
|
||||
%% emqx_authn_http, emqx_authn_jwt, emqx_authn_mongodb and emqx_authn_redis
|
||||
Module:refs(Value)
|
||||
catch
|
||||
error:undef ->
|
||||
%% otherwise expect only one member from this module
|
||||
[Type] = Module:refs(),
|
||||
{ok, Type}
|
||||
end.
|
||||
|
||||
%% authn is a core functionality however implemented outside of emqx app
|
||||
%% in emqx_schema, 'authentication' is a map() type which is to allow
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
|
||||
-export([
|
||||
refs/0,
|
||||
refs/1,
|
||||
create/2,
|
||||
update/2,
|
||||
authenticate/2,
|
||||
|
@ -66,12 +67,12 @@ roots() ->
|
|||
|
||||
fields(get) ->
|
||||
[
|
||||
{method, #{type => get, required => true, default => get, desc => ?DESC(method)}},
|
||||
{method, #{type => get, required => true, desc => ?DESC(method)}},
|
||||
{headers, fun headers_no_content_type/1}
|
||||
] ++ common_fields();
|
||||
fields(post) ->
|
||||
[
|
||||
{method, #{type => post, required => true, default => post, desc => ?DESC(method)}},
|
||||
{method, #{type => post, required => true, desc => ?DESC(method)}},
|
||||
{headers, fun headers/1}
|
||||
] ++ common_fields().
|
||||
|
||||
|
@ -159,6 +160,13 @@ refs() ->
|
|||
hoconsc:ref(?MODULE, post)
|
||||
].
|
||||
|
||||
refs(#{<<"method">> := <<"get">>}) ->
|
||||
{ok, hoconsc:ref(?MODULE, get)};
|
||||
refs(#{<<"method">> := <<"post">>}) ->
|
||||
{ok, hoconsc:ref(?MODULE, post)};
|
||||
refs(_) ->
|
||||
{error, "'http' auth backend must have get|post as 'method'"}.
|
||||
|
||||
create(_AuthenticatorID, Config) ->
|
||||
create(Config).
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
-include_lib("hocon/include/hoconsc.hrl").
|
||||
|
||||
-behaviour(hocon_schema).
|
||||
-behaviour(emqx_authentication).
|
||||
|
||||
-export([
|
||||
namespace/0,
|
||||
|
@ -33,6 +32,7 @@
|
|||
|
||||
-export([
|
||||
refs/0,
|
||||
refs/1,
|
||||
create/2,
|
||||
update/2,
|
||||
authenticate/2,
|
||||
|
@ -169,6 +169,19 @@ refs() ->
|
|||
hoconsc:ref(?MODULE, 'jwks')
|
||||
].
|
||||
|
||||
refs(#{<<"mechanism">> := <<"jwt">>} = V) ->
|
||||
UseJWKS = maps:get(<<"use_jwks">>, V, undefined),
|
||||
select_ref(UseJWKS, V).
|
||||
|
||||
select_ref(true, _) ->
|
||||
{ok, hoconsc:ref(?MODULE, 'jwks')};
|
||||
select_ref(false, #{<<"public_key">> := _}) ->
|
||||
{ok, hoconsc:ref(?MODULE, 'public-key')};
|
||||
select_ref(false, _) ->
|
||||
{ok, hoconsc:ref(?MODULE, 'hmac-based')};
|
||||
select_ref(_, _) ->
|
||||
{error, "use_jwks must be set"}.
|
||||
|
||||
create(_AuthenticatorID, Config) ->
|
||||
create(Config).
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
|
||||
-export([
|
||||
refs/0,
|
||||
refs/1,
|
||||
create/2,
|
||||
update/2,
|
||||
authenticate/2,
|
||||
|
@ -130,6 +131,15 @@ refs() ->
|
|||
hoconsc:ref(?MODULE, 'sharded-cluster')
|
||||
].
|
||||
|
||||
refs(#{<<"mongo_type">> := <<"single">>}) ->
|
||||
{ok, hoconsc:ref(?MODULE, standalone)};
|
||||
refs(#{<<"mongo_type">> := <<"rs">>}) ->
|
||||
{ok, hoconsc:ref(?MODULE, 'replica-set')};
|
||||
refs(#{<<"mongo_type">> := <<"sharded">>}) ->
|
||||
{ok, hoconsc:ref(?MODULE, 'sharded-cluster')};
|
||||
refs(_) ->
|
||||
{error, "unknown 'mongo_type'"}.
|
||||
|
||||
create(_AuthenticatorID, Config) ->
|
||||
create(Config).
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
|
||||
-export([
|
||||
refs/0,
|
||||
refs/1,
|
||||
create/2,
|
||||
update/2,
|
||||
authenticate/2,
|
||||
|
@ -97,6 +98,15 @@ refs() ->
|
|||
hoconsc:ref(?MODULE, sentinel)
|
||||
].
|
||||
|
||||
refs(#{<<"redis_type">> := <<"single">>}) ->
|
||||
{ok, hoconsc:ref(?MODULE, standalone)};
|
||||
refs(#{<<"redis_type">> := <<"cluster">>}) ->
|
||||
{ok, hoconsc:ref(?MODULE, cluster)};
|
||||
refs(#{<<"redis_type">> := <<"sentinel">>}) ->
|
||||
{ok, hoconsc:ref(?MODULE, sentinel)};
|
||||
refs(_) ->
|
||||
{error, "unknown 'redis_type'"}.
|
||||
|
||||
create(_AuthenticatorID, Config) ->
|
||||
create(Config).
|
||||
|
||||
|
|
Loading…
Reference in New Issue