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). -module(emqx_authn_api).
-behavior(minirest_api).
-include("emqx_authn.hrl"). -include("emqx_authn.hrl").
-export([ create_authenticator/2 -export([ api_spec/0 ]).
, 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
]).
-rest_api(#{name => create_authenticator, api_spec() ->
method => 'POST', {[authenticator_api()], definitions()}.
path => "/authentication/authenticators",
func => create_authenticator,
descr => "Create authenticator"
}).
-rest_api(#{name => delete_authenticator, authenticator_api() ->
method => 'DELETE', Example1 = #{name => <<"example">>,
path => "/authentication/authenticators/:bin:name", mechanism => <<"password-based">>,
func => delete_authenticator, config => #{
descr => "Delete authenticator" server_type => <<"built-in-example">>,
}). user_id_type => <<"username">>,
password_hash_algorithm => #{
-rest_api(#{name => update_authenticator, name => <<"sha256">>
method => 'PUT', }
path => "/authentication/authenticators/:bin:name", }},
func => update_authenticator, Example2 = #{name => <<"example">>,
descr => "Update authenticator" mechanism => <<"password-based">>,
}). config => #{
server_type => <<"http-server">>,
-rest_api(#{name => lookup_authenticator, method => <<"post">>,
method => 'GET', url => <<"http://localhost:80/login">>,
path => "/authentication/authenticators/:bin:name", headers => #{
func => lookup_authenticator, <<"content-type">> => <<"application/json">>
descr => "Lookup authenticator" },
}). form_data => #{
<<"username">> => <<"${mqtt-username}">>,
-rest_api(#{name => list_authenticators, <<"password">> => <<"${mqtt-password}">>
method => 'GET', }
path => "/authentication/authenticators", }},
func => list_authenticators, Example3 = #{name => <<"example">>,
descr => "List authenticators" mechanism => <<"jwt">>,
}). config => #{
use_jwks => false,
-rest_api(#{name => move_authenticator, algorithm => <<"hmac-based">>,
method => 'POST', secret => <<"mysecret">>,
path => "/authentication/authenticators/:bin:name/position", secret_base64_encoded => false,
func => move_authenticator, verify_claims => #{
descr => "Change the order of authenticators" <<"username">> => <<"${mqtt-username}">>
}). }
}},
-rest_api(#{name => import_users, Metadata = #{
method => 'POST', post => #{
path => "/authentication/authenticators/:bin:name/import-users", description => "Create authenticator",
func => import_users, requestBody => #{
descr => "Import users" content => #{
}). 'application/json' => #{
schema => minirest:ref(<<"authenticator">>),
-rest_api(#{name => add_user, examples => #{
method => 'POST', default => #{
path => "/authentication/authenticators/:bin:name/users", summary => <<"Default">>,
func => add_user, value => emqx_json:encode(Example1)
descr => "Add user" },
}). http => #{
summary => <<"Authentication provided by HTTP Server">>,
-rest_api(#{name => delete_user, value => emqx_json:encode(Example2)
method => 'DELETE', },
path => "/authentication/authenticators/:bin:name/users/:bin:user_id", jwt => #{
func => delete_user, summary => <<"JWT Authentication">>,
descr => "Delete user" value => emqx_json:encode(Example3)
}). }
}
-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
}]
} }
} = hocon_schema:check_plain(emqx_authn_schema, Config, },
#{atom_key => true, nullable => true}), responses => #{
case emqx_authn:update_authenticator(?CHAIN, Name, NewConfig1) of <<"201">> => #{
{ok, NAuthenticator} -> description => <<"Created successfully">>,
return({ok, NAuthenticator}); content => #{}
{error, Reason} -> }
return(serialize_error(Reason)) }
end; }
{error, Reason} -> },
return(serialize_error(Reason)) {"/authentication/authenticators", Metadata, clients}.
end.
lookup_authenticator(Binding, Params) -> definitions() ->
do_lookup_authenticator(uri_decode(Binding), maps:from_list(Params)). 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) -> PasswordBasedDef = #{
case emqx_authn:lookup_authenticator(?CHAIN, Name) of type => object,
{ok, Authenticator} -> properties => #{
return({ok, Authenticator}); mechanism => #{
{error, Reason} -> type => string,
return(serialize_error(Reason)) enum => [<<"password-based">>],
end. 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) -> JWTDef = #{
do_list_authenticators(uri_decode(Binding), maps:from_list(Params)). 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) -> PasswordBasedBuiltInDatabaseDef = #{
case emqx_authn:list_authenticators(?CHAIN) of type => object,
{ok, Authenticators} -> properties => #{
return({ok, Authenticators}); server_type => #{
{error, Reason} -> type => string,
return(serialize_error(Reason)) enum => [<<"built-in-database">>],
end. 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) -> PasswordBasedMySQLDef = #{
do_move_authenticator(uri_decode(Binding), maps:from_list(Params)). 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">>}) -> PasswordBasedPgSQLDef = #{
case emqx_authn:move_authenticator_to_the_front(?CHAIN, Name) of type => object,
ok -> properties => #{
return(ok); server_type => #{
{error, Reason} -> type => string,
return(serialize_error(Reason)) enum => [<<"pgsql">>],
end; example => <<"pgsql">>
do_move_authenticator(#{name := Name}, #{<<"position">> := <<"the end">>}) -> },
case emqx_authn:move_authenticator_to_the_end(?CHAIN, Name) of server => #{
ok -> type => string,
return(ok); example => <<"localhost:5432">>
{error, Reason} -> },
return(serialize_error(Reason)) database => #{
end; type => string
do_move_authenticator(#{name := Name}, #{<<"position">> := N}) when is_number(N) -> },
case emqx_authn:move_authenticator_to_the_nth(?CHAIN, Name, N) of pool_size => #{
ok -> type => integer,
return(ok); default => 8
{error, Reason} -> },
return(serialize_error(Reason)) username => #{
end; type => string
do_move_authenticator(_Binding, _Params) -> },
return(serialize_error({missing_parameter, <<"position">>})). 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) -> PasswordBasedHTTPServerDef = #{
do_import_users(uri_decode(Binding), maps:from_list(Params)). 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}, PasswordHashAlgorithmDef = #{
#{<<"filename">> := Filename}) -> type => object,
case emqx_authn:import_users(?CHAIN, Name, Filename) of required => [name],
ok -> properties => #{
return(ok); name => #{
{error, Reason} -> type => string,
return(serialize_error(Reason)) enum => [<<"plain">>, <<"md5">>, <<"sha">>, <<"sha256">>, <<"sha512">>, <<"bcrypt">>],
end; default => <<"sha256">>
do_import_users(_Binding, Params) -> },
Missed = get_missed_params(Params, [<<"filename">>, <<"file_format">>]), salt_rounds => #{
return(serialize_error({missing_parameter, Missed})). type => integer,
default => 10
}
}
},
add_user(Binding, Params) -> SSLDef = #{
do_add_user(uri_decode(Binding), maps:from_list(Params)). 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) -> [ #{<<"authenticator">> => AuthenticatorDef}
case emqx_authn:add_user(?CHAIN, Name, UserInfo) of , #{<<"password_based">> => PasswordBasedDef}
{ok, User} -> , #{<<"jwt">> => JWTDef}
return({ok, User}); , #{<<"scram">> => SCRAMDef}
{error, Reason} -> , #{<<"password_based_built_in_database">> => PasswordBasedBuiltInDatabaseDef}
return(serialize_error(Reason)) , #{<<"password_based_mysql">> => PasswordBasedMySQLDef}
end. , #{<<"password_based_pgsql">> => PasswordBasedPgSQLDef}
, #{<<"password_based_http_server">> => PasswordBasedHTTPServerDef}
delete_user(Binding, Params) -> , #{<<"password_hash_algorithm">> => PasswordHashAlgorithmDef}
do_delete_user(uri_decode(Binding), maps:from_list(Params)). , #{<<"ssl">> => SSLDef}
].
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.

View File

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

View File

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