fix(authn): fix some bugs

This commit is contained in:
zhouzb 2021-07-19 15:56:39 +08:00
parent a5a596e3ac
commit 2a594b1a73
4 changed files with 466 additions and 389 deletions

View File

@ -16,338 +16,414 @@
-module(emqx_authn_api).
-behavior(minirest_api).
-include("emqx_authn.hrl").
-export([ create_authenticator/2
, delete_authenticator/2
, update_authenticator/2
, lookup_authenticator/2
, list_authenticators/2
, move_authenticator/2
, import_users/2
, add_user/2
, delete_user/2
, update_user/2
, lookup_user/2
, list_users/2
]).
-export([ api_spec/0 ]).
-rest_api(#{name => create_authenticator,
method => 'POST',
path => "/authentication/authenticators",
func => create_authenticator,
descr => "Create authenticator"
}).
api_spec() ->
{[authenticator_api()], definitions()}.
-rest_api(#{name => delete_authenticator,
method => 'DELETE',
path => "/authentication/authenticators/:bin:name",
func => delete_authenticator,
descr => "Delete authenticator"
}).
-rest_api(#{name => update_authenticator,
method => 'PUT',
path => "/authentication/authenticators/:bin:name",
func => update_authenticator,
descr => "Update authenticator"
}).
-rest_api(#{name => lookup_authenticator,
method => 'GET',
path => "/authentication/authenticators/:bin:name",
func => lookup_authenticator,
descr => "Lookup authenticator"
}).
-rest_api(#{name => list_authenticators,
method => 'GET',
path => "/authentication/authenticators",
func => list_authenticators,
descr => "List authenticators"
}).
-rest_api(#{name => move_authenticator,
method => 'POST',
path => "/authentication/authenticators/:bin:name/position",
func => move_authenticator,
descr => "Change the order of authenticators"
}).
-rest_api(#{name => import_users,
method => 'POST',
path => "/authentication/authenticators/:bin:name/import-users",
func => import_users,
descr => "Import users"
}).
-rest_api(#{name => add_user,
method => 'POST',
path => "/authentication/authenticators/:bin:name/users",
func => add_user,
descr => "Add user"
}).
-rest_api(#{name => delete_user,
method => 'DELETE',
path => "/authentication/authenticators/:bin:name/users/:bin:user_id",
func => delete_user,
descr => "Delete user"
}).
-rest_api(#{name => update_user,
method => 'PUT',
path => "/authentication/authenticators/:bin:name/users/:bin:user_id",
func => update_user,
descr => "Update user"
}).
-rest_api(#{name => lookup_user,
method => 'GET',
path => "/authentication/authenticators/:bin:name/users/:bin:user_id",
func => lookup_user,
descr => "Lookup user"
}).
%% TODO: Support pagination
-rest_api(#{name => list_users,
method => 'GET',
path => "/authentication/authenticators/:bin:name/users",
func => list_users,
descr => "List all users"
}).
create_authenticator(Binding, Params) ->
do_create_authenticator(uri_decode(Binding), lists_to_map(Params)).
do_create_authenticator(_Binding, Authenticator0) ->
Config = #{<<"emqx_authn">> => #{
<<"authenticators">> => [Authenticator0]
}},
#{emqx_authn := #{authenticators := [Authenticator1]}}
= hocon_schema:check_plain(emqx_authn_schema, Config,
#{atom_key => true, nullable => true}),
case emqx_authn:create_authenticator(?CHAIN, Authenticator1) of
{ok, Authenticator2} ->
return({ok, Authenticator2});
{error, Reason} ->
return(serialize_error(Reason))
end.
delete_authenticator(Binding, Params) ->
do_delete_authenticator(uri_decode(Binding), maps:from_list(Params)).
do_delete_authenticator(#{name := Name}, _Params) ->
case emqx_authn:delete_authenticator(?CHAIN, Name) of
ok ->
return(ok);
{error, Reason} ->
return(serialize_error(Reason))
end.
%% TODO: Support incremental update
update_authenticator(Binding, Params) ->
do_update_authenticator(uri_decode(Binding), lists_to_map(Params)).
%% TOOD: PUT method supports creation and update
do_update_authenticator(#{name := Name}, NewConfig0) ->
case emqx_authn:lookup_authenticator(?CHAIN, Name) of
{ok, #{mechanism := Mechanism}} ->
Authenticator = #{<<"name">> => Name,
<<"mechanism">> => Mechanism,
<<"config">> => NewConfig0},
Config = #{<<"emqx_authn">> => #{
<<"authenticators">> => [Authenticator]
}},
#{
emqx_authn := #{
authenticators := [#{
config := NewConfig1
}]
authenticator_api() ->
Example1 = #{name => <<"example">>,
mechanism => <<"password-based">>,
config => #{
server_type => <<"built-in-example">>,
user_id_type => <<"username">>,
password_hash_algorithm => #{
name => <<"sha256">>
}
}},
Example2 = #{name => <<"example">>,
mechanism => <<"password-based">>,
config => #{
server_type => <<"http-server">>,
method => <<"post">>,
url => <<"http://localhost:80/login">>,
headers => #{
<<"content-type">> => <<"application/json">>
},
form_data => #{
<<"username">> => <<"${mqtt-username}">>,
<<"password">> => <<"${mqtt-password}">>
}
}},
Example3 = #{name => <<"example">>,
mechanism => <<"jwt">>,
config => #{
use_jwks => false,
algorithm => <<"hmac-based">>,
secret => <<"mysecret">>,
secret_base64_encoded => false,
verify_claims => #{
<<"username">> => <<"${mqtt-username}">>
}
}},
Metadata = #{
post => #{
description => "Create authenticator",
requestBody => #{
content => #{
'application/json' => #{
schema => minirest:ref(<<"authenticator">>),
examples => #{
default => #{
summary => <<"Default">>,
value => emqx_json:encode(Example1)
},
http => #{
summary => <<"Authentication provided by HTTP Server">>,
value => emqx_json:encode(Example2)
},
jwt => #{
summary => <<"JWT Authentication">>,
value => emqx_json:encode(Example3)
}
}
}
}
} = hocon_schema:check_plain(emqx_authn_schema, Config,
#{atom_key => true, nullable => true}),
case emqx_authn:update_authenticator(?CHAIN, Name, NewConfig1) of
{ok, NAuthenticator} ->
return({ok, NAuthenticator});
{error, Reason} ->
return(serialize_error(Reason))
end;
{error, Reason} ->
return(serialize_error(Reason))
end.
},
responses => #{
<<"201">> => #{
description => <<"Created successfully">>,
content => #{}
}
}
}
},
{"/authentication/authenticators", Metadata, clients}.
lookup_authenticator(Binding, Params) ->
do_lookup_authenticator(uri_decode(Binding), maps:from_list(Params)).
definitions() ->
AuthenticatorDef = #{
allOf => [
#{
type => object,
required => [name],
properties => #{
name => #{
type => string,
example => "exmaple"
}
}
},
#{
oneOf => [ minirest:ref(<<"password_based">>)
, minirest:ref(<<"jwt">>)
, minirest:ref(<<"scram">>)
]
}
]
},
do_lookup_authenticator(#{name := Name}, _Params) ->
case emqx_authn:lookup_authenticator(?CHAIN, Name) of
{ok, Authenticator} ->
return({ok, Authenticator});
{error, Reason} ->
return(serialize_error(Reason))
end.
PasswordBasedDef = #{
type => object,
properties => #{
mechanism => #{
type => string,
enum => [<<"password-based">>],
example => <<"password-based">>
},
config => #{
oneOf => [ minirest:ref(<<"password_based_built_in_database">>)
, minirest:ref(<<"password_based_mysql">>)
, minirest:ref(<<"password_based_pgsql">>)
, minirest:ref(<<"password_based_http_server">>)
]
}
}
},
list_authenticators(Binding, Params) ->
do_list_authenticators(uri_decode(Binding), maps:from_list(Params)).
JWTDef = #{
type => object,
properties => #{
mechanism => #{
type => string,
enum => [<<"jwt">>],
example => <<"jwt">>
},
config => #{
type => object,
properties => #{
use_jwks => #{
type => boolean,
default => false,
example => false
},
algorithm => #{
type => string,
enum => [<<"hmac-based">>, <<"public-key">>],
default => <<"hmac-based">>,
example => <<"hmac-based">>
},
secret => #{
type => string
},
secret_base64_encoded => #{
type => boolean,
default => false
},
certificate => #{
type => string
},
verify_claims => #{
type => object,
additionalProperties => #{
type => string
}
},
ssl => minirest:ref(<<"ssl">>)
}
}
}
},
SCRAMDef = #{
type => object,
properties => #{
mechanism => #{
type => string,
enum => [<<"scram">>],
example => <<"scram">>
},
config => #{
type => object,
properties => #{
server_type => #{
type => string,
enum => [<<"built-in-database">>],
default => <<"built-in-database">>
},
algorithm => #{
type => string,
enum => [<<"sha256">>, <<"sha512">>],
default => <<"sha256">>
},
iteration_count => #{
type => integer,
default => 4096
}
}
}
}
},
do_list_authenticators(_Binding, _Params) ->
case emqx_authn:list_authenticators(?CHAIN) of
{ok, Authenticators} ->
return({ok, Authenticators});
{error, Reason} ->
return(serialize_error(Reason))
end.
PasswordBasedBuiltInDatabaseDef = #{
type => object,
properties => #{
server_type => #{
type => string,
enum => [<<"built-in-database">>],
example => <<"built-in-database">>
},
user_id_type => #{
type => string,
enum => [<<"username">>, <<"clientid">>],
default => <<"username">>,
example => <<"username">>
},
password_hash_algorithm => minirest:ref(<<"password_hash_algorithm">>)
}
},
move_authenticator(Binding, Params) ->
do_move_authenticator(uri_decode(Binding), maps:from_list(Params)).
PasswordBasedMySQLDef = #{
type => object,
properties => #{
server_type => #{
type => string,
enum => [<<"mysql">>],
example => <<"mysql">>
},
server => #{
type => string,
example => <<"localhost:3306">>
},
database => #{
type => string
},
pool_size => #{
type => integer,
default => 8
},
username => #{
type => string
},
password => #{
type => string
},
auto_reconnect => #{
type => boolean,
default => true
},
ssl => minirest:ref(<<"ssl">>),
password_hash_algorithm => minirest:ref(<<"password_hash_algorithm">>),
salt_position => #{
type => string,
enum => [<<"prefix">>, <<"suffix">>],
default => <<"prefix">>
},
query => #{
type => string,
example => <<"SELECT password_hash FROM mqtt_user WHERE username = ${mqtt-username}">>
},
query_timeout => #{
type => integer,
description => <<"Query timeout, Unit: Milliseconds">>,
default => 5000
}
}
},
do_move_authenticator(#{name := Name}, #{<<"position">> := <<"the front">>}) ->
case emqx_authn:move_authenticator_to_the_front(?CHAIN, Name) of
ok ->
return(ok);
{error, Reason} ->
return(serialize_error(Reason))
end;
do_move_authenticator(#{name := Name}, #{<<"position">> := <<"the end">>}) ->
case emqx_authn:move_authenticator_to_the_end(?CHAIN, Name) of
ok ->
return(ok);
{error, Reason} ->
return(serialize_error(Reason))
end;
do_move_authenticator(#{name := Name}, #{<<"position">> := N}) when is_number(N) ->
case emqx_authn:move_authenticator_to_the_nth(?CHAIN, Name, N) of
ok ->
return(ok);
{error, Reason} ->
return(serialize_error(Reason))
end;
do_move_authenticator(_Binding, _Params) ->
return(serialize_error({missing_parameter, <<"position">>})).
PasswordBasedPgSQLDef = #{
type => object,
properties => #{
server_type => #{
type => string,
enum => [<<"pgsql">>],
example => <<"pgsql">>
},
server => #{
type => string,
example => <<"localhost:5432">>
},
database => #{
type => string
},
pool_size => #{
type => integer,
default => 8
},
username => #{
type => string
},
password => #{
type => string
},
auto_reconnect => #{
type => boolean,
default => true
},
password_hash_algorithm => minirest:ref(<<"password_hash_algorithm">>),
salt_position => #{
type => string,
enum => [<<"prefix">>, <<"suffix">>],
default => <<"prefix">>
},
query => #{
type => string,
example => <<"SELECT password_hash FROM mqtt_user WHERE username = ${mqtt-username}">>
}
}
},
import_users(Binding, Params) ->
do_import_users(uri_decode(Binding), maps:from_list(Params)).
PasswordBasedHTTPServerDef = #{
type => object,
properties => #{
server_type => #{
type => string,
enum => [<<"http-server">>],
example => <<"http-server">>
},
method => #{
type => string,
enum => [<<"get">>, <<"post">>],
default => <<"post">>
},
url => #{
type => string,
example => <<"http://localhost:80/login">>
},
headers => #{
type => object,
additionalProperties => #{
type => string
}
},
format_data => #{
type => string
},
connect_timeout => #{
type => integer,
default => 5000
},
max_retries => #{
type => integer,
default => 5
},
retry_interval => #{
type => integer,
default => 1000
},
request_timout => #{
type => integer,
default => 5000
},
pool_size => #{
type => integer,
default => 8
}
}
},
do_import_users(#{name := Name},
#{<<"filename">> := Filename}) ->
case emqx_authn:import_users(?CHAIN, Name, Filename) of
ok ->
return(ok);
{error, Reason} ->
return(serialize_error(Reason))
end;
do_import_users(_Binding, Params) ->
Missed = get_missed_params(Params, [<<"filename">>, <<"file_format">>]),
return(serialize_error({missing_parameter, Missed})).
PasswordHashAlgorithmDef = #{
type => object,
required => [name],
properties => #{
name => #{
type => string,
enum => [<<"plain">>, <<"md5">>, <<"sha">>, <<"sha256">>, <<"sha512">>, <<"bcrypt">>],
default => <<"sha256">>
},
salt_rounds => #{
type => integer,
default => 10
}
}
},
add_user(Binding, Params) ->
do_add_user(uri_decode(Binding), maps:from_list(Params)).
SSLDef = #{
type => object,
properties => #{
enable => #{
type => boolean,
default => false
},
certfile => #{
type => string
},
keyfile => #{
type => string
},
cacertfile => #{
type => string
},
verify => #{
type => boolean,
default => true
},
server_name_indication => #{
type => object,
properties => #{
enable => #{
type => boolean,
default => false
},
hostname => #{
type => string
}
}
}
}
},
do_add_user(#{name := Name}, UserInfo) ->
case emqx_authn:add_user(?CHAIN, Name, UserInfo) of
{ok, User} ->
return({ok, User});
{error, Reason} ->
return(serialize_error(Reason))
end.
delete_user(Binding, Params) ->
do_delete_user(uri_decode(Binding), maps:from_list(Params)).
do_delete_user(#{name := Name,
user_id := UserID}, _Params) ->
case emqx_authn:delete_user(?CHAIN, Name, UserID) of
ok ->
return(ok);
{error, Reason} ->
return(serialize_error(Reason))
end.
update_user(Binding, Params) ->
do_update_user(uri_decode(Binding), maps:from_list(Params)).
do_update_user(#{name := Name,
user_id := UserID}, NewUserInfo) ->
case emqx_authn:update_user(?CHAIN, Name, UserID, NewUserInfo) of
{ok, User} ->
return({ok, User});
{error, Reason} ->
return(serialize_error(Reason))
end.
lookup_user(Binding, Params) ->
do_lookup_user(uri_decode(Binding), maps:from_list(Params)).
do_lookup_user(#{name := Name,
user_id := UserID}, _Params) ->
case emqx_authn:lookup_user(?CHAIN, Name, UserID) of
{ok, User} ->
return({ok, User});
{error, Reason} ->
return(serialize_error(Reason))
end.
list_users(Binding, Params) ->
do_list_users(uri_decode(Binding), maps:from_list(Params)).
do_list_users(#{name := Name}, _Params) ->
case emqx_authn:list_users(?CHAIN, Name) of
{ok, Users} ->
return({ok, Users});
{error, Reason} ->
return(serialize_error(Reason))
end.
%%------------------------------------------------------------------------------
%% Internal functions
%%------------------------------------------------------------------------------
uri_decode(Params) ->
maps:fold(fun(K, V, Acc) ->
Acc#{K => emqx_http_lib:uri_decode(V)}
end, #{}, Params).
lists_to_map(L) ->
lists_to_map(L, #{}).
lists_to_map([], Acc) ->
Acc;
lists_to_map([{K, V} | More], Acc) when is_list(V) ->
NV = lists_to_map(V),
lists_to_map(More, Acc#{K => NV});
lists_to_map([{K, V} | More], Acc) ->
lists_to_map(More, Acc#{K => V});
lists_to_map([_ | _] = L, _) ->
L.
serialize_error({already_exists, {Type, ID}}) ->
{error, <<"ALREADY_EXISTS">>, list_to_binary(io_lib:format("~s '~s' already exists", [serialize_type(Type), ID]))};
serialize_error({not_found, {Type, ID}}) ->
{error, <<"NOT_FOUND">>, list_to_binary(io_lib:format("~s '~s' not found", [serialize_type(Type), ID]))};
serialize_error({duplicate, Name}) ->
{error, <<"INVALID_PARAMETER">>, list_to_binary(io_lib:format("Authenticator name '~s' is duplicated", [Name]))};
serialize_error({missing_parameter, Names = [_ | Rest]}) ->
Format = ["~s," || _ <- Rest] ++ ["~s"],
NFormat = binary_to_list(iolist_to_binary(Format)),
{error, <<"MISSING_PARAMETER">>, list_to_binary(io_lib:format("The input parameters " ++ NFormat ++ " that are mandatory for processing this request are not supplied.", Names))};
serialize_error({missing_parameter, Name}) ->
{error, <<"MISSING_PARAMETER">>, list_to_binary(io_lib:format("The input parameter '~s' that is mandatory for processing this request is not supplied.", [Name]))};
serialize_error(_) ->
{error, <<"UNKNOWN_ERROR">>, <<"Unknown error">>}.
serialize_type(authenticator) ->
"Authenticator".
get_missed_params(Actual, Expected) ->
Keys = lists:foldl(fun(Key, Acc) ->
case maps:is_key(Key, Actual) of
true -> Acc;
false -> [Key | Acc]
end
end, [], Expected),
lists:reverse(Keys).
return(_) ->
%% TODO: V5 API
ok.
[ #{<<"authenticator">> => AuthenticatorDef}
, #{<<"password_based">> => PasswordBasedDef}
, #{<<"jwt">> => JWTDef}
, #{<<"scram">> => SCRAMDef}
, #{<<"password_based_built_in_database">> => PasswordBasedBuiltInDatabaseDef}
, #{<<"password_based_mysql">> => PasswordBasedMySQLDef}
, #{<<"password_based_pgsql">> => PasswordBasedPgSQLDef}
, #{<<"password_based_http_server">> => PasswordBasedHTTPServerDef}
, #{<<"password_hash_algorithm">> => PasswordHashAlgorithmDef}
, #{<<"ssl">> => SSLDef}
].

View File

@ -26,13 +26,6 @@
, validations/0
]).
-type accept() :: 'application/json' | 'application/x-www-form-urlencoded'.
-type content_type() :: accept().
-reflect_type([ accept/0
, content_type/0
]).
-export([ create/3
, update/4
, authenticate/2
@ -53,45 +46,53 @@ fields("") ->
fields(get) ->
[ {method, #{type => get,
default => get}}
default => post}}
, {headers, fun headers_no_content_type/1}
] ++ common_fields();
fields(post) ->
[ {method, #{type => post,
default => get}}
, {content_type, fun content_type/1}
default => post}}
, {headers, fun headers/1}
] ++ common_fields().
common_fields() ->
[ {server_type, {enum, ['http-server']}}
, {url, fun url/1}
, {accept, fun accept/1}
, {headers, fun headers/1}
, {form_data, fun form_data/1}
, {request_timeout, fun request_timeout/1}
] ++ proplists:delete(base_url, emqx_connector_http:fields(config)).
] ++ maps:to_list(maps:without([ base_url
, pool_type],
maps:from_list(emqx_connector_http:fields(config)))).
validations() ->
[ {check_ssl_opts, fun check_ssl_opts/1} ].
[ {check_ssl_opts, fun check_ssl_opts/1}
, {check_headers, fun check_headers/1}
].
url(type) -> binary();
url(nullable) -> false;
url(validate) -> [fun check_url/1];
url(_) -> undefined.
accept(type) -> accept();
accept(default) -> 'application/json';
accept(_) -> undefined.
content_type(type) -> content_type();
content_type(default) -> 'application/json';
content_type(_) -> undefined.
headers(type) -> list();
headers(default) -> [];
headers(type) -> map();
headers(converter) ->
fun(Headers) ->
maps:merge(default_headers(), transform_header_name(Headers))
end;
headers(default) -> default_headers();
headers(_) -> undefined.
form_data(type) -> binary();
headers_no_content_type(type) -> map();
headers_no_content_type(converter) ->
fun(Headers) ->
maps:merge(default_headers_no_content_type(), transform_header_name(Headers))
end;
headers_no_content_type(default) -> default_headers_no_content_type();
headers_no_content_type(_) -> undefined.
%% TODO: Using map()
form_data(type) -> map();
form_data(nullable) -> false;
form_data(validate) -> [fun check_form_data/1];
form_data(_) -> undefined.
@ -107,28 +108,17 @@ request_timeout(_) -> undefined.
create(ChainID, AuthenticatorName,
#{method := Method,
url := URL,
accept := Accept,
headers := Headers,
form_data := FormData,
request_timeout := RequestTimeout} = Config) ->
ContentType = maps:get(content_type, Config, undefined),
DefaultHeader0 = case ContentType of
undefined -> #{};
_ -> #{<<"content-type">> => atom_to_binary(ContentType, utf8)}
end,
DefaultHeader = DefaultHeader0#{<<"accept">> => atom_to_binary(Accept, utf8)},
NHeaders = maps:to_list(maps:merge(DefaultHeader, maps:from_list(Headers))),
NFormData = preprocess_form_data(FormData),
#{path := Path,
query := Query} = URIMap = parse_url(URL),
BaseURL = generate_base_url(URIMap),
State = #{method => Method,
path => Path,
base_query => cow_qs:parse_qs(list_to_binary(Query)),
accept => Accept,
content_type => ContentType,
headers => NHeaders,
form_data => NFormData,
headers => maps:to_list(Headers),
form_data => FormData,
request_timeout => RequestTimeout},
ResourceID = <<ChainID/binary, "/", AuthenticatorName/binary>>,
case emqx_resource:create_local(ResourceID, emqx_connector_http, Config#{base_url => BaseURL}) of
@ -182,26 +172,38 @@ check_url(URL) ->
end.
check_form_data(FormData) ->
KVs = binary:split(FormData, [<<"&">>], [global]),
case false =:= lists:any(fun(T) -> T =:= <<>> end, KVs) of
true ->
NKVs = [list_to_tuple(binary:split(KV, [<<"=">>], [global])) || KV <- KVs],
false =:=
lists:any(fun({K, V}) ->
K =:= <<>> orelse V =:= <<>>;
(_) ->
true
end, NKVs);
false ->
false
end.
lists:any(fun({_, V}) ->
not is_binary(V)
end, maps:to_list(FormData)).
default_headers() ->
maps:put(<<"content-type">>,
<<"application/json">>,
default_headers_no_content_type()).
default_headers_no_content_type() ->
#{ <<"accept">> => <<"application/json">>
, <<"cache-control">> => <<"no-cache">>
, <<"connection">> => <<"keep-alive">>
, <<"keep-alive">> => <<"timeout=5">>
}.
transform_header_name(Headers) ->
maps:fold(fun(K0, V, Acc) ->
K = list_to_binary(string:to_lower(binary_to_list(K0))),
maps:put(K, V, Acc)
end, #{}, Headers).
check_ssl_opts(Conf) ->
emqx_connector_http:check_ssl_opts("url", Conf).
preprocess_form_data(FormData) ->
KVs = binary:split(FormData, [<<"&">>], [global]),
[list_to_tuple(binary:split(KV, [<<"=">>], [global])) || KV <- KVs].
check_headers(Conf) ->
Method = hocon_schema:get_value("method", Conf),
Headers = hocon_schema:get_value("headers", Conf),
case Method =:= get andalso maps:get(<<"content-type">>, Headers, undefined) =/= undefined of
true -> false;
false -> true
end.
parse_url(URL) ->
{ok, URIMap} = emqx_http_lib:uri_parse(URL),
@ -220,7 +222,6 @@ generate_base_url(#{scheme := Scheme,
generate_request(Credential, #{method := Method,
path := Path,
base_query := BaseQuery,
content_type := ContentType,
headers := Headers,
form_data := FormData0}) ->
FormData = replace_placeholders(FormData0, Credential),
@ -230,6 +231,7 @@ generate_request(Credential, #{method := Method,
{NPath, Headers};
post ->
NPath = append_query(Path, BaseQuery),
ContentType = proplists:get_value(<<"content-type">>, Headers),
Body = serialize_body(ContentType, FormData),
{NPath, Headers, Body}
end.

View File

@ -22,7 +22,6 @@
-export([ structs/0
, fields/1
, validations/0
]).
-export([ create/3
@ -81,19 +80,13 @@ fields(ssl_enable) ->
];
fields(ssl_disable) ->
[ {enable, #{type => false}} ];
fields(claim) ->
[ {"$name", fun expected_claim_value/1} ].
validations() ->
[ {check_verify_claims, fun check_verify_claims/1} ].
[ {enable, #{type => false}} ].
secret(type) -> string();
secret(_) -> undefined.
secret_base64_encoded(type) -> boolean();
secret_base64_encoded(defualt) -> false;
secret_base64_encoded(default) -> false;
secret_base64_encoded(_) -> undefined.
certificate(type) -> string();
@ -123,13 +116,15 @@ verify(_) -> undefined.
server_name_indication(type) -> string();
server_name_indication(_) -> undefined.
verify_claims(type) -> hoconsc:array(hoconsc:ref(claim));
verify_claims(default) -> [];
verify_claims(type) -> list();
verify_claims(default) -> #{};
verify_claims(validate) -> [fun check_verify_claims/1];
verify_claims(converter) ->
fun(VerifyClaims) ->
maps:to_list(VerifyClaims)
end;
verify_claims(_) -> undefined.
expected_claim_value(type) -> string();
expected_claim_value(_) -> undefined.
%%------------------------------------------------------------------------------
%% APIs
%%------------------------------------------------------------------------------

View File

@ -57,6 +57,10 @@ password_hash_algorithm(type) -> {union, [hoconsc:ref(bcrypt), hoconsc:ref(other
password_hash_algorithm(default) -> #{<<"name">> => sha256};
password_hash_algorithm(_) -> undefined.
salt_rounds(type) -> integer();
salt_rounds(default) -> 10;
salt_rounds(_) -> undefined.
salt_position(type) -> {enum, [prefix, suffix]};
salt_position(default) -> prefix;
salt_position(_) -> undefined.